/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright © 2011 – 2017 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 #include #include "goasmtpauth.h" #include "goautils.h" /* * SECTION:goasmtpauth * @title: GoaSmtpAuth * @short_description: Authentication mechanisms for SMTP * * #GoaSmtpAuth implements the PLAIN and LOGIN * SASL mechanisms (e.g. using usernames / passwords) for SMTP. */ /* * GoaSmtpAuth: * * The #GoaSmtpAuth structure contains only private data and should * only be accessed using the provided API. */ struct _GoaSmtpAuth { GoaMailAuth parent_instance; gboolean auth_supported; gboolean greeting_absent; gboolean login_supported; gboolean plain_supported; gchar *domain; gchar *username; gchar *password; }; typedef struct { GoaMailAuthClass parent_class; } GoaSmtpAuthClass; enum { PROP_0, PROP_DOMAIN, PROP_USERNAME, PROP_PASSWORD }; static gboolean goa_smtp_auth_is_needed (GoaMailAuth *auth); static gboolean goa_smtp_auth_run_sync (GoaMailAuth *auth, GCancellable *cancellable, GError **error); static gboolean goa_smtp_auth_starttls_sync (GoaMailAuth *auth, GCancellable *cancellable, GError **error); G_DEFINE_TYPE (GoaSmtpAuth, goa_smtp_auth, GOA_TYPE_MAIL_AUTH); /* ---------------------------------------------------------------------------------------------------- */ static gboolean smtp_auth_check_not_220 (const gchar *response, GError **error) { if (!g_str_has_prefix (response, "220")) { g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, /* TODO: more specific */ "Unexpected response `%s'", response); return TRUE; } return FALSE; } static gboolean smtp_auth_check_not_235 (const gchar *response, GError **error) { if (!g_str_has_prefix (response, "235")) { g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, /* TODO: more specific */ _("Authentication failed")); return TRUE; } return FALSE; } static gboolean smtp_auth_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_auth_check_not_334_login_password (const gchar *response, GError **error) { if (!g_str_has_prefix (response, "334 UGFzc3dvcmQ6")) { g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, /* TODO: more specific */ "Unexpected response `%s'", response); return TRUE; } return FALSE; } static gboolean smtp_auth_check_421 (const gchar *response, GError **error) { if (g_str_has_prefix (response, "421")) { g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, /* TODO: more specific */ _("Service not available")); return TRUE; } return FALSE; } static gboolean smtp_auth_check_454 (const gchar *response, GError **error) { if (g_str_has_prefix (response, "454")) { g_set_error (error, GOA_ERROR, GOA_ERROR_FAILED, /* TODO: more specific */ _("TLS not available")); return TRUE; } return FALSE; } /* ---------------------------------------------------------------------------------------------------- */ static gboolean smtp_auth_check_greeting (GDataInputStream *input, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; gchar *response = NULL; greeting_again: 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_421 (response, error)) goto out; if (smtp_auth_check_not_220 (response, error)) goto out; if (response[3] == '-') { g_clear_pointer (&response, g_free); goto greeting_again; } ret = TRUE; out: g_free (response); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static void goa_smtp_auth_finalize (GObject *object) { GoaSmtpAuth *self = GOA_SMTP_AUTH (object); g_free (self->domain); g_free (self->username); g_free (self->password); G_OBJECT_CLASS (goa_smtp_auth_parent_class)->finalize (object); } static void goa_smtp_auth_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GoaSmtpAuth *self = GOA_SMTP_AUTH (object); switch (prop_id) { case PROP_DOMAIN: g_value_set_string (value, self->domain); break; case PROP_USERNAME: g_value_set_string (value, self->username); break; case PROP_PASSWORD: g_value_set_string (value, self->password); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void goa_smtp_auth_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GoaSmtpAuth *self = GOA_SMTP_AUTH (object); switch (prop_id) { case PROP_DOMAIN: self->domain = g_value_dup_string (value); break; case PROP_USERNAME: self->username = g_value_dup_string (value); break; case PROP_PASSWORD: self->password = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /* ---------------------------------------------------------------------------------------------------- */ static void goa_smtp_auth_init (GoaSmtpAuth *self) { } static void goa_smtp_auth_class_init (GoaSmtpAuthClass *klass) { GObjectClass *gobject_class; GoaMailAuthClass *auth_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = goa_smtp_auth_finalize; gobject_class->get_property = goa_smtp_auth_get_property; gobject_class->set_property = goa_smtp_auth_set_property; auth_class = GOA_MAIL_AUTH_CLASS (klass); auth_class->is_needed = goa_smtp_auth_is_needed; auth_class->run_sync = goa_smtp_auth_run_sync; auth_class->starttls_sync = goa_smtp_auth_starttls_sync; /** * GoaSmtpAuth:domain: * * The domain. */ g_object_class_install_property (gobject_class, PROP_DOMAIN, g_param_spec_string ("domain", "domain", "domain", NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GoaSmtpAuth:user-name: * * The user name. */ g_object_class_install_property (gobject_class, PROP_USERNAME, g_param_spec_string ("user-name", "user-name", "user-name", NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); /** * GoaSmtpAuth:password: * * The password. */ g_object_class_install_property (gobject_class, PROP_PASSWORD, g_param_spec_string ("password", "password", "password", NULL, G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); } /* ---------------------------------------------------------------------------------------------------- */ /** * goa_smtp_auth_new: * @domain: The domain to use. * @username: The user name to use. * @password: The password to use. * * Creates a new #GoaMailAuth to be used for username/password * authentication using LOGIN or PLAIN over SMTP. * * Returns: (type GoaSmtpAuth): A #GoaSmtpAuth. Free with * g_object_unref(). */ GoaMailAuth * goa_smtp_auth_new (const gchar *domain, const gchar *username, const gchar *password) { 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); return GOA_MAIL_AUTH (g_object_new (GOA_TYPE_SMTP_AUTH, "domain", domain, "user-name", username, "password", password, NULL)); } /* ---------------------------------------------------------------------------------------------------- */ gboolean goa_smtp_auth_is_login (GoaSmtpAuth *self) { return self->login_supported; } gboolean goa_smtp_auth_is_plain (GoaSmtpAuth *self) { return self->plain_supported; } /* ---------------------------------------------------------------------------------------------------- */ static gboolean goa_smtp_auth_is_needed (GoaMailAuth *auth) { GoaSmtpAuth *self = GOA_SMTP_AUTH (auth); return self->auth_supported; } /* ---------------------------------------------------------------------------------------------------- */ static gboolean goa_smtp_auth_run_sync (GoaMailAuth *auth, GCancellable *cancellable, GError **error) { GoaSmtpAuth *self = GOA_SMTP_AUTH (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); /* Check the greeting, if there is one */ if (!self->greeting_absent) { if (!smtp_auth_check_greeting (input, cancellable, error)) goto out; } /* Send EHLO */ request = g_strdup_printf ("EHLO %s\r\n", self->domain); g_debug ("> %s", request); if (!g_data_output_stream_put_string (output, request, cancellable, error)) goto out; g_clear_pointer (&request, g_free); /* Check which SASL mechanisms are supported */ ehlo_again: 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_421 (response, error)) goto out; if (smtp_auth_check_not_250 (response, error)) goto out; if (g_str_has_prefix (response + 4, "AUTH")) { self->auth_supported = TRUE; if (strstr (response, "PLAIN") != NULL) self->plain_supported = TRUE; else if (strstr (response, "LOGIN") != NULL) self->login_supported = TRUE; } if (response[3] == '-') { g_free (response); goto ehlo_again; } else if (!self->auth_supported) { ret = TRUE; goto out; } else if (!self->login_supported && !self->plain_supported) { g_set_error (error, GOA_ERROR, GOA_ERROR_NOT_SUPPORTED, _("Unknown authentication mechanism")); goto out; } g_clear_pointer (&response, g_free); /* 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 { /* 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); } 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); ret = TRUE; out: g_free (auth_arg_base64); g_free (auth_arg_plain); g_free (response); g_free (request); return ret; } /* ---------------------------------------------------------------------------------------------------- */ static gboolean goa_smtp_auth_starttls_sync (GoaMailAuth *auth, GCancellable *cancellable, GError **error) { GoaSmtpAuth *self = GOA_SMTP_AUTH (auth); GDataInputStream *input; GDataOutputStream *output; gboolean ret = FALSE; gboolean starttls_supported = FALSE; gchar *request = NULL; gchar *response = NULL; input = goa_mail_auth_get_input (auth); output = goa_mail_auth_get_output (auth); /* Check the greeting */ if (!smtp_auth_check_greeting (input, cancellable, error)) goto out; /* Send EHLO */ request = g_strdup_printf ("EHLO %s\r\n", self->domain); g_debug ("> %s", request); if (!g_data_output_stream_put_string (output, request, cancellable, error)) goto out; g_clear_pointer (&request, g_free); /* Check if STARTTLS is supported or not */ ehlo_again: 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_421 (response, error)) goto out; if (smtp_auth_check_not_250 (response, error)) goto out; if (g_str_has_prefix (response + 4, "STARTTLS")) starttls_supported = TRUE; if (response[3] == '-') { g_free (response); goto ehlo_again; } else if (!starttls_supported) { g_set_error (error, GOA_ERROR, GOA_ERROR_NOT_SUPPORTED, _("Server does not support STARTTLS")); goto out; } g_clear_pointer (&response, g_free); /* Send STARTTLS */ request = g_strdup ("STARTTLS\r\n"); g_debug ("> %s", request); 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_454 (response, error)) goto out; if (smtp_auth_check_not_220 (response, error)) goto out; g_clear_pointer (&response, g_free); /* There won't be a greeting after this */ self->greeting_absent = TRUE; ret = TRUE; out: g_free (response); g_free (request); return ret; }