diff --git a/meson.build b/meson.build index c9270d599f2c0a2286c8c66b5a7761d8b43de4bd..1fecfb84be6fca40ae3d396f266b6705c6c3bc0a 100644 --- a/meson.build +++ b/meson.build @@ -200,6 +200,9 @@ config_h.set_quoted('GOA_WINDOWS_LIVE_CLIENT_ID', windows_live_client_id) enable_windows_live = get_option('windows_live') config_h.set('GOA_WINDOWS_LIVE_ENABLED', enable_windows_live) +# Programs +enable_sendmail = get_option('sendmail') + # Optional timerfd support timerfd_support_src = ''' #include @@ -288,6 +291,7 @@ summary({ 'Vala support': enable_vapi, 'API docs': enable_gtk_doc, 'Man pages': enable_man, + 'sendmail interface': enable_sendmail, }, bool_yn: true, section: 'Options', diff --git a/meson_options.txt b/meson_options.txt index 6c8f32be963d4c260b6c079628d9fa1cef15af70..f507e4dab7c409bd956ad757f05d8cade7cad45b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -27,5 +27,6 @@ option('windows_live_client_id', type: 'string', value: '0000000044067703', desc option('gtk_doc', type: 'boolean', value: false, description: 'use gtk_doc to build documentation') option('introspection', type: 'boolean', value: true, description: 'Enable GObject Introspection (depends on GObject)') option('man', type: 'boolean', value: false, description: 'enable man pages') +option('sendmail', type: 'boolean', value: true, description: 'enable goa-sendmail') option('template_file', type: 'string', value: '', description: 'Path to the template file') option('vapi', type: 'boolean', value: true, description: 'build Vala binding') diff --git a/src/goabackend/goabackendenums-priv.h b/src/goabackend/goabackendenums-priv.h index d1a190574c0e0aa69749f5d359da63c58b93df20..fad6f4454ca9b7a7ab380b1b6ac53cc31359b120 100644 --- a/src/goabackend/goabackendenums-priv.h +++ b/src/goabackend/goabackendenums-priv.h @@ -44,22 +44,6 @@ typedef enum GOA_LOG_LEVEL_ERROR } GoaLogLevel; -/** - * GoaTlsType: - * @GOA_TLS_TYPE_NONE: No encryption. - * @GOA_TLS_TYPE_STARTTLS: STARTTLS should be used on a standard port - * after the connection has been established to obtain a secure channel. - * @GOA_TLS_TYPE_SSL: SSL should be used on a dedicated port. - * - * Type of SSL/TLS used to connect to a server. - */ -typedef enum -{ - GOA_TLS_TYPE_NONE, - GOA_TLS_TYPE_STARTTLS, - GOA_TLS_TYPE_SSL -} GoaTlsType; - G_END_DECLS #endif /* __GOA_BACKEND_ENUMS_PRIV_H__ */ diff --git a/src/goabackend/goaewsclient.c b/src/goabackend/goaewsclient.c index 4cb23581423fb7550ba8d8da369c6eea4ce33b2b..3b85ba6b2d60fe3c9b164c1fc815578db3fe7d39 100644 --- a/src/goabackend/goaewsclient.c +++ b/src/goabackend/goaewsclient.c @@ -30,6 +30,7 @@ #include #include "goaewsclient.h" +#include "goamailutils.h" #include "goautils.h" struct _GoaEwsClient diff --git a/src/goabackend/goahttpclient.c b/src/goabackend/goahttpclient.c index f2ce3b7be8a112f19bd24e87f2b8433a983b248d..3e22b50517c8eb660003840d6d665d141593365c 100644 --- a/src/goabackend/goahttpclient.c +++ b/src/goabackend/goahttpclient.c @@ -22,6 +22,7 @@ #include "goahttpclient.h" #include "goasouplogger.h" +#include "goamailutils.h" #include "goautils.h" struct _GoaHttpClient diff --git a/src/goabackend/goaimapsmtpprovider.c b/src/goabackend/goaimapsmtpprovider.c index 811d48c93fba2b7d8102da09b371758c9554e8d1..34fe02fdccf7c350a8943ec339de60956a697802 100644 --- a/src/goabackend/goaimapsmtpprovider.c +++ b/src/goabackend/goaimapsmtpprovider.c @@ -297,10 +297,9 @@ ensure_credentials_sync (GoaProvider *provider, goto out; } - mail_client = goa_mail_client_new (); - /* IMAP */ + mail_client = goa_mail_client_new (); imap_accept_ssl_errors = goa_util_lookup_keyfile_boolean (object, "ImapAcceptSslErrors"); imap_server = goa_util_lookup_keyfile_string (object, "ImapHost"); imap_username = goa_util_lookup_keyfile_string (object, "ImapUserName"); @@ -335,8 +334,11 @@ ensure_credentials_sync (GoaProvider *provider, goto out; } + g_clear_object (&mail_client); + /* SMTP */ + mail_client = goa_mail_client_new (); if (!goa_util_lookup_keyfile_boolean (object, "SmtpUseAuth")) goto smtp_done; @@ -849,8 +851,6 @@ add_account (GoaProvider *provider, gtk_widget_show_all (GTK_WIDGET (vbox)); g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data); - mail_client = goa_mail_client_new (); - /* Introduction */ gtk_notebook_set_current_page (GTK_NOTEBOOK (data.notebook), 0); @@ -913,6 +913,7 @@ add_account (GoaProvider *provider, g_cancellable_reset (data.cancellable); imap_auth = goa_imap_auth_login_new (imap_username, imap_password); + mail_client = goa_mail_client_new (); goa_mail_client_check (mail_client, imap_server, imap_tls_type, @@ -926,6 +927,7 @@ add_account (GoaProvider *provider, gtk_widget_set_sensitive (data.forward_button, FALSE); show_progress_ui (GTK_CONTAINER (data.progress_grid), TRUE); g_main_loop_run (data.loop); + g_clear_object (&mail_client); if (g_cancellable_is_cancelled (data.cancellable)) { @@ -974,6 +976,7 @@ add_account (GoaProvider *provider, /* SMTP */ /* Re-use the username and password from the IMAP page */ + mail_client = goa_mail_client_new (); gtk_entry_set_text (GTK_ENTRY (data.smtp_username), imap_username); gtk_entry_set_text (GTK_ENTRY (data.smtp_password), imap_password); @@ -1232,8 +1235,6 @@ refresh_account (GoaProvider *provider, gtk_widget_show_all (dialog); g_signal_connect (dialog, "response", G_CALLBACK (dialog_response_cb), &data); - mail_client = goa_mail_client_new (); - /* IMAP */ gtk_notebook_set_current_page (GTK_NOTEBOOK (data.notebook), 0); @@ -1256,6 +1257,7 @@ refresh_account (GoaProvider *provider, imap_password = gtk_entry_get_text (GTK_ENTRY (data.imap_password)); g_cancellable_reset (data.cancellable); imap_auth = goa_imap_auth_login_new (imap_username, imap_password); + mail_client = goa_mail_client_new (); goa_mail_client_check (mail_client, imap_server, imap_tls_type, @@ -1269,6 +1271,7 @@ refresh_account (GoaProvider *provider, gtk_widget_set_sensitive (data.forward_button, FALSE); show_progress_ui (GTK_CONTAINER (data.progress_grid), TRUE); g_main_loop_run (data.loop); + g_clear_object(&mail_client); if (g_cancellable_is_cancelled (data.cancellable)) { @@ -1333,6 +1336,7 @@ refresh_account (GoaProvider *provider, g_cancellable_reset (data.cancellable); goa_utils_parse_email_address (email_address, NULL, &domain); smtp_auth = goa_smtp_auth_new (domain, smtp_username, smtp_password); + mail_client = goa_mail_client_new (); goa_mail_client_check (mail_client, smtp_server, smtp_tls_type, diff --git a/src/goabackend/goautils.c b/src/goabackend/goautils.c index 70d66a6eaf2ce5eb9153a948070c4c208e8ccad1..525e148461b9a16a2a0b8a01837b7864f49c7a21 100644 --- a/src/goabackend/goautils.c +++ b/src/goabackend/goautils.c @@ -274,34 +274,6 @@ goa_utils_convert_duration_sec_to_abs_usec (gint duration_sec) return ret; } -gchar * -goa_utils_data_input_stream_read_line (GDataInputStream *stream, - gsize *length, - GCancellable *cancellable, - GError **error) -{ - GError *local_error = NULL; - gchar *ret = NULL; - - ret = g_data_input_stream_read_line (stream, length, cancellable, &local_error); - - /* Handle g_data_input_stream_read_line returning NULL without - * setting an error when there was no content to read. - */ - if (ret == NULL && local_error == NULL) - { - g_set_error (&local_error, - GOA_ERROR, - GOA_ERROR_FAILED, /* TODO: more specific */ - _("Could not parse response")); - } - - if (local_error != NULL) - g_propagate_error (error, local_error); - - return ret; -} - void goa_utils_set_dialog_title (GoaProvider *provider, GtkDialog *dialog, gboolean add_account) { @@ -868,48 +840,6 @@ goa_utils_set_error_soup (GError **err, SoupMessage *msg) g_free (error_msg); } -void -goa_utils_set_error_ssl (GError **err, GTlsCertificateFlags flags) -{ - const gchar *error_msg; - - switch (flags) - { - case G_TLS_CERTIFICATE_UNKNOWN_CA: - error_msg = _("The signing certificate authority is not known."); - break; - - case G_TLS_CERTIFICATE_BAD_IDENTITY: - error_msg = _("The certificate does not match the expected identity of the site that it was " - "retrieved from."); - break; - - case G_TLS_CERTIFICATE_NOT_ACTIVATED: - error_msg = _("The certificate’s activation time is still in the future."); - break; - - case G_TLS_CERTIFICATE_EXPIRED: - error_msg = _("The certificate has expired."); - break; - - case G_TLS_CERTIFICATE_REVOKED: - error_msg = _("The certificate has been revoked."); - break; - - case G_TLS_CERTIFICATE_INSECURE: - error_msg = _("The certificate’s algorithm is considered insecure."); - break; - - case G_TLS_CERTIFICATE_GENERIC_ERROR: - case G_TLS_CERTIFICATE_VALIDATE_ALL: - default: - error_msg = _("Invalid certificate."); - break; - } - - g_set_error_literal (err, GOA_ERROR, GOA_ERROR_SSL, error_msg); -} - gboolean goa_utils_get_credentials (GoaProvider *provider, GoaObject *object, diff --git a/src/goabackend/goautils.h b/src/goabackend/goautils.h index 3bd5ab478c587a0dc08b1f4364b9e8893c5038e4..7419e0f7ec98f487d92b8a1265ccca79613e5377 100644 --- a/src/goabackend/goautils.h +++ b/src/goabackend/goautils.h @@ -57,11 +57,6 @@ gint goa_utils_convert_abs_usec_to_duration_sec (gint64 abs_usec); gint64 goa_utils_convert_duration_sec_to_abs_usec (gint duration_sec); -gchar *goa_utils_data_input_stream_read_line (GDataInputStream *stream, - gsize *length, - GCancellable *cancellable, - GError **error); - void goa_utils_set_dialog_title (GoaProvider *provider, GtkDialog *dialog, gboolean add_account); gboolean goa_utils_delete_credentials_for_account_sync (GoaProvider *provider, @@ -108,8 +103,6 @@ gboolean goa_utils_parse_email_address (const gchar *email, gchar **out_ void goa_utils_set_error_soup (GError **err, SoupMessage *msg); -void goa_utils_set_error_ssl (GError **err, GTlsCertificateFlags flags); - gboolean goa_utils_get_credentials (GoaProvider *provider, GoaObject *object, const gchar *id, diff --git a/src/goabackend/meson.build b/src/goabackend/meson.build index f5382b34be4a348886a208c45c7649c0875b9614..5125eedf75d00858f4b45183df57cfaf8914b608 100644 --- a/src/goabackend/meson.build +++ b/src/goabackend/meson.build @@ -9,17 +9,13 @@ libgoa_backend_sources = files( 'goaexchangeprovider.c', 'goagoogleprovider.c', 'goahttpclient.c', - 'goaimapauthlogin.c', 'goaimapsmtpprovider.c', 'goalastfmprovider.c', - 'goamailauth.c', - 'goamailclient.c', 'goamediaserverprovider.c', 'goaobjectskeletonutils.c', 'goaowncloudprovider.c', 'goaprovider.c', 'goarestproxy.c', - 'goasmtpauth.c', 'goasouplogger.c', 'goautils.c', 'goawebview.c', @@ -86,6 +82,7 @@ deps = [ javascript_core_gtk_dep, json_glib_dep, libgoa_dep, + libgoa_mailclient_dep, libsecret_dep, libsoup_dep, libxml_dep, diff --git a/src/goamailclient/Makefile.am b/src/goamailclient/Makefile.am new file mode 100644 index 0000000000000000000000000000000000000000..26a61fee3818b22595d3e0af239aacb8ea33c465 --- /dev/null +++ b/src/goamailclient/Makefile.am @@ -0,0 +1,45 @@ + +NULL = +CLEANFILES = +EXTRA_DIST = + +AM_CPPFLAGS = \ + -I$(top_builddir)/src -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/goa \ + -I$(top_builddir)/src/goa \ + -I$(top_srcdir)/src/goamailclient \ + -DG_LOG_DOMAIN=\"GoaMailClient\" \ + -DGOA_API_IS_SUBJECT_TO_CHANGE \ + -DPACKAGE_LIBEXEC_DIR=\""$(libexecdir)"\" \ + -DPACKAGE_SYSCONF_DIR=\""$(sysconfdir)"\" \ + -DPACKAGE_DATA_DIR=\""$(pkgdatadir)"\" \ + -DPACKAGE_BIN_DIR=\""$(bindir)"\" \ + -DPACKAGE_LOCALSTATE_DIR=\""$(localstatedir)"\" \ + -DPACKAGE_LOCALE_DIR=\""$(localedir)"\" \ + -DPACKAGE_LIB_DIR=\""$(libdir)"\" \ + $(WARN_CFLAGS) \ + $(NULL) + +# ---------------------------------------------------------------------------------------------------- + +noinst_LTLIBRARIES = libgoamailclient.la + +libgoamailclient_la_SOURCES = \ + goaimapauthlogin.h goaimapauthlogin.c \ + goamailauth.h goamailauth.c \ + goamailclient.h goamailclient.c \ + goamailutils.h goamailutils.c \ + goasmtpauth.h goasmtpauth.c + +libgoamailclient_la_CFLAGS = \ + $(GLIB_CFLAGS) \ + $(NULL) + +libgoamailclient_la_LDFLAGS = \ + $(WARN_LDFLAGS) \ + $(NULL) + +clean-local : + rm -f *~ + +-include $(top_srcdir)/git.mk diff --git a/src/goabackend/goaimapauthlogin.c b/src/goamailclient/goaimapauthlogin.c similarity index 99% rename from src/goabackend/goaimapauthlogin.c rename to src/goamailclient/goaimapauthlogin.c index 6014cfc9b00d3c93a94f90104b50213767382991..7d7656cf197a8ccfdb5e877c35b909915afdd8df 100644 --- a/src/goabackend/goaimapauthlogin.c +++ b/src/goamailclient/goaimapauthlogin.c @@ -20,12 +20,13 @@ #include +#include #include #include #include #include "goaimapauthlogin.h" -#include "goautils.h" +#include "goamailutils.h" /* * SECTION:goaimapauthlogin diff --git a/src/goabackend/goaimapauthlogin.h b/src/goamailclient/goaimapauthlogin.h similarity index 88% rename from src/goabackend/goaimapauthlogin.h rename to src/goamailclient/goaimapauthlogin.h index cb2f41397a1a83c21e60aad18284b37fc1c9973a..09bfc5da3e0c0b976c8bf5d70e8e6948f0894e2e 100644 --- a/src/goabackend/goaimapauthlogin.h +++ b/src/goamailclient/goaimapauthlogin.h @@ -16,10 +16,6 @@ * Public License along with this library; if not, see . */ -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - #ifndef __GOA_IMAP_AUTH_LOGIN_H__ #define __GOA_IMAP_AUTH_LOGIN_H__ diff --git a/src/goabackend/goamailauth.c b/src/goamailclient/goamailauth.c similarity index 78% rename from src/goabackend/goamailauth.c rename to src/goamailclient/goamailauth.c index 1f9ceb661298acd79d6923d3371f484fb9425fc8..574b13818e310130e56efd63b6deff1017a5b127 100644 --- a/src/goabackend/goamailauth.c +++ b/src/goamailclient/goamailauth.c @@ -24,15 +24,20 @@ struct _GoaMailAuthPrivate { - GDataInputStream *input; + GDataInputStream *input; GDataOutputStream *output; + + gchar *host; + guint port; }; enum { PROP_0, PROP_INPUT, - PROP_OUTPUT + PROP_OUTPUT, + PROP_HOST, + PROP_PORT, }; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GoaMailAuth, goa_mail_auth, G_TYPE_OBJECT); @@ -83,6 +88,7 @@ goa_mail_auth_dispose (GObject *object) g_clear_object (&priv->input); g_clear_object (&priv->output); + g_clear_pointer (&priv->host, g_free); G_OBJECT_CLASS (goa_mail_auth_parent_class)->dispose (object); } @@ -108,6 +114,14 @@ goa_mail_auth_get_property (GObject *object, g_value_set_object (value, priv->output); break; + case PROP_HOST: + g_value_set_string (value, priv->host); + break; + + case PROP_PORT: + g_value_set_uint (value, priv->port); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -135,6 +149,14 @@ goa_mail_auth_set_property (GObject *object, priv->output = g_value_dup_object (value); break; + case PROP_HOST: + priv->host = g_value_dup_string (value); + break; + + case PROP_PORT: + priv->port = g_value_get_uint (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -174,6 +196,35 @@ goa_mail_auth_class_init (GoaMailAuthClass *klass) G_TYPE_DATA_OUTPUT_STREAM, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GoaSmtpAuth:host: + * + * The host that the SMTP server resides on. + */ + g_object_class_install_property (gobject_class, + PROP_HOST, + g_param_spec_string ("host", + "host", + "host", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + /** + * GoaSmtpAuth:port: + * + * The port that the SMTP server resides on. + */ + g_object_class_install_property (gobject_class, + PROP_PORT, + g_param_spec_uint ("port", + "port", + "port", + 1, 65535, 25, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + } /* ---------------------------------------------------------------------------------------------------- */ @@ -310,7 +361,10 @@ goa_mail_auth_set_input (GoaMailAuth *self, return; g_clear_object (&priv->input); - priv->input = g_object_ref (input); + if (input) + g_object_ref (input); + priv->input = input; + g_object_notify (G_OBJECT (self), "input"); } @@ -335,6 +389,57 @@ goa_mail_auth_set_output (GoaMailAuth *self, return; g_clear_object (&priv->output); - priv->output = g_object_ref (output); + if (output) + g_object_ref (output); + priv->output = output; + g_object_notify (G_OBJECT (self), "output"); } + +const gchar * +goa_mail_auth_get_host (GoaMailAuth *self) +{ + GoaMailAuthPrivate *priv; + + priv = goa_mail_auth_get_instance_private (self); + return priv->host; +} + +void +goa_mail_auth_set_host (GoaMailAuth *self, + const gchar *host) +{ + GoaMailAuthPrivate *priv; + + g_return_if_fail (host != NULL && host[0] != '\0'); + + priv = goa_mail_auth_get_instance_private (self); + + g_clear_pointer(&priv->host, g_free); + priv->host = g_strdup(host); + + g_object_notify (G_OBJECT (self), "host"); +} + +guint +goa_mail_auth_get_port (GoaMailAuth *self) +{ + GoaMailAuthPrivate *priv; + + priv = goa_mail_auth_get_instance_private (self); + return priv->port; +} + +void +goa_mail_auth_set_port (GoaMailAuth *self, + guint port) +{ + GoaMailAuthPrivate *priv; + + g_return_if_fail (port > 0 && port <= 65535); + + priv = goa_mail_auth_get_instance_private (self); + priv->port = port; + + g_object_notify (G_OBJECT (self), "port"); +} diff --git a/src/goabackend/goamailauth.h b/src/goamailclient/goamailauth.h similarity index 88% rename from src/goabackend/goamailauth.h rename to src/goamailclient/goamailauth.h index 50ad04c5046b9a17e8042bb046255d58d46326b8..bdc7ef5f7c6bd15d14bb4c76a1413cbd67b2db03 100644 --- a/src/goabackend/goamailauth.h +++ b/src/goamailclient/goamailauth.h @@ -16,10 +16,6 @@ * Public License along with this library; if not, see . */ -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - #ifndef __GOA_MAIL_AUTH_H__ #define __GOA_MAIL_AUTH_H__ @@ -74,6 +70,13 @@ GDataOutputStream *goa_mail_auth_get_output (GoaMailAuth *sel void goa_mail_auth_set_output (GoaMailAuth *self, GDataOutputStream *input); +const gchar *goa_mail_auth_get_host (GoaMailAuth *self); +void goa_mail_auth_set_host (GoaMailAuth *self, + const gchar *host); +guint goa_mail_auth_get_port (GoaMailAuth *self); +void goa_mail_auth_set_port (GoaMailAuth *self, + guint port); + G_END_DECLS #endif /* __GOA_MAIL_AUTH_H__ */ diff --git a/src/goabackend/goamailclient.c b/src/goamailclient/goamailclient.c similarity index 74% rename from src/goabackend/goamailclient.c rename to src/goamailclient/goamailclient.c index b6586fbe21505594aa653c64c07b0c52cd3a19d3..4ae73fa35f7e94b55da7e48d0ebbf3a61d399210 100644 --- a/src/goabackend/goamailclient.c +++ b/src/goamailclient/goamailclient.c @@ -18,10 +18,11 @@ #include "config.h" +#include #include #include "goamailclient.h" -#include "goautils.h" +#include "goamailutils.h" /* The timeout used for non-IDLE commands */ #define COMMAND_TIMEOUT_SEC 30 @@ -30,6 +31,13 @@ struct _GoaMailClient { /*< private >*/ GObject parent_instance; + + GIOStream *tls_conn; + GSocket *socket; + GSocketClient *sc; + GSocketConnection *conn; + GDataInputStream *input; + GDataOutputStream *output; }; G_DEFINE_TYPE (GoaMailClient, goa_mail_client, G_TYPE_OBJECT); @@ -41,9 +49,29 @@ goa_mail_client_init (GoaMailClient *self) { } +static void +goa_mail_client_finalize (GObject *object) +{ + GoaMailClient *self = GOA_MAIL_CLIENT (object); + + g_io_stream_close (G_IO_STREAM (self->conn), NULL, NULL); + + g_clear_object (&self->sc); + g_clear_object (&self->input); + g_clear_object (&self->output); + g_clear_object (&self->socket); + g_clear_object (&self->conn); + g_clear_object (&self->tls_conn); + + G_OBJECT_CLASS (goa_mail_client_parent_class)->finalize (object); +} + static void goa_mail_client_class_init (GoaMailClientClass *klass) { + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = goa_mail_client_finalize; } /* ---------------------------------------------------------------------------------------------------- */ @@ -56,33 +84,41 @@ goa_mail_client_new (void) /* ---------------------------------------------------------------------------------------------------- */ +GDataInputStream * +goa_mail_client_get_input (GoaMailClient *self) +{ + g_return_val_if_fail (GOA_IS_MAIL_CLIENT (self), FALSE); + + return self->input; +} + +GDataOutputStream * +goa_mail_client_get_output (GoaMailClient *self) +{ + g_return_val_if_fail (GOA_IS_MAIL_CLIENT (self), FALSE); + + return self->output; +} + +/* ---------------------------------------------------------------------------------------------------- */ + typedef struct { - GDataInputStream *input; - GDataOutputStream *output; - GIOStream *tls_conn; - GSocket *socket; - GSocketClient *sc; - GSocketConnection *conn; + GoaMailClient *self; GTlsCertificateFlags cert_flags; GoaMailAuth *auth; GoaTlsType tls_type; gboolean accept_ssl_errors; - gchar *host_and_port; - guint16 default_port; + GSocketConnectable *server_identity; } CheckData; static void mail_client_check_data_free (CheckData *data) { - g_object_unref (data->sc); + goa_mail_auth_set_input (data->auth, NULL); + goa_mail_auth_set_output (data->auth, NULL); g_object_unref (data->auth); - g_clear_object (&data->input); - g_clear_object (&data->output); - g_clear_object (&data->socket); - g_clear_object (&data->conn); - g_clear_object (&data->tls_conn); - g_free (data->host_and_port); + g_object_unref (data->server_identity); g_slice_free (CheckData, data); } @@ -107,15 +143,16 @@ mail_client_check_event_cb (GSocketClient *sc, gpointer user_data) { CheckData *data = user_data; + GoaMailClient *self = data->self; if (event != G_SOCKET_CLIENT_TLS_HANDSHAKING) return; - data->tls_conn = g_object_ref (connection); + self->tls_conn = g_object_ref (connection); if (data->accept_ssl_errors) - g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (data->tls_conn), 0); + g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (self->tls_conn), 0); - g_signal_connect (data->tls_conn, + g_signal_connect (self->tls_conn, "accept-certificate", G_CALLBACK (mail_client_check_accept_certificate_cb), data); @@ -141,7 +178,6 @@ mail_client_check_auth_run_cb (GObject *source_object, GAsyncResult *res, gpoint goto out; } - g_io_stream_close (G_IO_STREAM (data->conn), NULL, NULL); g_task_return_boolean (task, TRUE); out: @@ -152,22 +188,19 @@ static void mail_client_check_tls_conn_handshake_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GTask *task = G_TASK (user_data); + GoaMailClient *self; CheckData *data; GCancellable *cancellable; - GDataInputStream *input; - GDataOutputStream *output; GInputStream *base_input; GError *error; GOutputStream *base_output; - input = NULL; - output = NULL; - cancellable = g_task_get_cancellable (task); data = g_task_get_task_data (task); + self = data->self; error = NULL; - if (!g_tls_connection_handshake_finish (G_TLS_CONNECTION (data->tls_conn), res, &error)) + if (!g_tls_connection_handshake_finish (G_TLS_CONNECTION (self->tls_conn), res, &error)) { g_warning ("g_tls_connection_handshake() failed: %s (%s, %d)", error->message, @@ -196,25 +229,23 @@ mail_client_check_tls_conn_handshake_cb (GObject *source_object, GAsyncResult *r goto out; } - g_clear_object (&data->conn); - data->conn = g_tcp_wrapper_connection_new (data->tls_conn, data->socket); + g_clear_object (&self->conn); + self->conn = g_tcp_wrapper_connection_new (self->tls_conn, self->socket); - base_input = g_io_stream_get_input_stream (G_IO_STREAM (data->conn)); - input = g_data_input_stream_new (base_input); - g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (input), FALSE); - g_data_input_stream_set_newline_type (input, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); - goa_mail_auth_set_input (data->auth, input); + base_input = g_io_stream_get_input_stream (G_IO_STREAM (self->conn)); + self->input = g_data_input_stream_new (base_input); + g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (self->input), FALSE); + g_data_input_stream_set_newline_type (self->input, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); + goa_mail_auth_set_input (data->auth, self->input); - base_output = g_io_stream_get_output_stream (G_IO_STREAM (data->conn)); - output = g_data_output_stream_new (base_output); - g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (output), FALSE); - goa_mail_auth_set_output (data->auth, output); + base_output = g_io_stream_get_output_stream (G_IO_STREAM (self->conn)); + self->output = g_data_output_stream_new (base_output); + g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (self->output), FALSE); + goa_mail_auth_set_output (data->auth, self->output); goa_mail_auth_run (data->auth, cancellable, mail_client_check_auth_run_cb, g_object_ref (task)); out: - g_clear_object (&input); - g_clear_object (&output); g_object_unref (G_OBJECT (task)); } @@ -222,15 +253,14 @@ static void mail_client_check_auth_starttls_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GTask *task = G_TASK (user_data); + GoaMailClient *self; CheckData *data; GCancellable *cancellable; - GSocketConnectable *server_identity; GError *error; - server_identity = NULL; - cancellable = g_task_get_cancellable (task); data = g_task_get_task_data (task); + self = data->self; error = NULL; if (!goa_mail_auth_starttls_finish (data->auth, res, &error)) @@ -244,37 +274,28 @@ mail_client_check_auth_starttls_cb (GObject *source_object, GAsyncResult *res, g } error = NULL; - server_identity = g_network_address_parse (data->host_and_port, data->default_port, &error); - if (server_identity == NULL) - { - g_task_return_error (task, error); - goto out; - } - - error = NULL; - data->tls_conn = g_tls_client_connection_new (G_IO_STREAM (data->conn), server_identity, &error); - if (data->tls_conn == NULL) + self->tls_conn = g_tls_client_connection_new (G_IO_STREAM (self->conn), data->server_identity, &error); + if (self->tls_conn == NULL) { g_task_return_error (task, error); goto out; } if (data->accept_ssl_errors) - g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (data->tls_conn), 0); + g_tls_client_connection_set_validation_flags (G_TLS_CLIENT_CONNECTION (self->tls_conn), 0); - g_signal_connect (data->tls_conn, + g_signal_connect (self->tls_conn, "accept-certificate", G_CALLBACK (mail_client_check_accept_certificate_cb), data); - g_tls_connection_handshake_async (G_TLS_CONNECTION (data->tls_conn), + g_tls_connection_handshake_async (G_TLS_CONNECTION (self->tls_conn), G_PRIORITY_DEFAULT, cancellable, mail_client_check_tls_conn_handshake_cb, g_object_ref (task)); out: - g_clear_object (&server_identity); g_object_unref (G_OBJECT (task)); } @@ -282,22 +303,22 @@ static void mail_client_check_connect_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { GTask *task = G_TASK (user_data); + GoaMailClient *self; CheckData *data; GCancellable *cancellable; - GDataInputStream *input; - GDataOutputStream *output; GInputStream *base_input; GError *error; GOutputStream *base_output; cancellable = g_task_get_cancellable (task); data = g_task_get_task_data (task); + self = data->self; error = NULL; - data->conn = g_socket_client_connect_to_host_finish (data->sc, res, &error); - if (data->conn == NULL) + self->conn = g_socket_client_connect_finish (self->sc, res, &error); + if (self->conn == NULL) { - g_warning ("g_socket_client_connect_to_host() failed: %s (%s, %d)", + g_warning ("g_socket_client_connect() failed: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); @@ -325,21 +346,19 @@ mail_client_check_connect_cb (GObject *source_object, GAsyncResult *res, gpointe } /* fail quickly */ - data->socket = g_object_ref (g_socket_connection_get_socket (data->conn)); - g_socket_set_timeout (data->socket, COMMAND_TIMEOUT_SEC); - - base_input = g_io_stream_get_input_stream (G_IO_STREAM (data->conn)); - input = g_data_input_stream_new (base_input); - g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (input), FALSE); - g_data_input_stream_set_newline_type (input, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); - goa_mail_auth_set_input (data->auth, input); - g_object_unref (input); - - base_output = g_io_stream_get_output_stream (G_IO_STREAM (data->conn)); - output = g_data_output_stream_new (base_output); - g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (output), FALSE); - goa_mail_auth_set_output (data->auth, output); - g_object_unref (output); + self->socket = g_object_ref (g_socket_connection_get_socket (self->conn)); + g_socket_set_timeout (self->socket, COMMAND_TIMEOUT_SEC); + + base_input = g_io_stream_get_input_stream (G_IO_STREAM (self->conn)); + self->input = g_data_input_stream_new (base_input); + g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (self->input), FALSE); + g_data_input_stream_set_newline_type (self->input, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); + goa_mail_auth_set_input (data->auth, self->input); + + base_output = g_io_stream_get_output_stream (G_IO_STREAM (self->conn)); + self->output = g_data_output_stream_new (base_output); + g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (self->output), FALSE); + goa_mail_auth_set_output (data->auth, self->output); if (data->tls_type == GOA_TLS_TYPE_STARTTLS) goa_mail_auth_starttls (data->auth, cancellable, mail_client_check_auth_starttls_cb, g_object_ref (task)); @@ -362,6 +381,8 @@ goa_mail_client_check (GoaMailClient *self, gpointer user_data) { CheckData *data; + GError *error = NULL; + GSocketConnectable *server_identity; GTask *task; g_return_if_fail (GOA_IS_MAIL_CLIENT (self)); @@ -372,29 +393,41 @@ goa_mail_client_check (GoaMailClient *self, task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, goa_mail_client_check); + server_identity = g_network_address_parse (host_and_port, default_port, &error); + if (server_identity == NULL) + { + g_task_return_error (task, error); + goto out; + } + data = g_slice_new0 (CheckData); g_task_set_task_data (task, data, (GDestroyNotify) mail_client_check_data_free); - data->sc = g_socket_client_new (); + data->self = self; + self->sc = g_socket_client_new (); if (tls_type == GOA_TLS_TYPE_SSL) { - g_socket_client_set_tls (data->sc, TRUE); - g_signal_connect (data->sc, "event", G_CALLBACK (mail_client_check_event_cb), data); + g_socket_client_set_tls (self->sc, TRUE); + g_signal_connect (self->sc, "event", G_CALLBACK (mail_client_check_event_cb), data); } - data->host_and_port = g_strdup (host_and_port); + goa_mail_auth_set_host(auth, + g_network_address_get_hostname(G_NETWORK_ADDRESS(server_identity))); + goa_mail_auth_set_port(auth, + g_network_address_get_port(G_NETWORK_ADDRESS(server_identity))); + + data->server_identity = server_identity; data->tls_type = tls_type; data->accept_ssl_errors = accept_ssl_errors; - data->default_port = default_port; data->auth = g_object_ref (auth); - g_socket_client_connect_to_host_async (data->sc, - data->host_and_port, - data->default_port, - cancellable, - mail_client_check_connect_cb, - g_object_ref (task)); + g_socket_client_connect_async (self->sc, + data->server_identity, + cancellable, + mail_client_check_connect_cb, + g_object_ref (task)); +out: g_object_unref (task); } diff --git a/src/goabackend/goamailclient.h b/src/goamailclient/goamailclient.h similarity index 93% rename from src/goabackend/goamailclient.h rename to src/goamailclient/goamailclient.h index 8d155094a179d309082d58fc4f75419ca4f0323e..4e16ae22c2a37bb2263f9af8436a9b118dadfbad 100644 --- a/src/goabackend/goamailclient.h +++ b/src/goamailclient/goamailclient.h @@ -16,10 +16,6 @@ * Public License along with this library; if not, see . */ -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - #ifndef __GOA_MAIL_CLIENT_H__ #define __GOA_MAIL_CLIENT_H__ @@ -27,7 +23,7 @@ #include #include -#include "goabackendenums-priv.h" +#include "goamailclientenums.h" #include "goamailauth.h" G_BEGIN_DECLS @@ -57,6 +53,9 @@ gboolean goa_mail_client_check_sync (GoaMailClient *self, GCancellable *cancellable, GError **error); +GDataInputStream *goa_mail_client_get_input (GoaMailClient *self); +GDataOutputStream *goa_mail_client_get_output (GoaMailClient *self); + G_END_DECLS #endif /* __GOA_MAIL_CLIENT_H__ */ diff --git a/src/goamailclient/goamailclientenums.h b/src/goamailclient/goamailclientenums.h new file mode 100644 index 0000000000000000000000000000000000000000..41e155646bde9dbef395658127cb7122299246a2 --- /dev/null +++ b/src/goamailclient/goamailclientenums.h @@ -0,0 +1,42 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright © 2013 – 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#ifndef __GOA_MAILCLIENT_ENUMS_PRIV_H__ +#define __GOA_MAILCLIENT_ENUMS_PRIV_H__ + +G_BEGIN_DECLS + +/** + * GoaTlsType: + * @GOA_TLS_TYPE_NONE: No encryption. + * @GOA_TLS_TYPE_STARTTLS: STARTTLS should be used on a standard port + * after the connection has been established to obtain a secure channel. + * @GOA_TLS_TYPE_SSL: SSL should be used on a dedicated port. + * + * Type of SSL/TLS used to connect to a server. + */ +typedef enum +{ + GOA_TLS_TYPE_NONE, + GOA_TLS_TYPE_STARTTLS, + GOA_TLS_TYPE_SSL +} GoaTlsType; + +G_END_DECLS + +#endif /* __GOA_BACKEND_ENUMS_PRIV_H__ */ diff --git a/src/goamailclient/goamailutils.c b/src/goamailclient/goamailutils.c new file mode 100644 index 0000000000000000000000000000000000000000..acbfcc98c6853b14c3b1b54c91856c42f7a74f06 --- /dev/null +++ b/src/goamailclient/goamailutils.c @@ -0,0 +1,93 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright © 2012 – 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#include "config.h" +#include "goamailutils.h" + +#include +#include + +gchar * +goa_utils_data_input_stream_read_line (GDataInputStream *stream, + gsize *length, + GCancellable *cancellable, + GError **error) +{ + GError *local_error = NULL; + gchar *ret = NULL; + + ret = g_data_input_stream_read_line (stream, length, cancellable, &local_error); + + /* Handle g_data_input_stream_read_line returning NULL without + * setting an error when there was no content to read. + */ + if (ret == NULL && local_error == NULL) + { + g_set_error (&local_error, + GOA_ERROR, + GOA_ERROR_FAILED, /* TODO: more specific */ + _("Could not parse response")); + } + + if (local_error != NULL) + g_propagate_error (error, local_error); + + return ret; +} + +void +goa_utils_set_error_ssl (GError **err, GTlsCertificateFlags flags) +{ + const gchar *error_msg; + + switch (flags) + { + case G_TLS_CERTIFICATE_UNKNOWN_CA: + error_msg = _("The signing certificate authority is not known."); + break; + + case G_TLS_CERTIFICATE_BAD_IDENTITY: + error_msg = _("The certificate does not match the expected identity of the site that it was " + "retrieved from."); + break; + + case G_TLS_CERTIFICATE_NOT_ACTIVATED: + error_msg = _("The certificate’s activation time is still in the future."); + break; + + case G_TLS_CERTIFICATE_EXPIRED: + error_msg = _("The certificate has expired."); + break; + + case G_TLS_CERTIFICATE_REVOKED: + error_msg = _("The certificate has been revoked."); + break; + + case G_TLS_CERTIFICATE_INSECURE: + error_msg = _("The certificate’s algorithm is considered insecure."); + break; + + case G_TLS_CERTIFICATE_GENERIC_ERROR: + case G_TLS_CERTIFICATE_VALIDATE_ALL: + default: + error_msg = _("Invalid certificate."); + break; + } + + g_set_error_literal (err, GOA_ERROR, GOA_ERROR_SSL, error_msg); +} diff --git a/src/goamailclient/goamailutils.h b/src/goamailclient/goamailutils.h new file mode 100644 index 0000000000000000000000000000000000000000..a9ae8affc394a173250b60d2a221fb776c324172 --- /dev/null +++ b/src/goamailclient/goamailutils.h @@ -0,0 +1,37 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright © 2012 – 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#ifndef __GOA_MAIL_UTILS_H__ +#define __GOA_MAIL_UTILS_H__ + +#include +#include + +G_BEGIN_DECLS + +gchar *goa_utils_data_input_stream_read_line (GDataInputStream *stream, + gsize *length, + GCancellable *cancellable, + GError **error); + +void goa_utils_set_error_ssl (GError **err, + GTlsCertificateFlags flags); + +G_END_DECLS + +#endif /* __GOA_UTILS_H__ */ diff --git a/src/goabackend/goasmtpauth.c b/src/goamailclient/goasmtpauth.c similarity index 64% rename from src/goabackend/goasmtpauth.c rename to src/goamailclient/goasmtpauth.c index 3c4351a786093c060deda3374e0e27e922c27f55..6657dc9a69ac79c4016e5bda83bf087743098941 100644 --- a/src/goabackend/goasmtpauth.c +++ b/src/goamailclient/goasmtpauth.c @@ -20,10 +20,11 @@ #include +#include #include #include "goasmtpauth.h" -#include "goautils.h" +#include "goamailutils.h" /* * SECTION:goasmtpauth @@ -50,9 +51,11 @@ struct _GoaSmtpAuth gboolean greeting_absent; gboolean login_supported; gboolean plain_supported; + gboolean oauthbearer_supported; gchar *domain; gchar *username; gchar *password; + gchar *access_token; }; enum @@ -60,9 +63,13 @@ enum PROP_0, PROP_DOMAIN, PROP_USERNAME, - PROP_PASSWORD + PROP_PASSWORD, + PROP_ACCESS_TOKEN, + PROP__AFTER_LAST }; +#define PROP_COUNT (PROP__AFTER_LAST - 1) + static gboolean goa_smtp_auth_is_needed (GoaMailAuth *auth); static gboolean goa_smtp_auth_run_sync (GoaMailAuth *auth, GCancellable *cancellable, @@ -209,6 +216,7 @@ goa_smtp_auth_finalize (GObject *object) g_free (self->domain); g_free (self->username); g_free (self->password); + g_free (self->access_token); G_OBJECT_CLASS (goa_smtp_auth_parent_class)->finalize (object); } @@ -235,6 +243,10 @@ goa_smtp_auth_get_property (GObject *object, g_value_set_string (value, self->password); break; + case PROP_ACCESS_TOKEN: + g_value_set_string (value, self->access_token); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -263,6 +275,10 @@ goa_smtp_auth_set_property (GObject *object, self->password = g_value_dup_string (value); break; + case PROP_ACCESS_TOKEN: + self->access_token = g_value_dup_string (value); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -340,6 +356,22 @@ goa_smtp_auth_class_init (GoaSmtpAuthClass *klass) G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + /** + * GoaSmtpAuth:access_token: + * + * The OAuth2 access token. + */ + g_object_class_install_property (gobject_class, + PROP_ACCESS_TOKEN, + g_param_spec_string ("access-token", + "access-token", + "access-token", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); } /* ---------------------------------------------------------------------------------------------------- */ @@ -361,15 +393,67 @@ goa_smtp_auth_new (const gchar *domain, const gchar *username, const gchar *password) { + g_return_val_if_fail (password != NULL && password[0] != '\0', NULL); + + return goa_smtp_auth_new_full(domain, username, password, NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +/** + * goa_smtp_auth_new_full: + * @domain: The domain to use. + * @username: The user name to use. + * @password: The password to use. + * @access_token: The OAuth2 access token to use. + * + * Creates a new #GoaMailAuth to be used for authentication + * using LOGIN, PLAIN or OAUTHBEARER over SMTP. + * + * Returns: (type GoaSmtpAuth): A #GoaSmtpAuth. Free with + * g_object_unref(). + */ +GoaMailAuth * +goa_smtp_auth_new_full (const gchar *domain, + const gchar *username, + const gchar *password, + const gchar *access_token) +{ + const char *names[PROP_COUNT] = {}; + GValue values[PROP_COUNT] = {}; + int n_params = -1; + g_return_val_if_fail (domain != NULL && domain[0] != '\0', NULL); g_return_val_if_fail (username != NULL, NULL); - g_return_val_if_fail (password != NULL && password[0] != '\0', NULL); + g_return_val_if_fail (password == NULL || password[0] != '\0', NULL); + g_return_val_if_fail (access_token == NULL || access_token[0] != '\0', NULL); + + names[++n_params] = "domain"; + g_value_init(&values[n_params], G_TYPE_STRING); + g_value_take_string(&values[n_params], g_strdup(domain)); + + names[++n_params] = "user-name"; + g_value_init(&values[n_params], G_TYPE_STRING); + g_value_take_string(&values[n_params], g_strdup(username)); - return GOA_MAIL_AUTH (g_object_new (GOA_TYPE_SMTP_AUTH, - "domain", domain, - "user-name", username, - "password", password, - NULL)); + if (password) + { + names[++n_params] = "password"; + g_value_init(&values[n_params], G_TYPE_STRING); + g_value_take_string(&values[n_params], g_strdup(password)); + } + + if (access_token) + { + names[++n_params] = "access-token"; + g_value_init(&values[n_params], G_TYPE_STRING); + g_value_take_string(&values[n_params], g_strdup(access_token)); + } + + return GOA_MAIL_AUTH (g_object_new_with_properties (GOA_TYPE_SMTP_AUTH, + ++n_params, + names, + values)); } /* ---------------------------------------------------------------------------------------------------- */ @@ -386,6 +470,12 @@ goa_smtp_auth_is_plain (GoaSmtpAuth *self) return self->plain_supported; } +gboolean +goa_smtp_auth_is_oauthbearer (GoaSmtpAuth *self) +{ + return self->oauthbearer_supported; +} + /* ---------------------------------------------------------------------------------------------------- */ static gboolean @@ -397,6 +487,161 @@ goa_smtp_auth_is_needed (GoaMailAuth *auth) /* ---------------------------------------------------------------------------------------------------- */ +static gboolean +goa_smtp_auth_plain_sync(GoaSmtpAuth *self, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gchar *auth_arg_base64 = NULL; + gchar *auth_arg_plain = NULL; + gchar *request = NULL; + gchar *response = NULL; + gsize auth_arg_plain_len; + + auth_arg_plain = g_strdup_printf ("%s%c%s%c%s", self->username, '\0', self->username, '\0', self->password); + auth_arg_plain_len = 2 * strlen (self->username) + 2 + strlen (self->password); + auth_arg_base64 = g_base64_encode ((guchar *) auth_arg_plain, auth_arg_plain_len); + + request = g_strdup_printf ("AUTH PLAIN %s\r\n", auth_arg_base64); + g_debug ("> AUTH PLAIN ********************"); + if (!g_data_output_stream_put_string (output, request, cancellable, error)) + goto out; + + response = goa_utils_data_input_stream_read_line (input, NULL, cancellable, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_auth_check_not_235 (response, error)) + goto out; + + ret = TRUE; + +out: + g_free (auth_arg_base64); + g_free (auth_arg_plain); + g_free (request); + g_free (response); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_smtp_auth_login_sync(GoaSmtpAuth *self, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gchar *auth_arg_base64 = NULL; + gchar *auth_arg_plain = NULL; + gchar *request = NULL; + gchar *response = NULL; + gsize auth_arg_plain_len; + + auth_arg_plain = g_strdup (self->username); + auth_arg_plain_len = strlen (self->username); + auth_arg_base64 = g_base64_encode ((guchar *) auth_arg_plain, auth_arg_plain_len); + + request = g_strdup_printf ("AUTH LOGIN %s\r\n", auth_arg_base64); + g_debug ("> AUTH LOGIN ********************"); + if (!g_data_output_stream_put_string (output, request, cancellable, error)) + goto out; + + g_clear_pointer (&auth_arg_base64, g_free); + g_clear_pointer (&auth_arg_plain, g_free); + g_clear_pointer (&request, g_free); + + response = goa_utils_data_input_stream_read_line (input, NULL, cancellable, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_auth_check_not_334_login_password (response, error)) + goto out; + + g_clear_pointer (&response, g_free); + + auth_arg_plain = g_strdup (self->password); + auth_arg_plain_len = strlen (self->password); + auth_arg_base64 = g_base64_encode ((guchar *) auth_arg_plain, auth_arg_plain_len); + + request = g_strdup_printf ("%s\r\n", auth_arg_base64); + g_debug ("> ********************"); + if (!g_data_output_stream_put_string (output, request, cancellable, error)) + goto out; + response = goa_utils_data_input_stream_read_line (input, NULL, cancellable, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_auth_check_not_235 (response, error)) + goto out; + + ret = TRUE; + +out: + g_free (auth_arg_base64); + g_free (auth_arg_plain); + g_free (request); + g_free (response); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gboolean +goa_smtp_auth_oauthbearer_sync(GoaSmtpAuth *self, + GDataInputStream *input, + GDataOutputStream *output, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + gchar *auth_arg_base64 = NULL; + gchar *auth_arg_plain = NULL; + gchar *request = NULL; + gchar *response = NULL; + const gchar *host = goa_mail_auth_get_host(GOA_MAIL_AUTH(self)); + guint port = goa_mail_auth_get_port(GOA_MAIL_AUTH(self)); + gsize auth_arg_plain_len; + + g_return_val_if_fail (host != NULL && host[0] != '\0', FALSE); + g_return_val_if_fail (port != 0, FALSE); + + /* The Keyed Message Digest format is in RFC 7628 paragraphs 3.3 and 4.1. */ + auth_arg_plain = g_strdup_printf ("n,a=%s,%chost=%s%cport=%d%cauth=Bearer %s%c%c", + self->username, '\1', host, '\1', port, '\1', + self->access_token, '\1', '\1'); + auth_arg_plain_len = strlen(auth_arg_plain); + auth_arg_base64 = g_base64_encode ((guchar *) auth_arg_plain, auth_arg_plain_len); + + request = g_strdup_printf ("AUTH OAUTHBEARER %s\r\n", auth_arg_base64); + g_debug ("> AUTH OAUTHBEARER ********************"); + if (!g_data_output_stream_put_string (output, request, cancellable, error)) + goto out; + + response = goa_utils_data_input_stream_read_line (input, NULL, cancellable, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_auth_check_not_235 (response, error)) + goto out; + + ret = TRUE; + +out: + g_free (auth_arg_base64); + g_free (auth_arg_plain); + g_free (request); + g_free (response); + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + static gboolean goa_smtp_auth_run_sync (GoaMailAuth *auth, GCancellable *cancellable, @@ -406,11 +651,8 @@ goa_smtp_auth_run_sync (GoaMailAuth *auth, GDataInputStream *input; GDataOutputStream *output; gboolean ret = FALSE; - gchar *auth_arg_base64 = NULL; - gchar *auth_arg_plain = NULL; gchar *request = NULL; gchar *response = NULL; - gsize auth_arg_plain_len; input = goa_mail_auth_get_input (auth); output = goa_mail_auth_get_output (auth); @@ -443,12 +685,14 @@ goa_smtp_auth_run_sync (GoaMailAuth *auth, if (smtp_auth_check_not_250 (response, error)) goto out; - if (g_str_has_prefix (response + 4, "AUTH")) + if ((self->password || self->access_token) && g_str_has_prefix (response + 4, "AUTH")) { self->auth_supported = TRUE; - if (strstr (response, "PLAIN") != NULL) + if (self->access_token && strstr (response, "OAUTHBEARER") != NULL) + self->oauthbearer_supported = TRUE; + if (self->password && strstr (response, "PLAIN") != NULL) self->plain_supported = TRUE; - else if (strstr (response, "LOGIN") != NULL) + if (self->password && strstr (response, "LOGIN") != NULL) self->login_supported = TRUE; } @@ -462,7 +706,7 @@ goa_smtp_auth_run_sync (GoaMailAuth *auth, ret = TRUE; goto out; } - else if (!self->login_supported && !self->plain_supported) + else if (!self->login_supported && !self->plain_supported && !self->oauthbearer_supported) { g_set_error (error, GOA_ERROR, @@ -474,68 +718,20 @@ goa_smtp_auth_run_sync (GoaMailAuth *auth, /* Try different SASL mechanisms */ - if (self->plain_supported) - { - /* AUTH PLAIN */ - - auth_arg_plain = g_strdup_printf ("%s%c%s%c%s", self->username, '\0', self->username, '\0', self->password); - auth_arg_plain_len = 2 * strlen (self->username) + 2 + strlen (self->password); - auth_arg_base64 = g_base64_encode ((guchar *) auth_arg_plain, auth_arg_plain_len); - - request = g_strdup_printf ("AUTH PLAIN %s\r\n", auth_arg_base64); - g_debug ("> AUTH PLAIN ********************"); - if (!g_data_output_stream_put_string (output, request, cancellable, error)) - goto out; - g_clear_pointer (&request, g_free); - } - else + if (self->oauthbearer_supported) { - /* AUTH LOGIN */ - - auth_arg_plain = g_strdup (self->username); - auth_arg_plain_len = strlen (self->username); - auth_arg_base64 = g_base64_encode ((guchar *) auth_arg_plain, auth_arg_plain_len); - - request = g_strdup_printf ("AUTH LOGIN %s\r\n", auth_arg_base64); - g_debug ("> AUTH LOGIN ********************"); - if (!g_data_output_stream_put_string (output, request, cancellable, error)) - goto out; - g_clear_pointer (&request, g_free); - - response = goa_utils_data_input_stream_read_line (input, NULL, cancellable, error); - if (response == NULL) - goto out; - g_debug ("< %s", response); - if (smtp_auth_check_not_334_login_password (response, error)) - goto out; - - g_free (auth_arg_plain); - g_free (auth_arg_base64); - - auth_arg_plain = g_strdup (self->password); - auth_arg_plain_len = strlen (self->password); - auth_arg_base64 = g_base64_encode ((guchar *) auth_arg_plain, auth_arg_plain_len); - - request = g_strdup_printf ("%s\r\n", auth_arg_base64); - g_debug ("> ********************"); - if (!g_data_output_stream_put_string (output, request, cancellable, error)) - goto out; - g_clear_pointer (&request, g_free); + ret = goa_smtp_auth_oauthbearer_sync (self, input, output, cancellable, error); + if (ret) + goto out; } - response = goa_utils_data_input_stream_read_line (input, NULL, cancellable, error); - if (response == NULL) - goto out; - g_debug ("< %s", response); - if (smtp_auth_check_not_235 (response, error)) - goto out; - g_clear_pointer (&response, g_free); + if (self->plain_supported) + ret = goa_smtp_auth_plain_sync (self, input, output, cancellable, error); - ret = TRUE; + else + ret = goa_smtp_auth_login_sync (self, input, output, cancellable, error); out: - g_free (auth_arg_base64); - g_free (auth_arg_plain); g_free (response); g_free (request); return ret; diff --git a/src/goabackend/goasmtpauth.h b/src/goamailclient/goasmtpauth.h similarity index 73% rename from src/goabackend/goasmtpauth.h rename to src/goamailclient/goasmtpauth.h index dd70e8600620c9f87b9297952fac6218735c7496..c444b7483be8a35b7438e590c4fe3f2dfde277f4 100644 --- a/src/goabackend/goasmtpauth.h +++ b/src/goamailclient/goasmtpauth.h @@ -16,10 +16,6 @@ * Public License along with this library; if not, see . */ -#if !defined (__GOA_BACKEND_INSIDE_GOA_BACKEND_H__) && !defined (GOA_BACKEND_COMPILATION) -#error "Only can be included directly." -#endif - #ifndef __GOA_SMTP_AUTH_H__ #define __GOA_SMTP_AUTH_H__ @@ -37,8 +33,15 @@ G_DECLARE_FINAL_TYPE (GoaSmtpAuth, goa_smtp_auth, GOA, SMTP_AUTH, GoaMailAuth); GoaMailAuth *goa_smtp_auth_new (const gchar *domain, const gchar *user_name, const gchar *password); -gboolean goa_smtp_auth_is_login (GoaSmtpAuth *self); -gboolean goa_smtp_auth_is_plain (GoaSmtpAuth *self); + +GoaMailAuth *goa_smtp_auth_new_full (const gchar *domain, + const gchar *username, + const gchar *password, + const gchar *access_token); + +gboolean goa_smtp_auth_is_login (GoaSmtpAuth *self); +gboolean goa_smtp_auth_is_plain (GoaSmtpAuth *self); +gboolean goa_smtp_auth_is_oauthbearer (GoaSmtpAuth *self); G_END_DECLS diff --git a/src/goamailclient/meson.build b/src/goamailclient/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..3df8b31bf7e7c9a1acf1b86d53236c53bb927411 --- /dev/null +++ b/src/goamailclient/meson.build @@ -0,0 +1,38 @@ +goamailclient_inc = include_directories('.') + +libgoa_mailclient_sources = files( + 'goaimapauthlogin.c', + 'goamailauth.c', + 'goamailclient.c', + 'goamailutils.c', + 'goasmtpauth.c', +) + +incs = common_incs + +deps = [ + gio_dep, + libgoa_dep, +] + +cflags = [ + '-DG_LOG_DOMAIN="GoaMailClient"', + '-DGOA_API_IS_SUBJECT_TO_CHANGE', + '-DPACKAGE_LOCALE_DIR="@0@"'.format(goa_localedir), + '-DPACKAGE_WEB_EXTENSIONS_DIR="@0@"'.format(join_paths(goa_pkglibdir, 'web-extensions')) +] + +libgoa_mailclient = static_library( + 'goa-mailclient', + libgoa_mailclient_sources, + include_directories: incs, + dependencies: deps, + c_args: cflags, + install: false +) + +libgoa_mailclient_dep = declare_dependency( + include_directories: goamailclient_inc, + dependencies: deps, + link_with: libgoa_mailclient +) diff --git a/src/meson.build b/src/meson.build index a87e282e98b141caf1a30caeec060a93351e5a17..f64c6282c7665a3467040f12eb6c674104ef0334 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,6 +4,10 @@ common_incs = [top_inc, src_inc] subdir('goa') +if enable_goabackend or enable_sendmail + subdir('goamailclient') +endif + if enable_goabackend if enable_fedora or enable_kerberos subdir('goaidentity') @@ -14,3 +18,6 @@ if enable_goabackend endif subdir('examples') +if enable_sendmail + subdir('sendmail') +endif diff --git a/src/sendmail/goa-sendmail.c b/src/sendmail/goa-sendmail.c new file mode 100644 index 0000000000000000000000000000000000000000..43028d72d0d4aff971afd93cbe8d7f1dd1e07156 --- /dev/null +++ b/src/sendmail/goa-sendmail.c @@ -0,0 +1,456 @@ +/* + * Copyright © 2022 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + */ + +#include + +#define GOA_API_IS_SUBJECT_TO_CHANGE +#include +#include "goamailclient.h" +#include "goamailutils.h" +#include "goasmtpauth.h" + +#include +#include + +#define SMTP_PORT_SSL 465 +#define SMTP_PORT_TLS 587 +#define SMTP_PORT_PLAIN 25 + +static gboolean +smtp_check_not_250 (const gchar *response, GError **error) +{ + if (!g_str_has_prefix (response, "250") || strlen (response) < 4) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, /* TODO: more specific */ + "Unexpected response `%s'", + response); + return TRUE; + } + + return FALSE; +} + +static gboolean +smtp_check_not_354 (const gchar *response, GError **error) +{ + if (!g_str_has_prefix (response, "354") || strlen (response) < 4) + { + g_set_error (error, + GOA_ERROR, + GOA_ERROR_FAILED, /* TODO: more specific */ + "Unexpected response `%s'", + response); + return TRUE; + } + + return FALSE; +} + +static gboolean +smtp_mail_from(GoaMailClient *client, + const char *addr, + GError **error) +{ + gboolean ret = FALSE; + GDataInputStream *input = goa_mail_client_get_input(client); + GDataOutputStream *output = goa_mail_client_get_output(client); + gchar *request = NULL; + gchar *response = NULL; + + request = g_strdup_printf ("MAIL FROM:<%s>\r\n", addr); + g_debug ("> %s", request); + if (!g_data_output_stream_put_string (output, request, NULL, error)) + goto out; + + response = goa_utils_data_input_stream_read_line (input, NULL, NULL, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_check_not_250 (response, error)) + goto out; + + ret = TRUE; + +out: + g_free (request); + g_free (response); + return ret; +} + +static gboolean +smtp_rcpt_to(GoaMailClient *client, + const char *addr, + GError **error) +{ + gboolean ret = FALSE; + GDataInputStream *input = goa_mail_client_get_input(client); + GDataOutputStream *output = goa_mail_client_get_output(client); + gchar *request = NULL; + gchar *response = NULL; + + request = g_strdup_printf ("RCPT TO:<%s>\r\n", addr); + g_debug ("> %s", request); + if (!g_data_output_stream_put_string (output, request, NULL, error)) + goto out; + + response = goa_utils_data_input_stream_read_line (input, NULL, NULL, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_check_not_250 (response, error)) + goto out; + + ret = TRUE; + +out: + g_free (request); + g_free (response); + return ret; +} + +#define IN_BUFFER_SIZE 16384 +/* Repeated "\n." can be expanded to "\r\n.." */ +#define OUT_BUFFER_SIZE (IN_BUFFER_SIZE * 2) + +enum { + STATE_BOL, + STATE_ANY, + STATE_CR, +}; + +static gint +smtp_quote(gchar *outbuf, const gchar *buf, guint size, gint *p_state) +{ + guint out = 0; + guint i = 0; + gint state = *p_state; + while (i < size) + { + switch (state) + { + case STATE_CR: + if (buf[i] == '\n') + state = STATE_BOL; + else + { + state = STATE_ANY; + goto any; + } + break; + + case STATE_BOL: + if (buf[i] == '.') + outbuf[out++] = '.'; + state = STATE_ANY; + goto any; + + case STATE_ANY: + any: + if (buf[i] == '\n') + { + outbuf[out++] = '\r'; + state = STATE_BOL; + break; + } + else + { + if (buf[i] == '\r') + state = STATE_CR; + } + break; + + default: + g_assert_not_reached(); + } + + outbuf[out++] = buf[i++]; + } + *p_state = state; + return out; +} + +static gboolean +smtp_send_body(GInputStream *source, + GOutputStream *output, + GError **error) +{ + gboolean ret = FALSE; + gchar *buf = g_malloc(IN_BUFFER_SIZE); + gchar *outbuf = g_malloc(OUT_BUFFER_SIZE); + gint state = STATE_BOL; + + for (;;) + { + gssize n = g_input_stream_read(source, buf, IN_BUFFER_SIZE, NULL, error); + if (n <= 0) + { + ret = (n == 0); + break; + } + + n = smtp_quote(outbuf, buf, n, &state); + g_debug ("> Sending %d bytes", (gint)n); + if (g_output_stream_write(output, outbuf, n, NULL, error) < 0) + break; + } + + g_free (buf); + g_free (outbuf); + return ret; +} + +static gboolean +smtp_data(GoaMailClient *client, + GInputStream *source, + GError **error) +{ + gboolean ret = FALSE; + GDataInputStream *input = goa_mail_client_get_input(client); + GDataOutputStream *output = goa_mail_client_get_output(client); + gchar *response = NULL; + + g_debug ("> DATA"); + if (!g_data_output_stream_put_string (output, "DATA\r\n", NULL, error)) + goto out; + + response = goa_utils_data_input_stream_read_line (input, NULL, NULL, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_check_not_354 (response, error)) + goto out; + + g_clear_pointer(&response, g_free); + + if (!smtp_send_body(source, G_OUTPUT_STREAM(output), error)) + goto out; + + g_debug ("> ."); + if (!g_data_output_stream_put_string (output, ".\r\n", NULL, error)) + goto out; + + response = goa_utils_data_input_stream_read_line (input, NULL, NULL, error); + if (response == NULL) + goto out; + g_debug ("< %s", response); + if (smtp_check_not_250 (response, error)) + goto out; + + ret = TRUE; + +out: + g_free (response); + return ret; +} + +static GoaMailClient * +get_goa_mail_client(const char *desired_sender) +{ + GError *error = NULL; + GoaClient *client; + GList *accounts, *l; + guint i; + + gchar *sender = NULL; + gchar *host_and_port = NULL; + guint port = 0; + GoaTlsType tls_type = GOA_TLS_TYPE_NONE; + gchar *username = NULL; + gchar *access_token = NULL; + gchar *password = NULL; + + GoaMailAuth *mail_auth = NULL; + GoaMailClient *mail_client = NULL; + + client = goa_client_new_sync (NULL, &error); + if (!client) { + g_error ("Could not create GoaClient: %s", error->message); + exit(1); + } + + accounts = goa_client_get_accounts (client); + for (l = accounts, i = 0; l != NULL; l = l->next) + { + GoaAccount *account_obj = goa_object_get_account (GOA_OBJECT (l->data)); + GoaMail *mail_obj = goa_object_get_mail (GOA_OBJECT (l->data)); + GoaOAuth2Based *oauth2_obj = NULL; + GoaPasswordBased *password_obj = NULL; + + if (!mail_obj) + continue; + + if (desired_sender) + { + if (!g_str_equal(desired_sender, goa_account_get_presentation_identity (account_obj))) + continue; + if (i == 1) + g_printerr ("goa-sendmail: warning: More than one account defined for '%s'\n", desired_sender); + } + else + { + if (i > 0) + { + g_printerr ("goa-sendmail: More than one account defined, please use the -f option.\n"); + g_printerr ("goa-sendmail: If you are using git-send-email, you may want to execute\n"); + g_printerr ("goa-sendmail: \"git config git.sendemail.envelopesender auto\"\n"); + exit (1); + } + } + + host_and_port = g_strdup(goa_mail_get_smtp_host (mail_obj)); + if (goa_mail_get_smtp_use_ssl(mail_obj)) + { + port = SMTP_PORT_SSL; + tls_type = GOA_TLS_TYPE_SSL; + } + else if (goa_mail_get_smtp_use_tls(mail_obj)) + { + port = SMTP_PORT_TLS; + tls_type = GOA_TLS_TYPE_STARTTLS; + } + else + { + port = SMTP_PORT_PLAIN; + tls_type = GOA_TLS_TYPE_NONE; + } + + sender = g_strdup(goa_account_get_presentation_identity (account_obj)); + username = g_strdup(goa_mail_get_smtp_user_name (mail_obj)); + + oauth2_obj = goa_object_get_oauth2_based (GOA_OBJECT (l->data)); + if (oauth2_obj && !goa_oauth2_based_call_get_access_token_sync (oauth2_obj, + &access_token, + NULL, + NULL, + NULL)) + { + g_printerr("goa-sendmail: Could not retrieve OAuth2 access token\n"); + exit (1); + } + + if (password_obj && !goa_password_based_call_get_password_sync (password_obj, + "smtp-password", + &password, + NULL, + NULL)) + { + g_printerr("goa-sendmail: Could not retrieve password\n"); + exit (1); + } + + g_clear_object (&mail_obj); + g_clear_object (&oauth2_obj); + g_clear_object (&password_obj); + i++; + } + + g_list_free_full (accounts, (GDestroyNotify) g_object_unref); + g_object_unref(client); + + if (!host_and_port) + { + if (desired_sender) + g_printerr("goa-sendmail: No mail accounts configured for '%s'\n", desired_sender); + else + g_printerr("goa-sendmail: No mail accounts configured\n"); + exit (1); + } + + mail_auth = goa_smtp_auth_new_full("goa-sendmail", username, password, access_token); + mail_client = goa_mail_client_new(); + if (!goa_mail_client_check_sync(mail_client, + host_and_port, tls_type, FALSE, port, + mail_auth, NULL, &error)) + { + g_printerr("goa-sendmail: %s\n", error->message); + exit(1); + } + + if (!smtp_mail_from(mail_client, sender, &error)) + { + g_printerr("goa-sendmail: %s\n", error->message); + exit(1); + } + + g_object_unref(mail_auth); + return mail_client; +} + + +static gboolean until_eof; +static gchar *desired_sender; +static gchar **recipients; +static const GOptionEntry entries[] = { + { + "sender", 'f', 0, G_OPTION_ARG_STRING, &desired_sender, + "The address from which mail will be sent", + "ADDRESS" + }, + { + "read-until-eof", 'i', 0, G_OPTION_ARG_NONE, &until_eof, + "This option is ignored", + "ADDRESS" + }, + { + G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &recipients, + "The addresses to which mail will be sent", + "ADDRESSES", + }, + { NULL } +}; + +int +main (int argc, char **argv) +{ + GError *error = NULL; + char **p_address; + GoaMailClient *mail_client; + GInputStream *source; + GOptionContext *context; + + setlocale (LC_ALL, ""); + + context = g_option_context_new("- sendmail interface to GNOME Online Accounts"); + g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE); + if (!g_option_context_parse(context, &argc, &argv, &error)) + goto err; + + if (!recipients) + { + g_printerr("goa-sendmail: No recipients supplied\n"); + exit(0); + } + + mail_client = get_goa_mail_client(desired_sender); + for (p_address = recipients; *p_address; p_address++) + if (!smtp_rcpt_to(mail_client, *p_address, &error)) + goto err; + + source = g_unix_input_stream_new(0, FALSE); + if (!smtp_data(mail_client, source, &error)) + goto err; + g_object_unref(source); + + g_object_unref(mail_client); + return 0; + +err: + g_printerr("goa-sendmail: %s\n", error->message); + exit(1); +} diff --git a/src/sendmail/meson.build b/src/sendmail/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..f2250cc5ae1005b1d811ab79c302e80ced2cc52f --- /dev/null +++ b/src/sendmail/meson.build @@ -0,0 +1,19 @@ +programs = [ + # program name, deps + ['goa-sendmail', [libgoa_dep, gio_unix_dep, libgoa_mailclient_dep]], +] + +cflags = [ + '-DG_LOG_DOMAIN="GoaMailClient"', +] + +foreach program: programs + executable( + program[0], + program[0] + '.c', + include_directories: common_incs, + dependencies: program[1], + c_args: cflags, + install: true + ) +endforeach