/* * e-backend.c * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ /** * SECTION: e-backend * @include: libebackend/libebackend.h * @short_description: An abstract base class for backends * * An #EBackend is paired with an #ESource to facilitate performing * actions on the local or remote resource described by the #ESource. * * In other words, whereas a certain backend type knows how to talk to a * certain type of server or data store, the #ESource fills in configuration * details such as host name, user name, resource path, etc. * * All #EBackend instances are created by an #EBackendFactory. **/ #include #include #include #include #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)) #define G_IS_IO_ERROR(error, code) \ (g_error_matches ((error), G_IO_ERROR, (code))) #define G_IS_RESOLVER_ERROR(error, code) \ (g_error_matches ((error), G_RESOLVER_ERROR, (code))) struct _EBackendPrivate { GMutex property_lock; ESource *source; EUserPrompter *prompter; GMainContext *main_context; GSocketConnectable *connectable; gboolean online; GNetworkMonitor *network_monitor; gulong network_changed_handler_id; GSource *update_online_state; GMutex update_online_state_lock; GMutex network_monitor_cancellable_lock; GCancellable *network_monitor_cancellable; GMutex authenticate_cancellable_lock; GCancellable *authenticate_cancellable; }; enum { PROP_0, PROP_CONNECTABLE, PROP_MAIN_CONTEXT, PROP_ONLINE, PROP_SOURCE, PROP_USER_PROMPTER }; G_DEFINE_ABSTRACT_TYPE (EBackend, e_backend, G_TYPE_OBJECT) static void backend_network_monitor_can_reach_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { EBackend *backend = E_BACKEND (user_data); gboolean host_is_reachable; GError *error = NULL; host_is_reachable = g_network_monitor_can_reach_finish ( G_NETWORK_MONITOR (source_object), result, &error); /* Sanity check. */ g_return_if_fail ( (host_is_reachable && (error == NULL)) || (!host_is_reachable && (error != NULL))); if (G_IS_IO_ERROR (error, G_IO_ERROR_CANCELLED) || host_is_reachable == e_backend_get_online (backend)) { g_clear_error (&error); g_object_unref (backend); return; } g_clear_error (&error); e_backend_set_online (backend, host_is_reachable); if (!host_is_reachable) { ESource *source; source = e_backend_get_source (backend); e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED); } g_object_unref (backend); } static gboolean backend_update_online_state_timeout_cb (gpointer user_data) { EBackend *backend; GSocketConnectable *connectable; GCancellable *cancellable; GSource *current_source; current_source = g_main_current_source (); if (current_source && g_source_is_destroyed (current_source)) return FALSE; backend = E_BACKEND (user_data); connectable = e_backend_ref_connectable (backend); g_mutex_lock (&backend->priv->update_online_state_lock); g_source_unref (backend->priv->update_online_state); backend->priv->update_online_state = NULL; g_mutex_unlock (&backend->priv->update_online_state_lock); g_mutex_lock (&backend->priv->network_monitor_cancellable_lock); cancellable = backend->priv->network_monitor_cancellable; backend->priv->network_monitor_cancellable = NULL; if (cancellable != NULL) { g_cancellable_cancel (cancellable); g_object_unref (cancellable); cancellable = NULL; } if (!connectable) { gchar *host = NULL; guint16 port = 0; if (e_backend_get_destination_address (backend, &host, &port) && host) connectable = g_network_address_new (host, port); g_free (host); } if (connectable == NULL) { backend->priv->network_monitor_cancellable = cancellable; g_mutex_unlock (&backend->priv->network_monitor_cancellable_lock); e_backend_set_online (backend, TRUE); } else { cancellable = g_cancellable_new (); g_network_monitor_can_reach_async ( backend->priv->network_monitor, connectable, cancellable, backend_network_monitor_can_reach_cb, g_object_ref (backend)); backend->priv->network_monitor_cancellable = cancellable; g_mutex_unlock (&backend->priv->network_monitor_cancellable_lock); } if (connectable != NULL) g_object_unref (connectable); return FALSE; } static void backend_update_online_state (EBackend *backend) { GMainContext *main_context; GSource *timeout_source; g_mutex_lock (&backend->priv->update_online_state_lock); /* Reference the backend before destroying any already scheduled GSource, in case the backend's last reference is held by that GSource. */ g_object_ref (backend); if (backend->priv->update_online_state) { g_source_destroy (backend->priv->update_online_state); g_source_unref (backend->priv->update_online_state); backend->priv->update_online_state = NULL; } main_context = e_backend_ref_main_context (backend); timeout_source = g_timeout_source_new_seconds (5); g_source_set_priority (timeout_source, G_PRIORITY_LOW); g_source_set_callback ( timeout_source, backend_update_online_state_timeout_cb, backend, (GDestroyNotify) g_object_unref); g_source_attach (timeout_source, main_context); backend->priv->update_online_state = g_source_ref (timeout_source); g_source_unref (timeout_source); g_main_context_unref (main_context); g_mutex_unlock (&backend->priv->update_online_state_lock); } static void backend_network_changed_cb (GNetworkMonitor *network_monitor, gboolean network_available, EBackend *backend) { backend_update_online_state (backend); } static ESourceAuthenticationResult e_backend_authenticate_sync (EBackend *backend, const ENamedParameters *credentials, gchar **out_certificate_pem, GTlsCertificateFlags *out_certificate_errors, GCancellable *cancellable, GError **error) { EBackendClass *class; g_return_val_if_fail (E_IS_BACKEND (backend), E_SOURCE_AUTHENTICATION_ERROR); g_return_val_if_fail (credentials != NULL, E_SOURCE_AUTHENTICATION_ERROR); class = E_BACKEND_GET_CLASS (backend); g_return_val_if_fail (class->authenticate_sync != NULL, E_SOURCE_AUTHENTICATION_ERROR); return class->authenticate_sync (backend, credentials, out_certificate_pem, out_certificate_errors, cancellable, error); } typedef struct _AuthenticateThreadData { EBackend *backend; GCancellable *cancellable; ENamedParameters *credentials; } AuthenticateThreadData; static AuthenticateThreadData * authenticate_thread_data_new (EBackend *backend, GCancellable *cancellable, const ENamedParameters *credentials) { AuthenticateThreadData *data; data = g_new0 (AuthenticateThreadData, 1); data->backend = g_object_ref (backend); data->cancellable = g_object_ref (cancellable); data->credentials = credentials ? e_named_parameters_new_clone (credentials) : e_named_parameters_new (); return data; } static void authenticate_thread_data_free (AuthenticateThreadData *data) { if (data) { g_clear_object (&data->backend); g_clear_object (&data->cancellable); e_named_parameters_free (data->credentials); g_free (data); } } static gpointer backend_source_authenticate_thread (gpointer user_data) { ESourceAuthenticationResult auth_result; AuthenticateThreadData *thread_data = user_data; gchar *certificate_pem = NULL; GTlsCertificateFlags certificate_errors = 0; GError *local_error = NULL; ESource *source; g_return_val_if_fail (thread_data != NULL, NULL); source = e_backend_get_source (thread_data->backend); e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTING); /* Update the SSL trust transparently. */ if (e_named_parameters_get (thread_data->credentials, E_SOURCE_CREDENTIAL_SSL_TRUST) && e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND)) { ESourceWebdav *webdav_extension; webdav_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND); e_source_webdav_set_ssl_trust (webdav_extension, e_named_parameters_get (thread_data->credentials, E_SOURCE_CREDENTIAL_SSL_TRUST)); } auth_result = e_backend_authenticate_sync (thread_data->backend, thread_data->credentials, &certificate_pem, &certificate_errors, thread_data->cancellable, &local_error); if (!g_cancellable_is_cancelled (thread_data->cancellable)) { ESourceCredentialsReason reason = E_SOURCE_CREDENTIALS_REASON_ERROR; switch (auth_result) { case E_SOURCE_AUTHENTICATION_ERROR: reason = E_SOURCE_CREDENTIALS_REASON_ERROR; break; case E_SOURCE_AUTHENTICATION_ERROR_SSL_FAILED: reason = E_SOURCE_CREDENTIALS_REASON_SSL_FAILED; break; case E_SOURCE_AUTHENTICATION_ACCEPTED: e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED); break; case E_SOURCE_AUTHENTICATION_REQUIRED: reason = E_SOURCE_CREDENTIALS_REASON_REQUIRED; break; case E_SOURCE_AUTHENTICATION_REJECTED: reason = E_SOURCE_CREDENTIALS_REASON_REJECTED; break; } if (auth_result == E_SOURCE_AUTHENTICATION_ACCEPTED) { const gchar *username = e_named_parameters_get (thread_data->credentials, E_SOURCE_CREDENTIAL_USERNAME); gboolean call_write = FALSE; if (username && *username && e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { ESourceAuthentication *extension_authentication = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); if (g_strcmp0 (username, e_source_authentication_get_user (extension_authentication)) != 0) { e_source_authentication_set_user (extension_authentication, username); call_write = TRUE; } } if (username && *username && e_source_has_extension (source, E_SOURCE_EXTENSION_COLLECTION)) { ESourceCollection *extension_collection = e_source_get_extension (source, E_SOURCE_EXTENSION_COLLECTION); if (g_strcmp0 (username, e_source_collection_get_identity (extension_collection)) != 0) { e_source_collection_set_identity (extension_collection, username); call_write = TRUE; } } if (call_write) { GError *local_error2 = NULL; if (!e_source_write_sync (source, thread_data->cancellable, &local_error2)) { g_warning ("%s: Failed to store changed user name: %s", G_STRFUNC, local_error2 ? local_error2->message : "Unknown error"); } g_clear_error (&local_error2); } } else { GError *local_error2 = NULL; e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED); if (!e_source_invoke_credentials_required_sync (source, reason, certificate_pem, certificate_errors, local_error, thread_data->cancellable, &local_error2)) { g_warning ("%s: Failed to invoke credentials required: %s", G_STRFUNC, local_error2 ? local_error2->message : "Unknown error"); } g_clear_error (&local_error2); } } else { e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED); } g_free (certificate_pem); g_clear_error (&local_error); authenticate_thread_data_free (thread_data); return NULL; } static void backend_source_authenticate_cb (ESource *source, const ENamedParameters *credentials, EBackend *backend) { g_return_if_fail (E_IS_BACKEND (backend)); g_return_if_fail (credentials != NULL); e_backend_schedule_authenticate (backend, credentials); } static void backend_source_unset_last_credentials_required_arguments_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GError *local_error = NULL; g_return_if_fail (E_IS_SOURCE (source_object)); e_source_unset_last_credentials_required_arguments_finish (E_SOURCE (source_object), result, &local_error); if (local_error) g_debug ("%s: Call failed: %s", G_STRFUNC, local_error->message); g_clear_error (&local_error); } static void backend_set_source (EBackend *backend, ESource *source) { g_return_if_fail (E_IS_SOURCE (source)); g_return_if_fail (backend->priv->source == NULL); backend->priv->source = g_object_ref (source); g_signal_connect (backend->priv->source, "authenticate", G_CALLBACK (backend_source_authenticate_cb), backend); } static void backend_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CONNECTABLE: e_backend_set_connectable ( E_BACKEND (object), g_value_get_object (value)); return; case PROP_ONLINE: e_backend_set_online ( E_BACKEND (object), g_value_get_boolean (value)); return; case PROP_SOURCE: backend_set_source ( E_BACKEND (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void backend_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_CONNECTABLE: g_value_take_object ( value, e_backend_ref_connectable ( E_BACKEND (object))); return; case PROP_MAIN_CONTEXT: g_value_take_boxed ( value, e_backend_ref_main_context ( E_BACKEND (object))); return; case PROP_ONLINE: g_value_set_boolean ( value, e_backend_get_online ( E_BACKEND (object))); return; case PROP_SOURCE: g_value_set_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); } static void backend_dispose (GObject *object) { EBackendPrivate *priv; priv = E_BACKEND_GET_PRIVATE (object); if (priv->network_changed_handler_id > 0) { g_signal_handler_disconnect ( priv->network_monitor, priv->network_changed_handler_id); priv->network_changed_handler_id = 0; } if (priv->main_context != NULL) { g_main_context_unref (priv->main_context); priv->main_context = NULL; } if (priv->update_online_state != NULL) { g_source_destroy (priv->update_online_state); g_source_unref (priv->update_online_state); priv->update_online_state = NULL; } if (priv->source) { g_signal_handlers_disconnect_by_func (priv->source, backend_source_authenticate_cb, object); e_source_set_connection_status (priv->source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED); e_source_unset_last_credentials_required_arguments (priv->source, NULL, backend_source_unset_last_credentials_required_arguments_cb, NULL); } g_mutex_lock (&priv->authenticate_cancellable_lock); if (priv->authenticate_cancellable) { g_cancellable_cancel (priv->authenticate_cancellable); g_clear_object (&priv->authenticate_cancellable); } g_mutex_unlock (&priv->authenticate_cancellable_lock); g_clear_object (&priv->source); g_clear_object (&priv->prompter); g_clear_object (&priv->connectable); g_clear_object (&priv->network_monitor); g_clear_object (&priv->network_monitor_cancellable); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_backend_parent_class)->dispose (object); } static void backend_finalize (GObject *object) { EBackendPrivate *priv; priv = E_BACKEND_GET_PRIVATE (object); g_mutex_clear (&priv->property_lock); g_mutex_clear (&priv->update_online_state_lock); g_mutex_clear (&priv->network_monitor_cancellable_lock); g_mutex_clear (&priv->authenticate_cancellable_lock); /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (e_backend_parent_class)->finalize (object); } static void backend_constructed (GObject *object) { EBackend *backend; ESource *source; const gchar *extension_name; backend = E_BACKEND (object); /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_backend_parent_class)->constructed (object); /* Get an initial GSocketConnectable from the data * source's [Authentication] extension, if present. */ source = e_backend_get_source (backend); extension_name = E_SOURCE_EXTENSION_AUTHENTICATION; if (e_source_has_extension (source, extension_name)) { ESourceAuthentication *extension; extension = e_source_get_extension (source, extension_name); backend->priv->connectable = e_source_authentication_ref_connectable (extension); backend_update_online_state (backend); } } static ESourceAuthenticationResult backend_authenticate_sync (EBackend *backend, const ENamedParameters *credentials, gchar **out_certificate_pem, GTlsCertificateFlags *out_certificate_errors, GCancellable *cancellable, GError **error) { /* The default implementation just reports success, it's for backends which do not use (nor define) authentication routines, because they use different methods to get to the credentials. */ return E_SOURCE_AUTHENTICATION_ACCEPTED; } static gboolean backend_get_destination_address (EBackend *backend, gchar **host, guint16 *port) { GSocketConnectable *connectable; GNetworkAddress *address; g_return_val_if_fail (E_IS_BACKEND (backend), FALSE); g_return_val_if_fail (host != NULL, FALSE); g_return_val_if_fail (port != NULL, FALSE); connectable = e_backend_ref_connectable (backend); if (!connectable) return FALSE; if (!G_IS_NETWORK_ADDRESS (connectable)) { g_object_unref (connectable); return FALSE; } address = G_NETWORK_ADDRESS (connectable); *host = g_strdup (g_network_address_get_hostname (address)); *port = g_network_address_get_port (address); g_object_unref (connectable); return *host != NULL; } static void backend_prepare_shutdown (EBackend *backend) { } static void e_backend_class_init (EBackendClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (EBackendPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = backend_set_property; object_class->get_property = backend_get_property; object_class->dispose = backend_dispose; object_class->finalize = backend_finalize; object_class->constructed = backend_constructed; class->authenticate_sync = backend_authenticate_sync; class->get_destination_address = backend_get_destination_address; class->prepare_shutdown = backend_prepare_shutdown; g_object_class_install_property ( object_class, PROP_CONNECTABLE, g_param_spec_object ( "connectable", "Connectable", "Socket endpoint of a network service", G_TYPE_SOCKET_CONNECTABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_MAIN_CONTEXT, g_param_spec_boxed ( "main-context", "Main Context", "The main loop context on " "which to attach event sources", G_TYPE_MAIN_CONTEXT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_ONLINE, g_param_spec_boolean ( "online", "Online", "Whether the backend is online", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_SOURCE, g_param_spec_object ( "source", "Source", "The data source being acted upon", E_TYPE_SOURCE, 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) { GNetworkMonitor *network_monitor; gulong handler_id; backend->priv = E_BACKEND_GET_PRIVATE (backend); backend->priv->prompter = e_user_prompter_new (); backend->priv->main_context = g_main_context_ref_thread_default (); g_mutex_init (&backend->priv->property_lock); g_mutex_init (&backend->priv->update_online_state_lock); g_mutex_init (&backend->priv->network_monitor_cancellable_lock); g_mutex_init (&backend->priv->authenticate_cancellable_lock); backend->priv->authenticate_cancellable = NULL; /* Configure network monitoring. */ network_monitor = g_network_monitor_get_default (); backend->priv->network_monitor = g_object_ref (network_monitor); backend->priv->online = g_network_monitor_get_network_available (network_monitor); handler_id = g_signal_connect ( backend->priv->network_monitor, "network-changed", G_CALLBACK (backend_network_changed_cb), backend); backend->priv->network_changed_handler_id = handler_id; } /** * e_backend_get_online: * @backend: an #EBackend * * Returns the online state of @backend: %TRUE if @backend is online, * %FALSE if offline. * * If the #EBackend:connectable property is non-%NULL, the @backend will * automatically determine whether the network service should be reachable, * and hence whether the @backend is #EBackend:online. But subclasses may * override the online state if, for example, a connection attempt fails. * * Returns: the online state * * Since: 3.4 **/ gboolean e_backend_get_online (EBackend *backend) { g_return_val_if_fail (E_IS_BACKEND (backend), FALSE); return backend->priv->online; } /** * e_backend_set_online: * @backend: an #EBackend * @online: the online state * * Sets the online state of @backend: %TRUE if @backend is online, * @FALSE if offline. * * If the #EBackend:connectable property is non-%NULL, the @backend will * automatically determine whether the network service should be reachable, * and hence whether the @backend is #EBackend:online. But subclasses may * override the online state if, for example, a connection attempt fails. * * Since: 3.4 **/ void e_backend_set_online (EBackend *backend, gboolean online) { g_return_if_fail (E_IS_BACKEND (backend)); /* Avoid unnecessary "notify" signals. */ if (backend->priv->online == online) return; backend->priv->online = online; /* Cancel any automatic "online" state update in progress. */ g_mutex_lock (&backend->priv->network_monitor_cancellable_lock); g_cancellable_cancel (backend->priv->network_monitor_cancellable); g_mutex_unlock (&backend->priv->network_monitor_cancellable_lock); g_object_notify (G_OBJECT (backend), "online"); if (!backend->priv->online && backend->priv->source) e_source_set_connection_status (backend->priv->source, E_SOURCE_CONNECTION_STATUS_DISCONNECTED); } /** * e_backend_ensure_online_state_updated: * @backend: an #EBackend * @cancellable: optional #GCancellable object, or %NULL * * Makes sure that the "online" property is updated, that is, if there * is any destination reachability test pending, it'll be done immediately * and the only state will be updated as well. * * Since: 3.18 **/ void e_backend_ensure_online_state_updated (EBackend *backend, GCancellable *cancellable) { gboolean needs_update = FALSE; g_return_if_fail (E_IS_BACKEND (backend)); g_object_ref (backend); g_mutex_lock (&backend->priv->update_online_state_lock); if (backend->priv->update_online_state) { g_source_destroy (backend->priv->update_online_state); g_source_unref (backend->priv->update_online_state); backend->priv->update_online_state = NULL; needs_update = TRUE; } g_mutex_unlock (&backend->priv->update_online_state_lock); if (!needs_update) { g_mutex_lock (&backend->priv->network_monitor_cancellable_lock); needs_update = backend->priv->network_monitor_cancellable != NULL; g_mutex_unlock (&backend->priv->network_monitor_cancellable_lock); } if (needs_update) e_backend_set_online (backend, e_backend_is_destination_reachable (backend, cancellable, NULL)); g_object_unref (backend); } /** * e_backend_get_source: * @backend: an #EBackend * * Returns the #ESource to which @backend is paired. * * Returns: the #ESource to which @backend is paired * * Since: 3.4 **/ ESource * e_backend_get_source (EBackend *backend) { g_return_val_if_fail (E_IS_BACKEND (backend), NULL); return backend->priv->source; } /** * e_backend_ref_connectable: * @backend: an #EBackend * * Returns the socket endpoint for the network service to which @backend * is a client, or %NULL if @backend does not use network sockets. * * The initial value of the #EBackend:connectable property is derived from * the #ESourceAuthentication extension of the @backend's #EBackend:source * property, if the extension is present. * * The returned #GSocketConnectable is referenced for thread-safety and * must be unreferenced with g_object_unref() when finished with it. * * Returns: a #GSocketConnectable, or %NULL * * Since: 3.8 **/ GSocketConnectable * e_backend_ref_connectable (EBackend *backend) { GSocketConnectable *connectable = NULL; g_return_val_if_fail (E_IS_BACKEND (backend), NULL); g_mutex_lock (&backend->priv->property_lock); if (backend->priv->connectable != NULL) connectable = g_object_ref (backend->priv->connectable); g_mutex_unlock (&backend->priv->property_lock); return connectable; } /** * e_backend_set_connectable: * @backend: an #EBackend * @connectable: a #GSocketConnectable, or %NULL * * Sets the socket endpoint for the network service to which @backend is * a client. This can be %NULL if @backend does not use network sockets. * * The initial value of the #EBackend:connectable property is derived from * the #ESourceAuthentication extension of the @backend's #EBackend:source * property, if the extension is present. * * Since: 3.8 **/ void e_backend_set_connectable (EBackend *backend, GSocketConnectable *connectable) { g_return_if_fail (E_IS_BACKEND (backend)); if (connectable != NULL) { g_return_if_fail (G_IS_SOCKET_CONNECTABLE (connectable)); g_object_ref (connectable); } g_mutex_lock (&backend->priv->property_lock); if (backend->priv->connectable != NULL) g_object_unref (backend->priv->connectable); backend->priv->connectable = connectable; g_mutex_unlock (&backend->priv->property_lock); backend_update_online_state (backend); g_object_notify (G_OBJECT (backend), "connectable"); } /** * e_backend_ref_main_context: * @backend: an #EBackend * * Returns the #GMainContext on which event sources for @backend are to * be attached. * * The returned #GMainContext is referenced for thread-safety and must be * unreferenced with g_main_context_unref() when finished with it. * * Returns: (transfer full): a #GMainContext * * Since: 3.8 **/ GMainContext * e_backend_ref_main_context (EBackend *backend) { g_return_val_if_fail (E_IS_BACKEND (backend), NULL); return g_main_context_ref (backend->priv->main_context); } /** * e_backend_credentials_required_sync: * @backend: an #EBackend * @reason: an #ESourceCredentialsReason, why the credentials are required * @certificate_pem: PEM-encoded secure connection certificate, or an empty string * @certificate_errors: a bit-or of #GTlsCertificateFlags for secure connection certificate * @op_error: (allow-none): a #GError with a description of the previous credentials error, or %NULL * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Synchronously lets the clients know that the backned requires credentials to be * properly opened. It's a proxy function for e_source_invoke_credentials_required_sync(), * where can be found more information about actual parameters meaning. * * The provided credentials are received through EBackend::authenticate_sync() * method asynchronously. * * If an error occurs, the function sets @error and returns %FALSE. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.16 **/ gboolean e_backend_credentials_required_sync (EBackend *backend, ESourceCredentialsReason reason, const gchar *certificate_pem, GTlsCertificateFlags certificate_errors, const GError *op_error, GCancellable *cancellable, GError **error) { ESource *source; g_return_val_if_fail (E_IS_BACKEND (backend), FALSE); source = e_backend_get_source (backend); g_return_val_if_fail (E_IS_SOURCE (source), FALSE); return e_source_invoke_credentials_required_sync (source, reason, certificate_pem, certificate_errors, op_error, cancellable, error); } typedef struct _CredentialsRequiredData { ESourceCredentialsReason reason; gchar *certificate_pem; GTlsCertificateFlags certificate_errors; GError *op_error; } CredentialsRequiredData; static void credentials_required_data_free (gpointer ptr) { CredentialsRequiredData *data = ptr; if (data) { g_free (data->certificate_pem); g_clear_error (&data->op_error); g_free (data); } } static void backend_credentials_required_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { CredentialsRequiredData *data = task_data; gboolean success; GError *local_error = NULL; success = e_backend_credentials_required_sync ( E_BACKEND (source_object), data->reason, data->certificate_pem, data->certificate_errors, data->op_error, cancellable, &local_error); if (local_error != NULL) { g_task_return_error (task, local_error); } else { g_task_return_boolean (task, success); } } /** * e_backend_credentials_required: * @backend: an #EBackend * @reason: an #ESourceCredentialsReason, why the credentials are required * @certificate_pem: PEM-encoded secure connection certificate, or an empty string * @certificate_errors: a bit-or of #GTlsCertificateFlags for secure connection certificate * @op_error: (allow-none): a #GError with a description of the previous credentials error, or %NULL * @cancellable: optional #GCancellable object, or %NULL * * Asynchronously calls the e_backend_credentials_required_sync() on the @backend, * to inform clients that credentials are required. * * When the operation is finished, @callback will be called. You can then * call e_backend_credentials_required_finish() to get the result of the operation. * * Since: 3.16 **/ void e_backend_credentials_required (EBackend *backend, ESourceCredentialsReason reason, const gchar *certificate_pem, GTlsCertificateFlags certificate_errors, const GError *op_error, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { CredentialsRequiredData *data; GTask *task; g_return_if_fail (E_IS_BACKEND (backend)); data = g_new0 (CredentialsRequiredData, 1); data->reason = reason; data->certificate_pem = g_strdup (certificate_pem); data->certificate_errors = certificate_errors; data->op_error = op_error ? g_error_copy (op_error) : NULL; task = g_task_new (backend, cancellable, callback, user_data); g_task_set_source_tag (task, e_backend_credentials_required); g_task_set_task_data (task, data, credentials_required_data_free); g_task_run_in_thread (task, backend_credentials_required_thread); g_object_unref (task); } /** * e_backend_credentials_required_finish: * @backend: an #EBackend * @result: a #GAsyncResult * @error: return location for a #GError, or %NULL * * Finishes the operation started with e_backend_credentials_required(). * * If an error occurs, the function sets @error and returns %FALSE. * * Returns: %TRUE on success, %FALSE on error * * Since: 3.16 **/ gboolean e_backend_credentials_required_finish (EBackend *backend, GAsyncResult *result, GError **error) { g_return_val_if_fail (E_IS_BACKEND (backend), FALSE); g_return_val_if_fail (g_task_is_valid (result, backend), FALSE); g_return_val_if_fail ( g_async_result_is_tagged ( result, e_backend_credentials_required), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } static void backend_scheduled_credentials_required_done_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GError *error = NULL; gchar *who_calls = user_data; g_return_if_fail (E_IS_BACKEND (source_object)); if (!e_backend_credentials_required_finish (E_BACKEND (source_object), result, &error) && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("%s: Failed to invoke credentials required: %s", who_calls ? who_calls : G_STRFUNC, error ? error->message : "Unknown error"); } g_clear_error (&error); g_free (who_calls); } /** * e_backend_schedule_credentials_required: * @backend: an #EBackend * @reason: an #ESourceCredentialsReason, why the credentials are required * @certificate_pem: PEM-encoded secure connection certificate, or an empty string * @certificate_errors: a bit-or of #GTlsCertificateFlags for secure connection certificate * @op_error: (allow-none): a #GError with a description of the previous credentials error, or %NULL * @cancellable: optional #GCancellable object, or %NULL * @who_calls: (allow-none): an identification who calls this * * Asynchronously invokes e_backend_credentials_required(), but installs its * own callback which only prints a runtime warning on the console when * the call fails. The @who_calls is a prefix of the console message. * This is useful when the caller just wants to start the operation * without having actual place where to show the operation result. * * Since: 3.16 **/ void e_backend_schedule_credentials_required (EBackend *backend, ESourceCredentialsReason reason, const gchar *certificate_pem, GTlsCertificateFlags certificate_errors, const GError *op_error, GCancellable *cancellable, const gchar *who_calls) { g_return_if_fail (E_IS_BACKEND (backend)); e_backend_credentials_required (backend, reason, certificate_pem, certificate_errors, op_error, cancellable, backend_scheduled_credentials_required_done_cb, g_strdup (who_calls)); } /** * e_backend_schedule_authenticate: * @backend: an #EBackend * @credentials: (allow-none): a credentials to use to authenticate, or %NULL * * Schedules a new authenticate session, cancelling any previously run. * This is usually done automatically, when an 'authenticate' signal is * received for the associated #ESource. With %NULL @credentials an attempt * without it is run. * * Since: 3.16 **/ void e_backend_schedule_authenticate (EBackend *backend, const ENamedParameters *credentials) { GCancellable *cancellable; AuthenticateThreadData *thread_data; g_return_if_fail (E_IS_BACKEND (backend)); g_mutex_lock (&backend->priv->authenticate_cancellable_lock); if (backend->priv->authenticate_cancellable) { g_cancellable_cancel (backend->priv->authenticate_cancellable); g_clear_object (&backend->priv->authenticate_cancellable); } backend->priv->authenticate_cancellable = g_cancellable_new (); cancellable = g_object_ref (backend->priv->authenticate_cancellable); g_mutex_unlock (&backend->priv->authenticate_cancellable_lock); thread_data = authenticate_thread_data_new (backend, cancellable, credentials); g_thread_unref (g_thread_new (NULL, backend_source_authenticate_thread, thread_data)); g_clear_object (&cancellable); } /** * e_backend_ensure_source_status_connected: * @backend: an #EBackend * * Makes sure that the associated ESource::connection-status is connected. This is * useful in cases when the backend can connect to the destination without invoking * EBackend::authenticate_sync, possibly through e_backend_schedule_authenticate(). * * Since: 3.18 **/ void e_backend_ensure_source_status_connected (EBackend *backend) { ESource *source; g_return_if_fail (E_IS_BACKEND (backend)); source = e_backend_get_source (backend); g_return_if_fail (E_IS_SOURCE (source)); if (e_source_get_connection_status (source) != E_SOURCE_CONNECTION_STATUS_CONNECTED) e_source_set_connection_status (source, E_SOURCE_CONNECTION_STATUS_CONNECTED); } /** * e_backend_get_user_prompter: * @backend: an #EBackend * * Gets an instance of #EUserPrompter, associated with this @backend. * * The returned 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); 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; } /** * e_backend_get_destination_address: * @backend: an #EBackend instance * @host: (out): destination server host name * @port: (out): destination server port * * Provides destination server host name and port to which * the backend connects. This is used to determine required * connection point for e_backend_destination_is_reachable(). * The @host is a newly allocated string, which will be freed * with g_free(). When @backend sets both @host and @port, then * it should return %TRUE, indicating it's a remote backend. * Default implementation returns %FALSE, which is treated * like the backend is local, no checking for server reachability * is possible. * * Returns: %TRUE, when it's a remote backend and provides both * @host and @port; %FALSE otherwise. * * Since: 3.8 **/ gboolean e_backend_get_destination_address (EBackend *backend, gchar **host, guint16 *port) { EBackendClass *klass; g_return_val_if_fail (E_IS_BACKEND (backend), FALSE); g_return_val_if_fail (host != NULL, FALSE); g_return_val_if_fail (port != NULL, FALSE); klass = E_BACKEND_GET_CLASS (backend); g_return_val_if_fail (klass->get_destination_address != NULL, FALSE); return klass->get_destination_address (backend, host, port); } /** * e_backend_is_destination_reachable: * @backend: an #EBackend instance * @cancellable: a #GCancellable instance, or %NULL * @error: a #GError for errors, or %NULL * * Checks whether the @backend's destination server, as returned * by e_backend_get_destination_address(), is reachable. * If the e_backend_get_destination_address() returns %FALSE, this function * returns %TRUE, meaning the destination is always reachable. * This uses #GNetworkMonitor's g_network_monitor_can_reach() * for reachability tests. * * Returns: %TRUE, when destination server address is reachable or * the backend doesn't provide destination address; %FALSE if * the backend destination server cannot be reached currently. * * Since: 3.8 **/ gboolean e_backend_is_destination_reachable (EBackend *backend, GCancellable *cancellable, GError **error) { gboolean reachable = TRUE; gchar *host = NULL; guint16 port = 0; g_return_val_if_fail (E_IS_BACKEND (backend), FALSE); if (e_backend_get_destination_address (backend, &host, &port)) { g_warn_if_fail (host != NULL); if (host) { GNetworkMonitor *network_monitor; GSocketConnectable *connectable; network_monitor = backend->priv->network_monitor; connectable = g_network_address_new (host, port); if (connectable) { reachable = g_network_monitor_can_reach ( network_monitor, connectable, cancellable, error); g_object_unref (connectable); } else { reachable = FALSE; } } } g_free (host); return reachable; } /** * e_backend_prepare_shutdown: * @backend: an #EBackend instance * * Let's the @backend know that it'll be shut down shortly, no client connects * to it anymore. The @backend can free any resources which reference it, for * example the opened views. * * Since: 3.16 */ void e_backend_prepare_shutdown (EBackend *backend) { EBackendClass *class; g_return_if_fail (E_IS_BACKEND (backend)); class = E_BACKEND_GET_CLASS (backend); g_return_if_fail (class->prepare_shutdown != NULL); class->prepare_shutdown (backend); }