Bug #690177 - Use trust-prompt for certificate verification in WebDAV backends
authorMilan Crha <mcrha@redhat.com>
Wed, 9 Jan 2013 22:03:37 +0000 (23:03 +0100)
committerMilan Crha <mcrha@redhat.com>
Wed, 9 Jan 2013 22:04:44 +0000 (23:04 +0100)
12 files changed:
addressbook/backends/webdav/e-book-backend-webdav.c
calendar/backends/caldav/e-cal-backend-caldav.c
calendar/backends/http/e-cal-backend-http.c
libebackend/e-backend.c
libebackend/e-backend.h
libedataserver/e-source-webdav.c
libedataserver/e-source-webdav.h
modules/trust-prompt/module-trust-prompt.c
modules/trust-prompt/trust-prompt-gtk.c
modules/trust-prompt/trust-prompt.h
po/POTFILES.in
services/evolution-user-prompter/prompt-user-gtk.c

index cb0a403..ef3c2bb 100644 (file)
@@ -141,9 +141,52 @@ get_closure (EDataBookView *book_view)
        return g_object_get_data (G_OBJECT (book_view), WEBDAV_CLOSURE_NAME);
 }
 
+static guint
+send_and_handle_ssl (EBookBackendWebdav *webdav,
+                    SoupMessage *message,
+                    GCancellable *cancellable)
+{
+       guint status_code;
+
+       status_code = soup_session_send_message (webdav->priv->session, message);
+       if (status_code == SOUP_STATUS_SSL_FAILED) {
+               ESource *source;
+               ESourceWebdav *extension;
+               ESourceRegistry *registry;
+               EBackend *backend;
+               ETrustPromptResponse response;
+               ENamedParameters *parameters;
+
+               backend = E_BACKEND (webdav);
+               source = e_backend_get_source (backend);
+               registry = e_book_backend_get_registry (E_BOOK_BACKEND (backend));
+               extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+
+               parameters = e_named_parameters_new ();
+
+               response = e_source_webdav_prepare_ssl_trust_prompt (extension, message, registry, parameters);
+               if (response == E_TRUST_PROMPT_RESPONSE_UNKNOWN) {
+                       response = e_backend_trust_prompt_sync (backend, parameters, cancellable, NULL);
+                       if (response != E_TRUST_PROMPT_RESPONSE_UNKNOWN)
+                               e_source_webdav_store_ssl_trust_prompt (extension, message, response);
+               }
+
+               e_named_parameters_free (parameters);
+
+               if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT ||
+                   response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY) {
+                       g_object_set (webdav->priv->session, SOUP_SESSION_SSL_STRICT, FALSE, NULL);
+                       status_code = soup_session_send_message (webdav->priv->session, message);
+               }
+       }
+
+       return status_code;
+}
+
 static EContact *
 download_contact (EBookBackendWebdav *webdav,
-                  const gchar *uri)
+                  const gchar *uri,
+                 GCancellable *cancellable)
 {
        SoupMessage *message;
        const gchar  *etag;
@@ -154,7 +197,7 @@ download_contact (EBookBackendWebdav *webdav,
        soup_message_headers_append (message->request_headers, "User-Agent", USERAGENT);
        soup_message_headers_append (message->request_headers, "Connection", "close");
 
-       status = soup_session_send_message (webdav->priv->session, message);
+       status = send_and_handle_ssl (webdav, message, cancellable);
        if (status != 200) {
                g_warning ("Couldn't load '%s' (http status %d)", uri, status);
                g_object_unref (message);
@@ -194,7 +237,8 @@ download_contact (EBookBackendWebdav *webdav,
 static guint
 upload_contact (EBookBackendWebdav *webdav,
                 EContact *contact,
-                gchar **reason)
+                gchar **reason,
+               GCancellable *cancellable)
 {
        ESource     *source;
        ESourceWebdav *webdav_extension;
@@ -250,7 +294,7 @@ upload_contact (EBookBackendWebdav *webdav,
                message, "text/vcard", SOUP_MEMORY_TEMPORARY,
                request, strlen (request));
 
-       status   = soup_session_send_message (webdav->priv->session, message);
+       status   = send_and_handle_ssl (webdav, message, cancellable);
        new_etag = soup_message_headers_get_list (message->response_headers, "ETag");
 
        redir_uri = soup_message_headers_get_list (message->response_headers, "Location");
@@ -349,7 +393,7 @@ e_book_backend_webdav_create_contacts (EBookBackend *backend,
        /* kill revision field (might have been set by some other backend) */
        e_contact_set (contact, E_CONTACT_REV, NULL);
 
-       status = upload_contact (webdav, contact, &status_reason);
+       status = upload_contact (webdav, contact, &status_reason, cancellable);
        if (status != 201 && status != 204) {
                g_object_unref (contact);
                if (status == 401 || status == 407) {
@@ -377,7 +421,7 @@ e_book_backend_webdav_create_contacts (EBookBackend *backend,
 
                g_warning ("Server didn't return etag for new address resource");
                new_uid = e_contact_get_const (contact, E_CONTACT_UID);
-               new_contact = download_contact (webdav, new_uid);
+               new_contact = download_contact (webdav, new_uid, cancellable);
                g_object_unref (contact);
 
                if (new_contact == NULL) {
@@ -403,7 +447,8 @@ e_book_backend_webdav_create_contacts (EBookBackend *backend,
 
 static guint
 delete_contact (EBookBackendWebdav *webdav,
-                const gchar *uri)
+                const gchar *uri,
+               GCancellable *cancellable)
 {
        SoupMessage *message;
        guint        status;
@@ -412,7 +457,7 @@ delete_contact (EBookBackendWebdav *webdav,
        soup_message_headers_append (message->request_headers, "User-Agent", USERAGENT);
        soup_message_headers_append (message->request_headers, "Connection", "close");
 
-       status = soup_session_send_message (webdav->priv->session, message);
+       status = send_and_handle_ssl (webdav, message, cancellable);
        g_object_unref (message);
 
        return status;
@@ -449,7 +494,7 @@ e_book_backend_webdav_remove_contacts (EBookBackend *backend,
                return;
        }
 
-       status = delete_contact (webdav, uid);
+       status = delete_contact (webdav, uid, cancellable);
        if (status != 204) {
                if (status == 401 || status == 407) {
                        e_data_book_respond_remove_contacts (
@@ -512,7 +557,7 @@ e_book_backend_webdav_modify_contacts (EBookBackend *backend,
 
        /* modify contact */
        contact = e_contact_new_from_vcard (vcard);
-       status = upload_contact (webdav, contact, &status_reason);
+       status = upload_contact (webdav, contact, &status_reason, cancellable);
        if (status != 201 && status != 204) {
                g_object_unref (contact);
                if (status == 401 || status == 407) {
@@ -555,7 +600,7 @@ e_book_backend_webdav_modify_contacts (EBookBackend *backend,
                EContact *new_contact;
 
                g_warning ("Server didn't return etag for modified address resource");
-               new_contact = download_contact (webdav, uid);
+               new_contact = download_contact (webdav, uid, cancellable);
                if (new_contact != NULL) {
                        contact = new_contact;
                }
@@ -586,7 +631,7 @@ e_book_backend_webdav_get_contact (EBookBackend *backend,
                contact = e_book_backend_cache_get_contact (priv->cache, uid);
                g_mutex_unlock (&priv->cache_lock);
        } else {
-               contact = download_contact (webdav, uid);
+               contact = download_contact (webdav, uid, cancellable);
                /* update cache as we possibly have changes */
                if (contact != NULL) {
                        g_mutex_lock (&priv->cache_lock);
@@ -743,7 +788,8 @@ parse_propfind_response (xmlTextReaderPtr reader)
 }
 
 static SoupMessage *
-send_propfind (EBookBackendWebdav *webdav)
+send_propfind (EBookBackendWebdav *webdav,
+              GCancellable *cancellable)
 {
        SoupMessage               *message;
        EBookBackendWebdavPrivate *priv    = webdav->priv;
@@ -759,7 +805,7 @@ send_propfind (EBookBackendWebdav *webdav)
                message, "text/xml", SOUP_MEMORY_TEMPORARY,
                (gchar *) request, strlen (request));
 
-       soup_session_send_message (priv->session, message);
+       send_and_handle_ssl (webdav, message, cancellable);
 
        return message;
 }
@@ -835,7 +881,8 @@ xp_object_get_status (xmlXPathObjectPtr result)
 
 static gboolean
 check_addressbook_changed (EBookBackendWebdav *webdav,
-                           gchar **new_ctag)
+                           gchar **new_ctag,
+                          GCancellable *cancellable)
 {
        gboolean res = TRUE;
        const gchar *request = "<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\"><prop><getctag/></prop></propfind>";
@@ -861,7 +908,7 @@ check_addressbook_changed (EBookBackendWebdav *webdav,
        soup_message_headers_append (message->request_headers, "Connection", "close");
        soup_message_headers_append (message->request_headers, "Depth", "0");
        soup_message_set_request (message, "text/xml", SOUP_MEMORY_TEMPORARY, (gchar *) request, strlen (request));
-       soup_session_send_message (priv->session, message);
+       send_and_handle_ssl (webdav, message, cancellable);
 
        if (message->status_code == 207 && message->response_body) {
                xmlDocPtr xml;
@@ -916,7 +963,8 @@ check_addressbook_changed (EBookBackendWebdav *webdav,
 static GError *
 download_contacts (EBookBackendWebdav *webdav,
                    EFlag *running,
-                  EDataBookView *book_view)
+                  EDataBookView *book_view,
+                  GCancellable *cancellable)
 {
        EBookBackendWebdavPrivate *priv = webdav->priv;
        EBookBackend              *book_backend;
@@ -932,7 +980,7 @@ download_contacts (EBookBackendWebdav *webdav,
 
        g_mutex_lock (&priv->update_lock);
 
-       if (!check_addressbook_changed (webdav, &new_ctag)) {
+       if (!check_addressbook_changed (webdav, &new_ctag, cancellable)) {
                g_free (new_ctag);
                g_mutex_unlock (&priv->update_lock);
                return EDB_ERROR (SUCCESS);
@@ -945,7 +993,7 @@ download_contacts (EBookBackendWebdav *webdav,
                                _("Loading Addressbook summary..."));
        }
 
-       message = send_propfind (webdav);
+       message = send_propfind (webdav, cancellable);
        status  = message->status_code;
 
        if (status == 401 || status == 407) {
@@ -1047,7 +1095,7 @@ download_contacts (EBookBackendWebdav *webdav,
                /* download contact if it is not cached or its ETag changed */
                if (contact == NULL || etag == NULL ||
                    strcmp (e_contact_get_const (contact, E_CONTACT_REV), etag) != 0) {
-                       contact = download_contact (webdav, complete_uri);
+                       contact = download_contact (webdav, complete_uri, cancellable);
                        if (contact != NULL) {
                                g_mutex_lock (&priv->cache_lock);
                                if (e_book_backend_cache_remove_contact (priv->cache, complete_uri))
@@ -1103,7 +1151,7 @@ book_view_thread (gpointer data)
         * it's stopped */
        g_object_ref (book_view);
 
-       error = download_contacts (webdav, closure->running, book_view);
+       error = download_contacts (webdav, closure->running, book_view, NULL);
 
        g_object_unref (book_view);
 
@@ -1191,7 +1239,7 @@ e_book_backend_webdav_get_contact_list (EBookBackend *backend,
 
        if (e_backend_get_online (E_BACKEND (backend))) {
                /* make sure the cache is up to date */
-               GError *error = download_contacts (webdav, NULL, NULL);
+               GError *error = download_contacts (webdav, NULL, NULL, cancellable);
 
                if (error) {
                        e_data_book_respond_get_contact_list (book, opid, error, NULL);
@@ -1235,7 +1283,7 @@ e_book_backend_webdav_get_contact_list_uids (EBookBackend *backend,
 
        if (e_backend_get_online (E_BACKEND (backend))) {
                /* make sure the cache is up to date */
-               GError *error = download_contacts (webdav, NULL, NULL);
+               GError *error = download_contacts (webdav, NULL, NULL, cancellable);
 
                if (error) {
                        e_data_book_respond_get_contact_list_uids (book, opid, error, NULL);
@@ -1372,13 +1420,13 @@ e_book_backend_webdav_open (EBookBackend *backend,
        g_mutex_unlock (&priv->cache_lock);
 
        session = soup_session_sync_new ();
-       g_object_set (session, SOUP_SESSION_TIMEOUT, 90, NULL);
+       g_object_set (session,
+               SOUP_SESSION_TIMEOUT, 90,
+               SOUP_SESSION_SSL_STRICT, TRUE,
+               SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+               NULL);
 
-       g_object_bind_property (
-               webdav_extension, "ignore-invalid-cert",
-               session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
-               G_BINDING_SYNC_CREATE |
-               G_BINDING_INVERT_BOOLEAN);
+       e_source_webdav_unset_temporary_ssl_trust (webdav_extension);
 
        g_signal_connect (
                session, "authenticate",
@@ -1514,7 +1562,7 @@ book_backend_webdav_try_password_sync (ESourceAuthenticator *authenticator,
        webdav->priv->password = g_strdup (password->str);
 
        /* Send a PROPFIND to test whether user/password is correct. */
-       message = send_propfind (webdav);
+       message = send_propfind (webdav, cancellable);
 
        switch (message->status_code) {
                case SOUP_STATUS_OK:
index f7f7635..ece46ee 100644 (file)
@@ -536,10 +536,6 @@ status_code_to_result (SoupMessage *message,
                        GError **perror)
 {
        ECalBackendCalDAVPrivate *priv;
-       ESource *source;
-       ESourceWebdav *extension;
-       const gchar *extension_name;
-       gboolean ignore_invalid_cert;
 
        g_return_val_if_fail (cbdav != NULL, FALSE);
        g_return_val_if_fail (message != NULL, FALSE);
@@ -550,11 +546,8 @@ status_code_to_result (SoupMessage *message,
                return TRUE;
        }
 
-       source = e_backend_get_source (E_BACKEND (cbdav));
-
-       extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
-       extension = e_source_get_extension (source, extension_name);
-       ignore_invalid_cert = e_source_webdav_get_ignore_invalid_cert (extension);
+       if (perror && *perror)
+               return FALSE;
 
        switch (message->status_code) {
        case SOUP_STATUS_CANT_CONNECT:
@@ -590,22 +583,12 @@ status_code_to_result (SoupMessage *message,
                break;
 
        case SOUP_STATUS_SSL_FAILED:
-               if (ignore_invalid_cert) {
-                       g_propagate_error (
-                               perror,
-                               e_data_cal_create_error_fmt ( OtherError,
-                               _("Failed to connect to a server using SSL: %s"),
-                               message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
-                               (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
-               } else {
-                       g_propagate_error (
-                               perror, EDC_ERROR_EX (OtherError,
-                               _("Failed to connect to a server using SSL. "
-                               "One possible reason is an invalid certificate being used by the server. "
-                               "If this is expected, like self-signed certificate being used on the server, "
-                               "then disable certificate validity tests by selecting 'Ignore invalid SSL certificate' option "
-                               "in Properties")));
-               }
+               g_propagate_error (
+                       perror,
+                       e_data_cal_create_error_fmt ( OtherError,
+                       _("Failed to connect to a server using SSL: %s"),
+                       message->reason_phrase && *message->reason_phrase ? message->reason_phrase :
+                       (soup_status_get_phrase (message->status_code) ? soup_status_get_phrase (message->status_code) : _("Unknown error"))));
                break;
 
        default:
@@ -1040,21 +1023,55 @@ redirect_handler (SoupMessage *msg,
 }
 
 static void
-send_and_handle_redirection (SoupSession *soup_session,
+send_and_handle_redirection (ECalBackendCalDAV *cbdav,
                              SoupMessage *msg,
-                             gchar **new_location)
+                             gchar **new_location,
+                            GCancellable *cancellable,
+                            GError **error)
 {
        gchar *old_uri = NULL;
 
+       g_return_if_fail (E_IS_CAL_BACKEND_CALDAV (cbdav));
        g_return_if_fail (msg != NULL);
 
        if (new_location)
                old_uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
 
        soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT);
-       soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), soup_session);
+       soup_message_add_header_handler (msg, "got_body", "Location", G_CALLBACK (redirect_handler), cbdav->priv->session);
        soup_message_headers_append (msg->request_headers, "Connection", "close");
-       soup_session_send_message (soup_session, msg);
+       soup_session_send_message (cbdav->priv->session, msg);
+
+       if (msg->status_code == SOUP_STATUS_SSL_FAILED) {
+               ESource *source;
+               ESourceWebdav *extension;
+               ESourceRegistry *registry;
+               EBackend *backend;
+               ETrustPromptResponse response;
+               ENamedParameters *parameters;
+
+               backend = E_BACKEND (cbdav);
+               source = e_backend_get_source (backend);
+               registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
+               extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+
+               parameters = e_named_parameters_new ();
+
+               response = e_source_webdav_prepare_ssl_trust_prompt (extension, msg, registry, parameters);
+               if (response == E_TRUST_PROMPT_RESPONSE_UNKNOWN) {
+                       response = e_backend_trust_prompt_sync (backend, parameters, cancellable, error);
+                       if (response != E_TRUST_PROMPT_RESPONSE_UNKNOWN)
+                               e_source_webdav_store_ssl_trust_prompt (extension, msg, response);
+               }
+
+               e_named_parameters_free (parameters);
+
+               if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT ||
+                   response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY) {
+                       g_object_set (cbdav->priv->session, SOUP_SESSION_SSL_STRICT, FALSE, NULL);
+                       soup_session_send_message (cbdav->priv->session, msg);
+               }
+       }
 
        if (new_location) {
                gchar *new_loc = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
@@ -1088,19 +1105,20 @@ caldav_generate_uri (ECalBackendCalDAV *cbdav,
 static gboolean
 caldav_server_open_calendar (ECalBackendCalDAV *cbdav,
                              gboolean *server_unreachable,
+                            GCancellable *cancellable,
                              GError **perror)
 {
-       SoupMessage               *message;
-       const gchar                *header;
-       gboolean                   calendar_access;
-       gboolean                   put_allowed;
-       gboolean                   delete_allowed;
+       SoupMessage *message;
+       const gchar *header;
+       gboolean calendar_access;
+       gboolean put_allowed;
+       gboolean delete_allowed;
+       ESource *source;
+       ESourceWebdav *webdav_extension;
 
        g_return_val_if_fail (cbdav != NULL, FALSE);
        g_return_val_if_fail (server_unreachable != NULL, FALSE);
 
-       /* FIXME: setup text_uri */
-
        message = soup_message_new (SOUP_METHOD_OPTIONS, cbdav->priv->uri);
        if (message == NULL) {
                g_propagate_error (perror, EDC_ERROR (NoSuchCal));
@@ -1110,7 +1128,14 @@ caldav_server_open_calendar (ECalBackendCalDAV *cbdav,
                message->request_headers,
                "User-Agent", "Evolution/" VERSION);
 
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       /* re-check server's certificate trust, if needed */
+       g_object_set (cbdav->priv->session, SOUP_SESSION_SSL_STRICT, TRUE, NULL);
+
+       source = e_backend_get_source (E_BACKEND (cbdav));
+       webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+       e_source_webdav_unset_temporary_ssl_trust (webdav_extension);
+
+       send_and_handle_redirection (cbdav, message, NULL, cancellable, perror);
 
        if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
                switch (message->status_code) {
@@ -1265,7 +1290,7 @@ check_calendar_changed_on_server (ECalBackendCalDAV *cbdav)
                buf_content, buf_size);
 
        /* Send the request now */
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       send_and_handle_redirection (cbdav, message, NULL, NULL, NULL);
 
        /* Clean up the memory */
        xmlOutputBufferClose (buf);
@@ -1418,7 +1443,7 @@ caldav_server_list_objects (ECalBackendCalDAV *cbdav,
                buf_content, buf_size);
 
        /* Send the request now */
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       send_and_handle_redirection (cbdav, message, NULL, NULL, NULL);
 
        /* Clean up the memory */
        xmlOutputBufferClose (buf);
@@ -1475,7 +1500,7 @@ caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
        }
 
        soup_message_headers_append (message->request_headers, "User-Agent", "Evolution/" VERSION);
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       send_and_handle_redirection (cbdav, message, NULL, NULL, NULL);
 
        if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
                status_code_to_result (message, cbdav, FALSE, error);
@@ -1498,6 +1523,7 @@ caldav_server_download_attachment (ECalBackendCalDAV *cbdav,
 static gboolean
 caldav_server_get_object (ECalBackendCalDAV *cbdav,
                           CalDAVObject *object,
+                         GCancellable *cancellable,
                           GError **perror)
 {
        SoupMessage              *message;
@@ -1518,7 +1544,7 @@ caldav_server_get_object (ECalBackendCalDAV *cbdav,
                message->request_headers,
                "User-Agent", "Evolution/" VERSION);
 
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       send_and_handle_redirection (cbdav, message, NULL, cancellable, perror);
 
        if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
                status_code_to_result (message, cbdav, FALSE, perror);
@@ -1564,6 +1590,7 @@ static void
 caldav_post_freebusy (ECalBackendCalDAV *cbdav,
                       const gchar *url,
                       gchar **post_fb,
+                     GCancellable *cancellable,
                       GError **error)
 {
        SoupMessage *message;
@@ -1586,7 +1613,7 @@ caldav_post_freebusy (ECalBackendCalDAV *cbdav,
                SOUP_MEMORY_COPY,
                *post_fb, strlen (*post_fb));
 
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       send_and_handle_redirection (cbdav, message, NULL, cancellable, error);
 
        if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code)) {
                status_code_to_result (message, cbdav, FALSE, error);
@@ -1645,6 +1672,7 @@ static gboolean
 caldav_server_put_object (ECalBackendCalDAV *cbdav,
                           CalDAVObject *object,
                           icalcomponent *icalcomp,
+                         GCancellable *cancellable,
                           GError **perror)
 {
        SoupMessage              *message;
@@ -1687,7 +1715,7 @@ caldav_server_put_object (ECalBackendCalDAV *cbdav,
                strlen (object->cdata));
 
        uri = NULL;
-       send_and_handle_redirection (cbdav->priv->session, message, &uri);
+       send_and_handle_redirection (cbdav, message, &uri, cancellable, perror);
 
        if (uri) {
                gchar *file = strrchr (uri, '/');
@@ -1734,7 +1762,7 @@ caldav_server_put_object (ECalBackendCalDAV *cbdav,
                        }
                }
 
-               if (!caldav_server_get_object (cbdav, object, &local_error)) {
+               if (!caldav_server_get_object (cbdav, object, cancellable, &local_error)) {
                        if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
                                gchar *file;
 
@@ -1748,7 +1776,7 @@ caldav_server_put_object (ECalBackendCalDAV *cbdav,
                                        g_free (object->href);
                                        object->href = file;
 
-                                       if (!caldav_server_get_object (cbdav, object, &local_error)) {
+                                       if (!caldav_server_get_object (cbdav, object, cancellable, &local_error)) {
                                                if (g_error_matches (local_error, E_DATA_CAL_ERROR, ObjectNotFound)) {
                                                        g_clear_error (&local_error);
 
@@ -1792,6 +1820,7 @@ caldav_server_put_object (ECalBackendCalDAV *cbdav,
 static void
 caldav_server_delete_object (ECalBackendCalDAV *cbdav,
                              CalDAVObject *object,
+                            GCancellable *cancellable,
                              GError **perror)
 {
        SoupMessage              *message;
@@ -1817,7 +1846,7 @@ caldav_server_delete_object (ECalBackendCalDAV *cbdav,
                        "If-Match", object->etag);
        }
 
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       send_and_handle_redirection (cbdav, message, NULL, cancellable, perror);
 
        status_code_to_result (message, cbdav, FALSE, perror);
 
@@ -1828,7 +1857,9 @@ caldav_server_delete_object (ECalBackendCalDAV *cbdav,
 }
 
 static gboolean
-caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
+caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav,
+                                   GCancellable *cancellable,
+                                   GError **error)
 {
        SoupMessage *message;
        xmlOutputBufferPtr buf;
@@ -1870,7 +1901,7 @@ caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
                buf_content, buf_size);
 
        /* Send the request now */
-       send_and_handle_redirection (cbdav->priv->session, message, NULL);
+       send_and_handle_redirection (cbdav, message, NULL, cancellable, error);
 
        /* Clean up the memory */
        xmlOutputBufferClose (buf);
@@ -1921,7 +1952,7 @@ caldav_receive_schedule_outbox_url (ECalBackendCalDAV *cbdav)
                        buf_content, buf_size);
 
                /* Send the request now */
-               send_and_handle_redirection (cbdav->priv->session, message, NULL);
+               send_and_handle_redirection (cbdav, message, NULL, cancellable, error);
 
                if (message->status_code == 207 && parse_propfind_response (message, XPATH_SCHEDULE_OUTBOX_URL_STATUS, XPATH_SCHEDULE_OUTBOX_URL, &cbdav->priv->schedule_outbox_url)) {
                        if (!*cbdav->priv->schedule_outbox_url) {
@@ -2365,7 +2396,7 @@ caldav_synch_slave_loop (gpointer data)
                        GError *local_error = NULL;
                        gboolean online;
 
-                       if (caldav_server_open_calendar (cbdav, &server_unreachable, &local_error)) {
+                       if (caldav_server_open_calendar (cbdav, &server_unreachable, NULL, &local_error)) {
                                cbdav->priv->opened = TRUE;
                                update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
                                g_cond_signal (&cbdav->priv->cond);
@@ -2715,6 +2746,7 @@ initialize_backend (ECalBackendCalDAV *cbdav,
 
 static gboolean
 open_calendar (ECalBackendCalDAV *cbdav,
+              GCancellable *cancellable,
                GError **error)
 {
        gboolean server_unreachable = FALSE;
@@ -2727,7 +2759,7 @@ open_calendar (ECalBackendCalDAV *cbdav,
        proxy_settings_changed (cbdav->priv->proxy, cbdav->priv);
 
        success = caldav_server_open_calendar (
-               cbdav, &server_unreachable, &local_error);
+               cbdav, &server_unreachable, cancellable, &local_error);
 
        if (success) {
                update_slave_cmd (cbdav->priv, SLAVE_SHOULD_WORK);
@@ -2790,7 +2822,7 @@ caldav_do_open (ECalBackendSync *backend,
        if (online) {
                GError *local_error = NULL;
 
-               opened = open_calendar (cbdav, &local_error);
+               opened = open_calendar (cbdav, cancellable, &local_error);
 
                if (g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationRequired) || g_error_matches (local_error, E_DATA_CAL_ERROR, AuthenticationFailed)) {
                        g_clear_error (&local_error);
@@ -3683,6 +3715,7 @@ do_create_objects (ECalBackendCalDAV *cbdav,
                    const GSList *in_calobjs,
                    GSList **uids,
                    GSList **new_components,
+                  GCancellable *cancellable,
                    GError **perror)
 {
        ECalComponent            *comp;
@@ -3755,7 +3788,7 @@ do_create_objects (ECalBackendCalDAV *cbdav,
                object.etag  = NULL;
                object.cdata = pack_cobj (cbdav, icalcomp);
 
-               did_put = caldav_server_put_object (cbdav, &object, icalcomp, perror);
+               did_put = caldav_server_put_object (cbdav, &object, icalcomp, cancellable, perror);
 
                caldav_object_free (&object, FALSE);
        } else {
@@ -3781,6 +3814,7 @@ do_modify_objects (ECalBackendCalDAV *cbdav,
                    CalObjModType mod,
                    GSList **old_components,
                    GSList **new_components,
+                  GCancellable *cancellable,
                    GError **error)
 {
        ECalComponent            *comp;
@@ -3920,7 +3954,7 @@ do_modify_objects (ECalBackendCalDAV *cbdav,
                object.etag  = etag;
                object.cdata = pack_cobj (cbdav, cache_comp);
 
-               did_put = caldav_server_put_object (cbdav, &object, cache_comp, error);
+               did_put = caldav_server_put_object (cbdav, &object, cache_comp, cancellable, error);
 
                caldav_object_free (&object, FALSE);
                href = NULL;
@@ -3951,6 +3985,7 @@ do_remove_objects (ECalBackendCalDAV *cbdav,
                    CalObjModType mod,
                    GSList **old_components,
                    GSList **new_components,
+                  GCancellable *cancellable,
                    GError **perror)
 {
        icalcomponent            *cache_comp;
@@ -4033,9 +4068,9 @@ do_remove_objects (ECalBackendCalDAV *cbdav,
                if (mod == CALOBJ_MOD_THIS && rid && *rid) {
                        caldav_object.cdata = pack_cobj (cbdav, cache_comp);
 
-                       caldav_server_put_object (cbdav, &caldav_object, cache_comp, perror);
+                       caldav_server_put_object (cbdav, &caldav_object, cache_comp, cancellable, perror);
                } else
-                       caldav_server_delete_object (cbdav, &caldav_object, perror);
+                       caldav_server_delete_object (cbdav, &caldav_object, cancellable, perror);
 
                caldav_object_free (&caldav_object, FALSE);
                href = NULL;
@@ -4130,6 +4165,7 @@ process_object (ECalBackendCalDAV *cbdav,
                 ECalComponent *ecomp,
                 gboolean online,
                 icalproperty_method method,
+               GCancellable *cancellable,
                 GError **error)
 {
        ESourceRegistry *registry;
@@ -4171,7 +4207,7 @@ process_object (ECalBackendCalDAV *cbdav,
 
                                new_obj_strs.data = new_obj_str;
                                do_modify_objects (cbdav, &new_obj_strs, mod,
-                                                 &old_components, &new_components, &err);
+                                                 &old_components, &new_components, cancellable, &err);
                                if (!err && new_components && new_components->data) {
                                        if (!old_components || !old_components->data) {
                                                e_cal_backend_notify_component_created (backend, new_components->data);
@@ -4187,7 +4223,7 @@ process_object (ECalBackendCalDAV *cbdav,
                                GSList ids = {0,};
 
                                ids.data = id;
-                               do_remove_objects (cbdav, &ids, mod, &old_components, &new_components, &err);
+                               do_remove_objects (cbdav, &ids, mod, &old_components, &new_components, cancellable, &err);
                                if (!err && old_components && old_components->data) {
                                        if (new_components && new_components->data) {
                                                e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
@@ -4205,7 +4241,7 @@ process_object (ECalBackendCalDAV *cbdav,
 
                        new_objs.data = new_obj_str;
 
-                       do_create_objects (cbdav, &new_objs, NULL, &new_components, &err);
+                       do_create_objects (cbdav, &new_objs, NULL, &new_components, cancellable, &err);
 
                        if (!err) {
                                if (new_components && new_components->data)
@@ -4221,7 +4257,7 @@ process_object (ECalBackendCalDAV *cbdav,
                        GSList ids = {0,};
 
                        ids.data = id;
-                       do_remove_objects (cbdav, &ids, CALOBJ_MOD_THIS, &old_components, &new_components, &err);
+                       do_remove_objects (cbdav, &ids, CALOBJ_MOD_THIS, &old_components, &new_components, cancellable, &err);
                        if (!err && old_components && old_components->data) {
                                if (new_components && new_components->data) {
                                        e_cal_backend_notify_component_modified (backend, old_components->data, new_components->data);
@@ -4307,7 +4343,7 @@ do_receive_objects (ECalBackendSync *backend,
                        method = tmethod;
                }
 
-               process_object (cbdav, ecomp, online, method, &err);
+               process_object (cbdav, ecomp, online, method, cancellable, &err);
                g_object_unref (ecomp);
        }
 
@@ -4363,6 +4399,7 @@ caldav_busy_stub (
                   in_calobjs,
                   uids,
                   new_components,
+                 cancellable,
                   perror))
 
 caldav_busy_stub (
@@ -4381,6 +4418,7 @@ caldav_busy_stub (
                   mod,
                   old_components,
                   new_components,
+                 cancellable,
                   perror))
 
 caldav_busy_stub (
@@ -4399,6 +4437,7 @@ caldav_busy_stub (
                   mod,
                   old_components,
                   new_components,
+                 cancellable,
                   perror))
 
 caldav_busy_stub (
@@ -4634,10 +4673,11 @@ caldav_get_free_busy (ECalBackendSync *backend,
        }
 
        if (!cbdav->priv->schedule_outbox_url) {
-               caldav_receive_schedule_outbox_url (cbdav);
+               caldav_receive_schedule_outbox_url (cbdav, cancellable, error);
                if (!cbdav->priv->schedule_outbox_url) {
                        cbdav->priv->calendar_schedule = FALSE;
-                       g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Schedule outbox url not found")));
+                       if (error && !*error)
+                               g_propagate_error (error, EDC_ERROR_EX (OtherError, _("Schedule outbox url not found")));
                        return;
                }
        }
@@ -4718,7 +4758,7 @@ caldav_get_free_busy (ECalBackendSync *backend,
 
        e_return_data_cal_error_if_fail (str != NULL, OtherError);
 
-       caldav_post_freebusy (cbdav, cbdav->priv->schedule_outbox_url, &str, &err);
+       caldav_post_freebusy (cbdav, cbdav->priv->schedule_outbox_url, &str, cancellable, &err);
 
        if (!err) {
                /* parse returned xml */
@@ -4890,7 +4930,7 @@ caldav_try_password_sync (ESourceAuthenticator *authenticator,
        g_free (cbdav->priv->password);
        cbdav->priv->password = g_strdup (password->str);
 
-       open_calendar (cbdav, &local_error);
+       open_calendar (cbdav, cancellable, &local_error);
 
        if (local_error == NULL) {
                result = E_SOURCE_AUTHENTICATION_ACCEPTED;
@@ -4996,23 +5036,6 @@ e_cal_backend_caldav_finalize (GObject *object)
 static void
 cal_backend_caldav_constructed (GObject *object)
 {
-       ESource *source;
-       ESourceWebdav *extension;
-       ECalBackendCalDAV *cbdav;
-       const gchar *extension_name;
-
-       cbdav = E_CAL_BACKEND_CALDAV (object);
-
-       source = e_backend_get_source (E_BACKEND (cbdav));
-       extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
-       extension = e_source_get_extension (source, extension_name);
-
-       g_object_bind_property (
-               extension, "ignore-invalid-cert",
-               cbdav->priv->session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
-               G_BINDING_SYNC_CREATE |
-               G_BINDING_INVERT_BOOLEAN);
-
        /* Chain up to parent's constructed() method. */
        G_OBJECT_CLASS (e_cal_backend_caldav_parent_class)->
                constructed (object);
@@ -5023,7 +5046,11 @@ e_cal_backend_caldav_init (ECalBackendCalDAV *cbdav)
 {
        cbdav->priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
        cbdav->priv->session = soup_session_sync_new ();
-       g_object_set (cbdav->priv->session, SOUP_SESSION_TIMEOUT, 90, NULL);
+       g_object_set (cbdav->priv->session,
+               SOUP_SESSION_TIMEOUT, 90,
+               SOUP_SESSION_SSL_STRICT, TRUE,
+               SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+               NULL);
 
        cbdav->priv->proxy = e_proxy_new ();
        e_proxy_setup_proxy (cbdav->priv->proxy);
index c2db383..dad22eb 100644 (file)
@@ -160,31 +160,22 @@ e_cal_backend_http_finalize (GObject *object)
 static void
 e_cal_backend_http_constructed (GObject *object)
 {
-       ESource *source;
        ECalBackendHttp *backend;
-       ESourceWebdav *extension;
        SoupSession *soup_session;
-       const gchar *extension_name;
 
        /* Chain up to parent's constructed() method. */
        G_OBJECT_CLASS (e_cal_backend_http_parent_class)->constructed (object);
 
        soup_session = soup_session_sync_new ();
-       g_object_set (soup_session, SOUP_SESSION_TIMEOUT, 90, NULL);
+       g_object_set (soup_session,
+               SOUP_SESSION_TIMEOUT, 90,
+               SOUP_SESSION_SSL_STRICT, TRUE,
+               SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE,
+               NULL);
 
        backend = E_CAL_BACKEND_HTTP (object);
        backend->priv->soup_session = soup_session;
 
-       source = e_backend_get_source (E_BACKEND (backend));
-       extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
-       extension = e_source_get_extension (source, extension_name);
-
-       g_object_bind_property (
-               extension, "ignore-invalid-cert",
-               soup_session, SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE,
-               G_BINDING_SYNC_CREATE |
-               G_BINDING_INVERT_BOOLEAN);
-
        g_signal_connect (
                backend->priv->soup_session, "authenticate",
                G_CALLBACK (soup_authenticate), backend);
@@ -493,6 +484,36 @@ cal_backend_http_load (ECalBackendHttp *backend,
        }
 
        status_code = soup_session_send_message (soup_session, soup_message);
+       if (status_code == SOUP_STATUS_SSL_FAILED) {
+               ESource *source;
+               ESourceWebdav *extension;
+               ESourceRegistry *registry;
+               EBackend *ebackend;
+               ETrustPromptResponse response;
+               ENamedParameters *parameters;
+
+               ebackend = E_BACKEND (backend);
+               source = e_backend_get_source (ebackend);
+               registry = e_cal_backend_get_registry (E_CAL_BACKEND (backend));
+               extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
+
+               parameters = e_named_parameters_new ();
+
+               response = e_source_webdav_prepare_ssl_trust_prompt (extension, soup_message, registry, parameters);
+               if (response == E_TRUST_PROMPT_RESPONSE_UNKNOWN) {
+                       response = e_backend_trust_prompt_sync (ebackend, parameters, cancellable, NULL);
+                       if (response != E_TRUST_PROMPT_RESPONSE_UNKNOWN)
+                               e_source_webdav_store_ssl_trust_prompt (extension, soup_message, response);
+               }
+
+               e_named_parameters_free (parameters);
+
+               if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT ||
+                   response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY) {
+                       g_object_set (soup_session, SOUP_SESSION_SSL_STRICT, FALSE, NULL);
+                       status_code = soup_session_send_message (soup_session, soup_message);
+               }
+       }
 
        if (G_IS_CANCELLABLE (cancellable))
                g_cancellable_disconnect (cancellable, cancel_id);
@@ -841,6 +862,7 @@ e_cal_backend_http_open (ECalBackendSync *backend,
        ESource *source;
        ESourceRegistry *registry;
        ESourceAuthentication *auth_extension;
+       ESourceWebdav *webdav_extension;
        const gchar *extension_name;
        const gchar *cache_dir;
        gboolean auth_required;
@@ -867,6 +889,12 @@ e_cal_backend_http_open (ECalBackendSync *backend,
        auth_extension = e_source_get_extension (source, extension_name);
        auth_required = e_source_authentication_required (auth_extension);
 
+       extension_name = E_SOURCE_EXTENSION_WEBDAV_BACKEND;
+       webdav_extension = e_source_get_extension (source, extension_name);
+
+       g_object_set (cbhttp->priv->soup_session, SOUP_SESSION_SSL_STRICT, TRUE, NULL);
+       e_source_webdav_unset_temporary_ssl_trust (webdav_extension);
+
        if (priv->source_changed_id == 0) {
                priv->source_changed_id = g_signal_connect (
                        source, "changed",
index d24ba37..494100f 100644 (file)
  * All #EBackend instances are created by an #EBackendFactory.
  **/
 
-#include "e-backend.h"
-
 #include <config.h>
 #include <glib/gi18n-lib.h>
 
 #include <gio/gio.h>
 
+#include <libedataserver/libedataserver.h>
+
+#include "e-backend.h"
+#include "e-user-prompter.h"
+
 #define E_BACKEND_GET_PRIVATE(obj) \
        (G_TYPE_INSTANCE_GET_PRIVATE \
        ((obj), E_TYPE_BACKEND, EBackendPrivate))
@@ -46,6 +49,7 @@ typedef struct _AsyncContext AsyncContext;
 
 struct _EBackendPrivate {
        ESource *source;
+       EUserPrompter *prompter;
        gboolean online;
 };
 
@@ -56,7 +60,8 @@ struct _AsyncContext {
 enum {
        PROP_0,
        PROP_ONLINE,
-       PROP_SOURCE
+       PROP_SOURCE,
+       PROP_USER_PROMPTER
 };
 
 G_DEFINE_ABSTRACT_TYPE (EBackend, e_backend, G_TYPE_OBJECT)
@@ -121,6 +126,12 @@ backend_get_property (GObject *object,
                                value, e_backend_get_source (
                                E_BACKEND (object)));
                        return;
+
+               case PROP_USER_PROMPTER:
+                       g_value_set_object (
+                               value, e_backend_get_user_prompter (
+                               E_BACKEND (object)));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -138,6 +149,11 @@ backend_dispose (GObject *object)
                priv->source = NULL;
        }
 
+       if (priv->prompter) {
+               g_object_unref (priv->prompter);
+               priv->prompter = NULL;
+       }
+
        /* Chain up to parent's dispose() method. */
        G_OBJECT_CLASS (e_backend_parent_class)->dispose (object);
 }
@@ -280,12 +296,24 @@ e_backend_class_init (EBackendClass *class)
                        G_PARAM_READWRITE |
                        G_PARAM_CONSTRUCT_ONLY |
                        G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (
+               object_class,
+               PROP_USER_PROMPTER,
+               g_param_spec_object (
+                       "user-prompter",
+                       "User Prompter",
+                       "User prompter instance",
+                       E_TYPE_USER_PROMPTER,
+                       G_PARAM_READABLE |
+                       G_PARAM_STATIC_STRINGS));
 }
 
 static void
 e_backend_init (EBackend *backend)
 {
        backend->priv = E_BACKEND_GET_PRIVATE (backend);
+       backend->priv->prompter = e_user_prompter_new ();
 }
 
 /**
@@ -461,3 +489,185 @@ e_backend_authenticate_finish (EBackend *backend,
        return class->authenticate_finish (backend, result, error);
 }
 
+/**
+ * e_backend_get_user_prompter:
+ * @backend: an #EBackend
+ *
+ * Gets an instance of #EUserPrompter, associated with this @backend.
+ * The instance is owned by the @backend.
+ *
+ * Returns: (transfer-none): an #EUserPrompter instance
+ *
+ * Since: 3.8
+ **/
+EUserPrompter *
+e_backend_get_user_prompter (EBackend *backend)
+{
+       g_return_val_if_fail (E_IS_BACKEND (backend), NULL);
+
+       return backend->priv->prompter;
+}
+
+/**
+ * e_backend_trust_prompt_sync:
+ * @backend: an #EBackend
+ * @parameters: an #ENamedParameters with values for the trust prompt
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Asks a user a trust prompt with given @parameters, and returns what
+ * user responded. This blocks until the response is delivered.
+ *
+ * Returns: an #ETrustPromptResponse what user responded
+ *
+ * Note: The function can return also %E_TRUST_PROMPT_RESPONSE_UNKNOWN,
+ *    it's on error or if user closes the trust prompt dialog with other
+ *    than the offered buttons. Usual behaviour in such case is to treat
+ *    it as a temporary reject.
+ *
+ * Since: 3.8
+ **/
+ETrustPromptResponse
+e_backend_trust_prompt_sync (EBackend *backend,
+                            const ENamedParameters *parameters,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       EUserPrompter *prompter;
+       gint response;
+
+       g_return_val_if_fail (E_IS_BACKEND (backend), E_TRUST_PROMPT_RESPONSE_UNKNOWN);
+       g_return_val_if_fail (parameters != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
+
+       prompter = e_backend_get_user_prompter (backend);
+       g_return_val_if_fail (prompter != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
+
+       /* Just a workaround for libsoup bug about not providing connection certificate
+          on failed requests. This is fixed in libsoup since 2.41.3, but let's have this
+          for now. I wrote it here to avoid code duplication, and once the libsoup 2.42.0
+          will be out all this code, together with other SOUP_CHECK_VERSION usages also
+          in evolution, will be removed.
+       */
+       if (!e_named_parameters_get (parameters, "cert")) {
+               GSList *button_captions = NULL;
+               const gchar *markup;
+               gchar *tmp = NULL;
+
+               button_captions = g_slist_append (button_captions, _("_Reject"));
+               button_captions = g_slist_append (button_captions, _("Accept _Temporarily"));
+               button_captions = g_slist_append (button_captions, _("_Accept Permanently"));
+
+               markup = e_named_parameters_get (parameters, "markup");
+               if (!markup) {
+                       gchar *bhost;
+
+                       bhost = g_strconcat ("<b>", e_named_parameters_get (parameters, "host"), "</b>", NULL);
+                       tmp = g_strdup_printf (_("SSL certificate for '%s' is not trusted. Do you wish to accept it?"), bhost);
+                       g_free (bhost);
+
+                       markup = tmp;
+               }
+
+               response = e_user_prompter_prompt_sync (prompter, "question", _("Certificate trust..."),
+                       markup, NULL, TRUE, button_captions, cancellable, NULL);
+
+               if (response == 1)
+                       response = 2;
+               else if (response == 2)
+                       response = 1;
+
+               g_slist_free (button_captions);
+               g_free (tmp);
+       } else {
+               response = e_user_prompter_extension_prompt_sync (prompter, "ETrustPrompt::trust-prompt", parameters, NULL, cancellable, error);
+       }
+
+       if (response == 0)
+               return E_TRUST_PROMPT_RESPONSE_REJECT;
+       if (response == 1)
+               return E_TRUST_PROMPT_RESPONSE_ACCEPT;
+       if (response == 2)
+               return E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY;
+       if (response == -1)
+               return E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY;
+
+       return E_TRUST_PROMPT_RESPONSE_UNKNOWN;
+}
+
+/**
+ * e_backend_trust_prompt:
+ * @backend: an #EBackend
+ * @parameters: an #ENamedParameters with values for the trust prompt
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Initiates a user trust prompt with given @parameters.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call e_backend_trust_prompt_finish() to get the result of the operation.
+ *
+ * Since: 3.8
+ **/
+void
+e_backend_trust_prompt (EBackend *backend,
+                       const ENamedParameters *parameters,
+                       GCancellable *cancellable,
+                       GAsyncReadyCallback callback,
+                       gpointer user_data)
+{
+       EUserPrompter *prompter;
+
+       g_return_if_fail (E_IS_BACKEND (backend));
+       g_return_if_fail (parameters != NULL);
+
+       prompter = e_backend_get_user_prompter (backend);
+       g_return_if_fail (prompter != NULL);
+
+       e_user_prompter_extension_prompt (prompter, "ETrustPrompt::trust-prompt", parameters, cancellable, callback, user_data);
+}
+
+/**
+ * e_backend_trust_prompt_finish:
+ * @backend: an #EBackend
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_backend_trust_prompt().  If
+ * an error occurred, the function will set @error and return %E_TRUST_PROMPT_RESPONSE_UNKNOWN.
+ *
+ * Returns: an #ETrustPromptResponse what user responded
+ *
+ * Note: The function can return also %E_TRUST_PROMPT_RESPONSE_UNKNOWN,
+ *    it's on error or if user closes the trust prompt dialog with other
+ *    than the offered buttons. Usual behaviour in such case is to treat
+ *    it as a temporary reject.
+ *
+ * Since: 3.8
+ **/
+ETrustPromptResponse
+e_backend_trust_prompt_finish (EBackend *backend,
+                              GAsyncResult *result,
+                              GError **error)
+{
+       EUserPrompter *prompter;
+       gint response;
+
+       g_return_val_if_fail (E_IS_BACKEND (backend), E_TRUST_PROMPT_RESPONSE_UNKNOWN);
+
+       prompter = e_backend_get_user_prompter (backend);
+       g_return_val_if_fail (prompter != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
+
+       response = e_user_prompter_extension_prompt_finish (prompter, result, NULL, error);
+
+       if (response == 0)
+               return E_TRUST_PROMPT_RESPONSE_REJECT;
+       if (response == 1)
+               return E_TRUST_PROMPT_RESPONSE_ACCEPT;
+       if (response == 2)
+               return E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY;
+       if (response == -1)
+               return E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY;
+
+       return E_TRUST_PROMPT_RESPONSE_UNKNOWN;
+}
index 30b697e..65b8e8b 100644 (file)
@@ -46,6 +46,9 @@
 
 G_BEGIN_DECLS
 
+/* forward declaration */
+struct _EUserPrompter;
+
 typedef struct _EBackend EBackend;
 typedef struct _EBackendClass EBackendClass;
 typedef struct _EBackendPrivate EBackendPrivate;
@@ -100,6 +103,22 @@ void               e_backend_authenticate          (EBackend *backend,
 gboolean       e_backend_authenticate_finish   (EBackend *backend,
                                                 GAsyncResult *result,
                                                 GError **error);
+struct _EUserPrompter *
+               e_backend_get_user_prompter     (EBackend *backend);
+ETrustPromptResponse
+               e_backend_trust_prompt_sync     (EBackend *backend,
+                                                const ENamedParameters *parameters,
+                                                GCancellable *cancellable,
+                                                GError **error);
+void           e_backend_trust_prompt          (EBackend *backend,
+                                                const ENamedParameters *parameters,
+                                                GCancellable *cancellable,
+                                                GAsyncReadyCallback callback,
+                                                gpointer user_data);
+ETrustPromptResponse
+               e_backend_trust_prompt_finish   (EBackend *backend,
+                                                GAsyncResult *result,
+                                                GError **error);
 
 G_END_DECLS
 
index e0ae6ba..0bbbf8f 100644 (file)
  * ]|
  **/
 
-#include "e-source-webdav.h"
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
 
 #include <libedataserver/e-data-server-util.h>
+#include <libedataserver/e-source-address-book.h>
 #include <libedataserver/e-source-authentication.h>
+#include <libedataserver/e-source-calendar.h>
+#include <libedataserver/e-source-registry.h>
 #include <libedataserver/e-source-security.h>
 
+#include "e-source-webdav.h"
+
 #define E_SOURCE_WEBDAV_GET_PRIVATE(obj) \
        (G_TYPE_INSTANCE_GET_PRIVATE \
        ((obj), E_TYPE_SOURCE_WEBDAV, ESourceWebdavPrivate))
@@ -60,9 +69,9 @@ struct _ESourceWebdavPrivate {
        gchar *email_address;
        gchar *resource_path;
        gchar *resource_query;
+       gchar *ssl_trust;
        gboolean avoid_ifmatch;
        gboolean calendar_auto_schedule;
-       gboolean ignore_invalid_cert;
        SoupURI *soup_uri;
 };
 
@@ -72,7 +81,7 @@ enum {
        PROP_CALENDAR_AUTO_SCHEDULE,
        PROP_DISPLAY_NAME,
        PROP_EMAIL_ADDRESS,
-       PROP_IGNORE_INVALID_CERT,
+       PROP_SSL_TRUST,
        PROP_RESOURCE_PATH,
        PROP_RESOURCE_QUERY,
        PROP_SOUP_URI
@@ -256,12 +265,6 @@ source_webdav_set_property (GObject *object,
                                g_value_get_string (value));
                        return;
 
-               case PROP_IGNORE_INVALID_CERT:
-                       e_source_webdav_set_ignore_invalid_cert (
-                               E_SOURCE_WEBDAV (object),
-                               g_value_get_boolean (value));
-                       return;
-
                case PROP_RESOURCE_PATH:
                        e_source_webdav_set_resource_path (
                                E_SOURCE_WEBDAV (object),
@@ -274,6 +277,12 @@ source_webdav_set_property (GObject *object,
                                g_value_get_string (value));
                        return;
 
+               case PROP_SSL_TRUST:
+                       e_source_webdav_set_ssl_trust (
+                               E_SOURCE_WEBDAV (object),
+                               g_value_get_string (value));
+                       return;
+
                case PROP_SOUP_URI:
                        e_source_webdav_set_soup_uri (
                                E_SOURCE_WEBDAV (object),
@@ -319,13 +328,6 @@ source_webdav_get_property (GObject *object,
                                E_SOURCE_WEBDAV (object)));
                        return;
 
-               case PROP_IGNORE_INVALID_CERT:
-                       g_value_set_boolean (
-                               value,
-                               e_source_webdav_get_ignore_invalid_cert (
-                               E_SOURCE_WEBDAV (object)));
-                       return;
-
                case PROP_RESOURCE_PATH:
                        g_value_take_string (
                                value,
@@ -340,6 +342,13 @@ source_webdav_get_property (GObject *object,
                                E_SOURCE_WEBDAV (object)));
                        return;
 
+               case PROP_SSL_TRUST:
+                       g_value_take_string (
+                               value,
+                               e_source_webdav_dup_ssl_trust (
+                               E_SOURCE_WEBDAV (object)));
+                       return;
+
                case PROP_SOUP_URI:
                        g_value_take_boxed (
                                value,
@@ -364,6 +373,7 @@ source_webdav_finalize (GObject *object)
        g_free (priv->email_address);
        g_free (priv->resource_path);
        g_free (priv->resource_query);
+       g_free (priv->ssl_trust);
 
        soup_uri_free (priv->soup_uri);
 
@@ -500,18 +510,6 @@ e_source_webdav_class_init (ESourceWebdavClass *class)
 
        g_object_class_install_property (
                object_class,
-               PROP_IGNORE_INVALID_CERT,
-               g_param_spec_boolean (
-                       "ignore-invalid-cert",
-                       "Ignore Invalid Cert",
-                       "Ignore invalid SSL certificates",
-                       FALSE,
-                       G_PARAM_READWRITE |
-                       G_PARAM_CONSTRUCT |
-                       E_SOURCE_PARAM_SETTING));
-
-       g_object_class_install_property (
-               object_class,
                PROP_RESOURCE_PATH,
                g_param_spec_string (
                        "resource-path",
@@ -536,6 +534,18 @@ e_source_webdav_class_init (ESourceWebdavClass *class)
 
        g_object_class_install_property (
                object_class,
+               PROP_SSL_TRUST,
+               g_param_spec_string (
+                       "ssl-trust",
+                       "SSL Trust",
+                       "SSL certificate trust setting, for invalid server certificates",
+                       NULL,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT |
+                       E_SOURCE_PARAM_SETTING));
+
+       g_object_class_install_property (
+               object_class,
                PROP_SOUP_URI,
                g_param_spec_boxed (
                        "soup-uri",
@@ -832,53 +842,6 @@ e_source_webdav_set_email_address (ESourceWebdav *extension,
 }
 
 /**
- * e_source_webdav_get_ignore_invalid_cert:
- * @extension: an #ESourceWebdav
- *
- * Returns %TRUE if invalid SSL certificates should be ignored.
- *
- * This option allows SSL certificates to be accepted even if they have
- * signed by an unrecognized Certificate Authority.
- *
- * Returns: whether invalid SSL certificates should be ignored
- *
- * Since: 3.6
- **/
-gboolean
-e_source_webdav_get_ignore_invalid_cert (ESourceWebdav *extension)
-{
-       g_return_val_if_fail (E_IS_SOURCE_WEBDAV (extension), FALSE);
-
-       return extension->priv->ignore_invalid_cert;
-}
-
-/**
- * e_source_webdav_set_ignore_invalid_cert:
- * @extension: an #ESourceWebdav
- * @ignore_invalid_cert: whether invalid SSL certificates should be ignored
- *
- * Sets whether invalid SSL certificates should be ignored.
- *
- * This option allows SSL certificates to be accepted even if they have
- * signed by an unrecognized Certificate Authority.
- *
- * Since: 3.6
- **/
-void
-e_source_webdav_set_ignore_invalid_cert (ESourceWebdav *extension,
-                                         gboolean ignore_invalid_cert)
-{
-       g_return_if_fail (E_IS_SOURCE_WEBDAV (extension));
-
-       if (extension->priv->ignore_invalid_cert == ignore_invalid_cert)
-               return;
-
-       extension->priv->ignore_invalid_cert = ignore_invalid_cert;
-
-       g_object_notify (G_OBJECT (extension), "ignore-invalid-cert");
-}
-
-/**
  * e_source_webdav_get_resource_path:
  * @extension: an #ESourceWebdav
  *
@@ -1057,6 +1020,93 @@ e_source_webdav_set_resource_query (ESourceWebdav *extension,
 }
 
 /**
+ * e_source_webdav_get_ssl_trust:
+ * @extension: an #ESourceWebdav
+ *
+ * Returns an SSL certificate trust for the @extension.
+ * The value encodes three parameters, divided by a pipe '|',
+ * the first is users preference, can be one of "reject", "accept",
+ * "temporary-reject" and "temporary-accept". The second is a host
+ * name for which the trust was set. Finally the last is a SHA1
+ * hash of the certificate. This is not meant to be changed by a caller,
+ * it is supposed to be manipulated with e_source_webdav_prepare_ssl_trust_prompt()
+ * and e_source_webdav_store_ssl_trust_prompt().
+ *
+ * Returns: an SSL certificate trust for the @extension
+ *
+ * Since: 3.8
+ **/
+const gchar *
+e_source_webdav_get_ssl_trust (ESourceWebdav *extension)
+{
+       g_return_val_if_fail (E_IS_SOURCE_WEBDAV (extension), NULL);
+
+       return extension->priv->ssl_trust;
+}
+
+/**
+ * e_source_webdav_dup_ssl_trust:
+ * @extension: an #ESourceWebdav
+ *
+ * Thread-safe variation of e_source_webdav_get_ssl_trust().
+ * Use this function when accessing @extension from multiple threads.
+ *
+ * The returned string should be freed with g_free() when no longer needed.
+ *
+ * Returns: the newly-allocated copy of #ESourceWebdav:ssl-trust
+ *
+ * Since: 3.8
+ **/
+gchar *
+e_source_webdav_dup_ssl_trust (ESourceWebdav *extension)
+{
+       const gchar *protected;
+       gchar *duplicate;
+
+       g_return_val_if_fail (E_IS_SOURCE_WEBDAV (extension), NULL);
+
+       g_mutex_lock (&extension->priv->property_lock);
+
+       protected = e_source_webdav_get_ssl_trust (extension);
+       duplicate = g_strdup (protected);
+
+       g_mutex_unlock (&extension->priv->property_lock);
+
+       return duplicate;
+}
+
+/**
+ * e_source_webdav_set_ssl_trust:
+ * @extension: an #ESourceWebdav
+ * @ssl_trust: (allow-none): the ssl_trust to store, or %NULL to unset
+ *
+ * Sets the SSL certificate trust. See e_source_webdav_get_ssl_trust()
+ * for more infomation about its content and how to use it.
+ *
+ * Since: 3.8
+ **/
+void
+e_source_webdav_set_ssl_trust (ESourceWebdav *extension,
+                              const gchar *ssl_trust)
+{
+       g_return_if_fail (E_IS_SOURCE_WEBDAV (extension));
+
+       g_mutex_lock (&extension->priv->property_lock);
+
+       if (g_strcmp0 (extension->priv->ssl_trust, ssl_trust) == 0) {
+               g_mutex_unlock (&extension->priv->property_lock);
+               return;
+       }
+
+       g_free (extension->priv->ssl_trust);
+       extension->priv->ssl_trust = g_strdup (ssl_trust);
+
+       g_mutex_unlock (&extension->priv->property_lock);
+
+       g_object_notify (G_OBJECT (extension), "ssl-trust");
+}
+
+/**
  * e_source_webdav_dup_soup_uri:
  * @extension: an #ESourceWebdav
  *
@@ -1123,3 +1173,402 @@ e_source_webdav_set_soup_uri (ESourceWebdav *extension,
        g_object_thaw_notify (G_OBJECT (extension));
 }
 
+static gboolean
+decode_ssl_trust (ESourceWebdav *extension,
+                 ETrustPromptResponse *response,
+                 gchar **host,
+                 gchar **hash)
+{
+       gchar *ssl_trust, **strv;
+
+       g_return_val_if_fail (E_IS_SOURCE_WEBDAV (extension), FALSE);
+
+       ssl_trust = e_source_webdav_dup_ssl_trust (extension);
+       if (!ssl_trust || !*ssl_trust) {
+               g_free (ssl_trust);
+               return FALSE;
+       }
+
+       strv = g_strsplit (ssl_trust, "|", 3);
+       if (!strv || !strv[0] || !strv[1] || !strv[2] || strv[3]) {
+               g_free (ssl_trust);
+               g_strfreev (strv);
+               return FALSE;
+       }
+
+       if (response) {
+               const gchar *resp = strv[0];
+
+               if (g_strcmp0 (resp, "reject") == 0)
+                       *response = E_TRUST_PROMPT_RESPONSE_REJECT;
+               else if (g_strcmp0 (resp, "accept") == 0)
+                       *response = E_TRUST_PROMPT_RESPONSE_ACCEPT;
+               else if (g_strcmp0 (resp, "temporary-reject") == 0)
+                       *response = E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY;
+               else if (g_strcmp0 (resp, "temporary-accept") == 0)
+                       *response = E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY;
+               else
+                       *response = E_TRUST_PROMPT_RESPONSE_UNKNOWN;
+       }
+
+       if (host)
+               *host = g_strdup (strv[1]);
+
+       if (hash)
+               *hash = g_strdup (strv[2]);
+
+       g_free (ssl_trust);
+       g_strfreev (strv);
+
+       return TRUE;
+}
+
+static gboolean
+encode_ssl_trust (ESourceWebdav *extension,
+                 ETrustPromptResponse response,
+                 const gchar *host,
+                 const gchar *hash)
+{
+       gchar *ssl_trust;
+       const gchar *resp;
+       gboolean changed;
+
+       g_return_val_if_fail (E_IS_SOURCE_WEBDAV (extension), FALSE);
+
+       if (response == E_TRUST_PROMPT_RESPONSE_REJECT)
+               resp = "reject";
+       else if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT)
+               resp = "accept";
+       else if (response == E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY)
+               resp = "temporary-reject";
+       else if (response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY)
+               resp = "temporary-accept";
+       else
+               resp = "temporary-reject";
+
+       if (host && *host && hash && *hash) {
+               ssl_trust = g_strconcat (resp, "|", host, "|", hash, NULL);
+       } else {
+               ssl_trust = NULL;
+       }
+
+       changed = g_strcmp0 (extension->priv->ssl_trust, ssl_trust) != 0;
+
+       if (changed)
+               e_source_webdav_set_ssl_trust (extension, ssl_trust);
+
+       g_free (ssl_trust);
+
+       return changed;
+}
+
+/**
+ * e_source_webdav_prepare_ssl_trust_prompt:
+ * @extension: an #ESourceWebdav
+ * @message: a #SoupMessage with #SOUP_STATUS_SSL_FAILED status code
+ * @registry: an #ESourceRegistry, to use for parent lookups
+ * @parameters: an #ENamedParameters to be populated
+ *
+ * Checks @messages<!-- -->'s certificate against currently stored trust
+ * response and either returns what to do immediately, or returns
+ * #E_TRUST_PROMPT_RESPONSE_UNKNOWN and populates @parameters with necessary
+ * values for a trust prompt.
+ *
+ * Returns: What to do with SSL connection, where #E_TRUST_PROMPT_RESPONSE_UNKNOWN
+ *   means 'ask a user, with populated parameters'.
+ *
+ * Note: The #E_TRUST_PROMPT_RESPONSE_REJECT is returned on any errors, like
+ *  the @message not being with the #SOUP_STATUS_SSL_FAILED status code,
+ *  no certificate being stored in the @message and so on.
+ *
+ * Since: 3.8
+ **/
+ETrustPromptResponse
+e_source_webdav_prepare_ssl_trust_prompt (ESourceWebdav *extension,
+                                         SoupMessage *message,
+                                         ESourceRegistry *registry,
+                                         ENamedParameters *parameters)
+{
+       ETrustPromptResponse response;
+       ESource *source;
+       GTlsCertificate *cert = NULL;
+       GTlsCertificateFlags cert_errors = 0;
+       GByteArray *bytes = NULL;
+       SoupURI *soup_uri;
+       const gchar *host;
+       gchar *base64, *old_host = NULL, *old_hash = NULL, *cert_errs_str, *markup = NULL;
+       gint issuer_count;
+
+       g_return_val_if_fail (E_IS_SOURCE_WEBDAV (extension), E_TRUST_PROMPT_RESPONSE_REJECT);
+       g_return_val_if_fail (SOUP_IS_MESSAGE (message), E_TRUST_PROMPT_RESPONSE_REJECT);
+       g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), E_TRUST_PROMPT_RESPONSE_REJECT);
+       g_return_val_if_fail (parameters != NULL, E_TRUST_PROMPT_RESPONSE_REJECT);
+
+       if (message->status_code != SOUP_STATUS_SSL_FAILED)
+               return E_TRUST_PROMPT_RESPONSE_REJECT;
+
+       if (!soup_message_get_https_status (message, &cert, &cert_errors) || !cert) {
+               /* before libsoup 2.41.3 the certificate was not set on failed requests,
+                  thus workaround this and do a simple prompt later
+               */
+               #ifdef SOUP_CHECK_VERSION
+               #if SOUP_CHECK_VERSION(2, 41, 3)
+               return E_TRUST_PROMPT_RESPONSE_REJECT;
+               #endif
+               #endif
+       }
+
+       soup_uri = soup_message_get_uri (message);
+       if (!soup_uri || !soup_uri_get_host (soup_uri))
+               return E_TRUST_PROMPT_RESPONSE_REJECT;
+
+       #ifdef SOUP_CHECK_VERSION
+       #if SOUP_CHECK_VERSION(2, 41, 3)
+       g_return_val_if_fail (cert != NULL, E_TRUST_PROMPT_RESPONSE_REJECT);
+       g_object_get (cert, "certificate", &bytes, NULL);
+       #else
+       bytes = g_byte_array_new ();
+       g_byte_array_append (bytes, (guint8 *) "1", 1);
+       #endif
+       #else
+       bytes = g_byte_array_new ();
+       g_byte_array_append (bytes, (guint8 *) "1", 1);
+       #endif
+
+       if (!bytes)
+               return E_TRUST_PROMPT_RESPONSE_REJECT;
+
+       host = soup_uri_get_host (soup_uri);
+
+       if (decode_ssl_trust (extension, &response, &old_host, &old_hash)) {
+               gchar *hash = g_compute_checksum_for_data (G_CHECKSUM_SHA1, bytes->data, bytes->len);
+
+               if (response != E_TRUST_PROMPT_RESPONSE_UNKNOWN &&
+                   g_strcmp0 (old_host, host) == 0 &&
+                   g_strcmp0 (old_hash, hash) == 0) {
+                       g_free (old_host);
+                       g_free (old_hash);
+                       g_free (hash);
+
+                       return response;
+               }
+
+               g_free (old_host);
+               g_free (old_hash);
+               g_free (hash);
+       }
+
+       source = e_source_extension_ref_source (E_SOURCE_EXTENSION (extension));
+       if (source) {
+               gchar *bhost = g_strconcat ("<b>", host, "</b>", NULL);
+               gchar *bname = NULL;
+
+               if (e_source_get_parent (source)) {
+                       ESource *parent = NULL;
+
+                       parent = e_source_registry_ref_source (registry, e_source_get_parent (source));
+
+                       if (parent) {
+                               bname = g_strconcat ("<b>", e_source_get_display_name (parent), ": ",
+                                       e_source_get_display_name (source), "</b>", NULL);
+                               g_object_unref (parent);
+                       }
+               }
+
+               if (!bname)
+                       bname = g_strconcat ("<b>", e_source_get_display_name (source), "</b>", NULL);
+
+               if (e_source_has_extension (source, E_SOURCE_EXTENSION_ADDRESS_BOOK)) {
+                       /* Translators: The first %s is replaced with a host name, like "www.example.com";
+                          the second %s is replaced with actual source name, like "On The Web: My Work"
+                       */
+                       markup = g_strdup_printf (_("SSL certificate for host '%s', used by address book '%s', is not trusted. Do you wish to accept it?"),
+                               bhost, bname);
+               } else if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR)) {
+                       /* Translators: The first %s is replaced with a host name, like "www.example.com";
+                          the second %s is replaced with actual source name, like "On The Web: My Work"
+                       */
+                       markup = g_strdup_printf (_("SSL certificate for host '%s', used by calendar '%s', is not trusted. Do you wish to accept it?"),
+                               bhost, bname);
+               } else if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST)) {
+                       /* Translators: The first %s is replaced with a host name, like "www.example.com";
+                          the second %s is replaced with actual source name, like "On The Web: My Work"
+                       */
+                       markup = g_strdup_printf (_("SSL certificate for host '%s', used by memo list '%s', is not trusted. Do you wish to accept it?"),
+                               bhost, bname);
+               } else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST)) {
+                       /* Translators: The first %s is replaced with a host name, like "www.example.com";
+                          the second %s is replaced with actual source name, like "On The Web: My Work"
+                       */
+                       markup = g_strdup_printf (_("SSL certificate for host '%s', used by task list '%s', is not trusted. Do you wish to accept it?"),
+                               bhost, bname);
+               }
+
+               g_object_unref (source);
+               g_free (bname);
+               g_free (bhost);
+       }
+
+       base64 = g_base64_encode (bytes->data, bytes->len);
+       cert_errs_str = g_strdup_printf ("%x", cert_errors);
+
+       e_named_parameters_set (parameters, "host", host);
+       e_named_parameters_set (parameters, "markup", markup);
+       e_named_parameters_set (parameters, "certificate", base64);
+       e_named_parameters_set (parameters, "certificate-errors", cert_errs_str);
+
+       g_byte_array_unref (bytes);
+       g_free (cert_errs_str);
+       g_free (markup);
+
+       issuer_count = 0;
+       while (cert) {
+               GTlsCertificate *issuer = NULL;
+               g_object_get (cert, "issuer", &issuer, NULL);
+
+               cert = issuer;
+
+               if (cert) {
+                       bytes = NULL;
+                       g_object_get (cert, "certificate", &bytes, NULL);
+
+                       if (bytes) {
+                               base64 = g_base64_encode (bytes->data, bytes->len);
+                               if (issuer_count == 0) {
+                                       e_named_parameters_set (parameters, "issuer", base64);
+                               } else {
+                                       gchar *name;
+
+                                       name = g_strdup_printf ("issuer-%d", issuer_count);
+                                       e_named_parameters_set (parameters, name, base64);
+                                       g_free (name);
+                               }
+
+                               g_free (base64);
+                               g_byte_array_unref (bytes);
+                       } else {
+                               break;
+                       }
+               }
+
+               issuer_count++;
+       }
+
+       return E_TRUST_PROMPT_RESPONSE_UNKNOWN;
+}
+
+static void
+webdav_extension_changes_written_cb (GObject *source_object,
+                                    GAsyncResult *result,
+                                    gpointer user_data)
+{
+       GError *error = NULL;
+
+       e_source_write_finish (E_SOURCE (source_object), result, &error);
+
+       if (error) {
+               g_message ("%s: Failed with error: %s", G_STRFUNC, error->message);
+               g_clear_error (&error);
+       }
+}
+
+/**
+ * e_source_webdav_store_ssl_trust_prompt:
+ * @extension: an #ESourceWebdav
+ * @message: a #SoupMessage with #SOUP_STATUS_SSL_FAILED status code
+ * @response: user's response from a trust prompt
+ *
+ * Stores user's response from a trust prompt, thus it is re-used the next
+ * time it'll be needed. An #E_TRUST_PROMPT_RESPONSE_UNKNOWN is treated as
+ * a temporary reject, which means the user will be asked again.
+ *
+ * Since: 3.8
+ **/
+void
+e_source_webdav_store_ssl_trust_prompt (ESourceWebdav *extension,
+                                       SoupMessage *message,
+                                       ETrustPromptResponse response)
+{
+       GTlsCertificate *cert = NULL;
+       GByteArray *bytes = NULL;
+       SoupURI *soup_uri;
+       const gchar *host;
+       gchar *hash;
+       gboolean changed;
+
+       g_return_if_fail (E_IS_SOURCE_WEBDAV (extension));
+       g_return_if_fail (SOUP_IS_MESSAGE (message));
+
+       if (message->status_code != SOUP_STATUS_SSL_FAILED)
+               return;
+
+       if (!soup_message_get_https_status (message, &cert, NULL) || !cert) {
+               /* before libsoup 2.41.3 the certificate was not set on failed requests,
+                  thus workaround this and store only simple value
+               */
+               #ifdef SOUP_CHECK_VERSION
+               #if SOUP_CHECK_VERSION(2, 41, 3)
+               return;
+               #endif
+               #endif
+       }
+
+       soup_uri = soup_message_get_uri (message);
+       if (!soup_uri || !soup_uri_get_host (soup_uri))
+               return;
+
+       #ifdef SOUP_CHECK_VERSION
+       #if SOUP_CHECK_VERSION(2, 41, 3)
+       g_return_if_fail (cert != NULL);
+       g_object_get (cert, "certificate", &bytes, NULL);
+       #else
+       bytes = g_byte_array_new ();
+       g_byte_array_append (bytes, (guint8 *) "1", 1);
+       #endif
+       #else
+       bytes = g_byte_array_new ();
+       g_byte_array_append (bytes, (guint8 *) "1", 1);
+       #endif
+
+       if (!bytes)
+               return;
+
+       host = soup_uri_get_host (soup_uri);
+       hash = g_compute_checksum_for_data (G_CHECKSUM_SHA1, bytes->data, bytes->len);
+
+       changed = encode_ssl_trust (extension, response, host, hash);
+
+       g_byte_array_unref (bytes);
+       g_free (hash);
+
+       if (changed) {
+               ESource *source;
+
+               source = e_source_extension_ref_source (E_SOURCE_EXTENSION (extension));
+               e_source_write (source, NULL, webdav_extension_changes_written_cb, NULL);
+               g_object_unref (source);
+       }
+}
+
+/**
+ * e_source_webdav_unset_temporary_ssl_trust:
+ * @extension: an #ESourceWebdav
+ *
+ * Unsets temporary trust set on this @extension, but keeps
+ * it as is for other values.
+ *
+ * Since: 3.8
+ **/
+void
+e_source_webdav_unset_temporary_ssl_trust (ESourceWebdav *extension)
+{
+       ETrustPromptResponse response = E_TRUST_PROMPT_RESPONSE_UNKNOWN;
+
+       g_return_if_fail (E_IS_SOURCE_WEBDAV (extension));
+
+       if (!decode_ssl_trust (extension, &response, NULL, NULL) ||
+           response == E_TRUST_PROMPT_RESPONSE_UNKNOWN ||
+           response == E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY ||
+           response == E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY)
+               e_source_webdav_set_ssl_trust (extension, NULL);
+}
index 797d238..7209160 100644 (file)
 
 G_BEGIN_DECLS
 
+/* forward declaration */
+struct _ENamedParameters;
+struct _ESourceRegistry;
+
 typedef struct _ESourceWebdav ESourceWebdav;
 typedef struct _ESourceWebdavClass ESourceWebdavClass;
 typedef struct _ESourceWebdavPrivate ESourceWebdavPrivate;
@@ -78,6 +82,14 @@ struct _ESourceWebdavClass {
        ESourceExtensionClass parent_class;
 };
 
+typedef enum {
+       E_TRUST_PROMPT_RESPONSE_UNKNOWN                 = -1,
+       E_TRUST_PROMPT_RESPONSE_REJECT                  =  0,
+       E_TRUST_PROMPT_RESPONSE_ACCEPT                  =  1,
+       E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY      =  2,
+       E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY      =  3
+} ETrustPromptResponse;
+
 GType          e_source_webdav_get_type        (void) G_GNUC_CONST;
 gboolean       e_source_webdav_get_avoid_ifmatch
                                                (ESourceWebdav *extension);
@@ -103,11 +115,6 @@ gchar *            e_source_webdav_dup_email_address
 void           e_source_webdav_set_email_address
                                                (ESourceWebdav *extension,
                                                 const gchar *email_address);
-gboolean       e_source_webdav_get_ignore_invalid_cert
-                                               (ESourceWebdav *extension);
-void           e_source_webdav_set_ignore_invalid_cert
-                                               (ESourceWebdav *extension,
-                                                gboolean ignore_invalid_cert);
 const gchar *  e_source_webdav_get_resource_path
                                                (ESourceWebdav *extension);
 gchar *                e_source_webdav_dup_resource_path
@@ -122,9 +129,25 @@ gchar *            e_source_webdav_dup_resource_query
 void           e_source_webdav_set_resource_query
                                                (ESourceWebdav *extension,
                                                 const gchar *resource_query);
+const gchar *  e_source_webdav_get_ssl_trust   (ESourceWebdav *extension);
+gchar *                e_source_webdav_dup_ssl_trust   (ESourceWebdav *extension);
+void           e_source_webdav_set_ssl_trust   (ESourceWebdav *extension,
+                                                const gchar *ssl_trust);
 SoupURI *      e_source_webdav_dup_soup_uri    (ESourceWebdav *extension);
 void           e_source_webdav_set_soup_uri    (ESourceWebdav *extension,
                                                 SoupURI *soup_uri);
+ETrustPromptResponse
+               e_source_webdav_prepare_ssl_trust_prompt
+                                               (ESourceWebdav *extension,
+                                                SoupMessage *message,
+                                                struct _ESourceRegistry *registry,
+                                                struct _ENamedParameters *parameters);
+void           e_source_webdav_store_ssl_trust_prompt
+                                               (ESourceWebdav *extension,
+                                                SoupMessage *message,
+                                                ETrustPromptResponse response);
+void           e_source_webdav_unset_temporary_ssl_trust
+                                               (ESourceWebdav *extension);
 
 G_END_DECLS
 
index d8db7d8..7e2f363 100644 (file)
@@ -140,6 +140,7 @@ e_module_unload (GTypeModule *type_module)
 /* ETrustPrompt::trust-prompt
    The dialog expects these parameters:
       "host" - host from which the certificate is received
+      "markup" - markup for the trust prompt, if not set, then "SSL certificate for '<b>host</b>' is not trusted. Do you wish to accept it?" is used
       "certificate" - a base64-encoded DER certificate, for which ask on trust
       "certificate-errors" - a hexa-decimal integer (as string) corresponding to GTlsCertificateFlags
 
@@ -287,7 +288,7 @@ trust_prompt_show_trust_prompt (EUserPrompterServerExtension *extension,
                                gint prompt_id,
                                const ENamedParameters *parameters)
 {
-       const gchar *host, *base64_cert, *cert_errs_str;
+       const gchar *host, *markup, *base64_cert, *cert_errs_str;
        gchar *fingerprint, *reason;
        gint64 cert_errs;
        CERTCertDBHandle *certdb;
@@ -301,6 +302,7 @@ trust_prompt_show_trust_prompt (EUserPrompterServerExtension *extension,
        g_return_val_if_fail (parameters != NULL, FALSE);
 
        host = e_named_parameters_get (parameters, "host");
+       markup = e_named_parameters_get (parameters, "markup");
        base64_cert = e_named_parameters_get (parameters, "certificate");
        cert_errs_str = e_named_parameters_get (parameters, "certificate-errors");
 
@@ -323,7 +325,7 @@ trust_prompt_show_trust_prompt (EUserPrompterServerExtension *extension,
        reason = cert_errors_to_reason (cert_errs);
        fingerprint = cert_fingerprint (cert);
 
-       success = trust_prompt_show (extension, prompt_id, host, cert, fingerprint, reason, issuers);
+       success = trust_prompt_show (extension, prompt_id, host, markup, cert, fingerprint, reason, issuers);
 
        trust_prompt_free_certificate (cert);
        g_slist_free_full (issuers, trust_prompt_free_certificate);
index 2c94f11..d05194d 100644 (file)
@@ -136,6 +136,7 @@ gboolean
 trust_prompt_show (EUserPrompterServerExtension *extension,
                   gint prompt_id,
                   const gchar *host,
+                  const gchar *markup,
                   const CERTCertificate *pcert,
                   const gchar *cert_fingerprint,
                   const gchar *reason,
@@ -143,7 +144,7 @@ trust_prompt_show (EUserPrompterServerExtension *extension,
 {
        GtkWidget *dialog, *widget;
        GtkGrid *grid;
-       gchar *tmp, *info, *issuer, *subject;
+       gchar *tmp, *issuer, *subject, *head;
        GSList *issuers, *iter;
        CERTCertificate *cert;
        gint row = 0;
@@ -189,13 +190,23 @@ trust_prompt_show (EUserPrompterServerExtension *extension,
                NULL);
        gtk_grid_attach (grid, widget, 0, row, 1, 3);
 
-       info = g_strconcat ("<b>", host, "</b>", NULL);
-       tmp = g_strdup_printf (_("SSL Certificate for '%s' is not trusted. Do you wish to accept it?\n\n"
-                                   "Detailed information about the certificate:"), info);
-       g_free (info);
+       tmp = NULL;
+       if (!markup || !*markup) {
+               gchar *bhost;
+
+               bhost = g_strconcat ("<b>", host, "</b>", NULL);
+               tmp = g_strdup_printf (_("SSL certificate for '%s' is not trusted. Do you wish to accept it?"), bhost);
+               g_free (bhost);
+
+               markup = tmp;
+       }
+
+       head = g_strdup_printf ("%s\n\n%s", markup, _("Detailed information about the certificate:"));
+
        widget = gtk_label_new (NULL);
-       gtk_label_set_markup (GTK_LABEL (widget), tmp);
+       gtk_label_set_markup (GTK_LABEL (widget), head);
        gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+       g_free (head);
        g_free (tmp);
 
        gtk_grid_attach (grid, widget, 1, row, 2, 1);
index 9966ad6..cbcae58 100644 (file)
@@ -36,6 +36,7 @@ gboolean
 trust_prompt_show (EUserPrompterServerExtension *extension,
                   gint prompt_id,
                   const gchar *host,
+                  const gchar *markup,
                   const CERTCertificate *pcert,
                   const gchar *cert_fingerprint,
                   const gchar *reason,
index 938c944..2d5a2c4 100644 (file)
@@ -201,6 +201,7 @@ libedataserver/e-source-authenticator.c
 libedataserver/e-source.c
 libedataserver/e-source-mail-signature.c
 libedataserver/e-source-registry.c
+libedataserver/e-source-webdav.c
 libedataserver/e-time-utils.c
 libedataserver/org.gnome.evolution.shell.network-config.gschema.xml.in
 modules/gnome-online-accounts/goaewsclient.c
index c493410..257a73b 100644 (file)
@@ -80,10 +80,10 @@ prompt_user_show (EUserPrompterServer *server,
                        ntype = GTK_MESSAGE_ERROR;
        }
 
-       if (use_markup)
-               message = gtk_message_dialog_new_with_markup (NULL, 0, ntype, GTK_BUTTONS_NONE,
-                       "%s", primary_text ? primary_text : "");
-       else
+       if (use_markup) {
+               message = gtk_message_dialog_new_with_markup (NULL, 0, ntype, GTK_BUTTONS_NONE, "%s", "");
+               gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (message), primary_text ? primary_text : "");
+       else
                message = gtk_message_dialog_new (NULL, 0, ntype, GTK_BUTTONS_NONE,
                        "%s", primary_text ? primary_text : "");