[balsa/oauth2-support: 27/30] improve authentication mode selection
- From: Albrecht Dreß <albrecht src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa/oauth2-support: 27/30] improve authentication mode selection
- Date: Wed, 9 Dec 2020 20:07:52 +0000 (UTC)
commit 1456ea8dd4bcb65096b16371e776e3c1c4c77457
Author: Albrecht Dreß <albrecht dress arcor de>
Date: Mon Aug 31 21:54:18 2020 +0200
improve authentication mode selection
Let the user choose between OAuth2, Kerberos, user/password and
anonymous authentication instead of auto-selecting the “best” method.
Rationale: even if OAuth2/Kerberos is supported, the user may prefer a
different method. To support the user in configuring the account,
implement server probing.
Note 1: OAuth2 is yet untested!
Note 2: Auth mode is stored differently in the user config file!
Backend changes:
- libbalsa/imap:
- imap-auth.c: rework authentication
- imap-handle.[ch], imap_private.h, imap_tst.c: new method
imap_handle_set_auth_mode(), detect XOAUTH2 capability, implement probe
function
- libnetclient:
- net-client.h, net-client-pop.[ch], net-client-smtp.[ch]: replace
net_client_*_allow_auth() by net_client_*_set_auth_mode(), detect
XOAUTH2 capability, rework authentication including OAuth2, implement
probe functions
- net-client-utils.[ch]: add net_client_host_reachable() used for
probing, add net_client_auth_oauth2_calc(), fix GSSAPI paranoia check
- libbalsa:
- imap-server.c, mailbox_pop3.c, send.c: use new auth configuration
methods
- server.[ch]: save/restore/set/get auth mode as config item AuthMode,
remove deprecated items from config file
User frontend changes:
- libbalsa/server-config.c: add a button for probing a server, add combo
for selecting the auth mode
Signed-off-by: Albrecht Dreß <albrecht dress arcor de>
libbalsa/imap-server.c | 2 +-
libbalsa/imap/imap-auth.c | 37 ++++---
libbalsa/imap/imap-handle.c | 135 +++++++++++++++++++++--
libbalsa/imap/imap-handle.h | 6 +-
libbalsa/imap/imap_private.h | 3 +-
libbalsa/imap/imap_tst.c | 2 +-
libbalsa/mailbox_pop3.c | 11 +-
libbalsa/send.c | 1 +
libbalsa/server-config.c | 220 ++++++++++++++++++++++++++++++-------
libbalsa/server.c | 29 +++--
libbalsa/server.h | 4 +-
libnetclient/net-client-pop.c | 228 ++++++++++++++++++++++++++++++++++-----
libnetclient/net-client-pop.h | 69 +++++-------
libnetclient/net-client-smtp.c | 232 ++++++++++++++++++++++++++++++++++------
libnetclient/net-client-smtp.h | 58 +++++-----
libnetclient/net-client-utils.c | 38 ++++++-
libnetclient/net-client-utils.h | 40 ++++++-
libnetclient/net-client.h | 18 +++-
18 files changed, 907 insertions(+), 226 deletions(-)
---
diff --git a/libbalsa/imap-server.c b/libbalsa/imap-server.c
index 29dad11d3..56025e92e 100644
--- a/libbalsa/imap-server.c
+++ b/libbalsa/imap-server.c
@@ -247,7 +247,7 @@ lb_imap_server_info_new(LibBalsaServer *server)
imap_handle_set_authcb(handle, G_CALLBACK(libbalsa_server_get_auth), server);
imap_handle_set_certcb(handle, G_CALLBACK(libbalsa_server_check_cert));
imap_handle_set_tls_mode(handle, libbalsa_server_get_security(server));
- imap_handle_set_option(handle, IMAP_OPT_ANONYMOUS, libbalsa_server_get_try_anonymous(server));
+ imap_handle_set_auth_mode(handle, libbalsa_server_get_auth_mode(server));
imap_handle_set_option(handle, IMAP_OPT_CLIENT_SORT, TRUE);
/* binary fetches change encoding and the checksums, and
signatures, disable them if we ever consider verifying message
diff --git a/libbalsa/imap/imap-auth.c b/libbalsa/imap/imap-auth.c
index d94ca3dd4..94258a5d4 100644
--- a/libbalsa/imap/imap-auth.c
+++ b/libbalsa/imap/imap-auth.c
@@ -37,11 +37,8 @@ static ImapResult imap_auth_login(ImapMboxHandle* handle);
typedef ImapResult (*ImapAuthenticator)(ImapMboxHandle* handle);
-/* ordered from strongest to weakest. Auth anonymous does not really
- * belong here, does it? */
+/* User name/password methods, ordered from strongest to weakest. */
static ImapAuthenticator imap_authenticators_arr[] = {
- imap_auth_anonymous, /* will be tried only if enabled */
- imap_auth_gssapi,
imap_auth_cram,
imap_auth_plain,
imap_auth_login, /* login is deprecated */
@@ -59,16 +56,28 @@ imap_authenticate(ImapMboxHandle* handle)
if (imap_mbox_is_authenticated(handle) || imap_mbox_is_selected(handle))
return IMAP_SUCCESS;
- for(authenticator = imap_authenticators_arr;
- *authenticator; authenticator++) {
- if ((r = (*authenticator)(handle))
- != IMAP_AUTH_UNAVAIL) {
- if (r == IMAP_SUCCESS)
- imap_mbox_handle_set_state(handle, IMHS_AUTHENTICATED);
- return r;
- }
+ if ((handle->auth_mode & NET_CLIENT_AUTH_OAUTH2) != 0U) {
+ /* FIXME!! */
+ }
+
+ if ((r != IMAP_SUCCESS) && (handle->auth_mode & NET_CLIENT_AUTH_KERBEROS) != 0U) {
+ r = imap_auth_gssapi(handle);
+ }
+
+ if ((r != IMAP_SUCCESS) && (handle->auth_mode & NET_CLIENT_AUTH_ANONYMOUS) != 0U) {
+ r = imap_auth_anonymous(handle);
+ }
+
+ for (authenticator = imap_authenticators_arr; (r != IMAP_SUCCESS) && *authenticator; authenticator++) {
+ r = (*authenticator)(handle);
+ if (r == IMAP_SUCCESS) {
+ imap_mbox_handle_set_state(handle, IMHS_AUTHENTICATED);
+ }
+ }
+
+ if (r != IMAP_SUCCESS) {
+ imap_mbox_handle_set_msg(handle, _("No way to authenticate is known"));
}
- imap_mbox_handle_set_msg(handle, _("No way to authenticate is known"));
return r;
}
@@ -223,8 +232,6 @@ getmsg_anonymous(ImapMboxHandle *h, char **retmsg, int *retmsglen)
static ImapResult
imap_auth_anonymous(ImapMboxHandle* handle)
{
- if(!handle->enable_anonymous)
- return IMAP_AUTH_UNAVAIL;
return imap_auth_sasl(handle, IMCAP_AANONYMOUS, "AUTHENTICATE ANONYMOUS",
getmsg_anonymous);
}
diff --git a/libbalsa/imap/imap-handle.c b/libbalsa/imap/imap-handle.c
index 625a121eb..1ae777482 100644
--- a/libbalsa/imap/imap-handle.c
+++ b/libbalsa/imap/imap-handle.c
@@ -86,6 +86,7 @@ imap_mbox_handle_init(ImapMboxHandle *handle)
NULL, NULL);
handle->state = IMHS_DISCONNECTED;
handle->tls_mode = NET_CLIENT_CRYPT_STARTTLS;
+ handle->auth_mode = NET_CLIENT_AUTH_USER_PASS | NET_CLIENT_AUTH_KERBEROS | NET_CLIENT_AUTH_OAUTH2;
handle->idle_state = IDLE_INACTIVE;
handle->enable_idle = 1;
@@ -172,7 +173,6 @@ void
imap_handle_set_option(ImapMboxHandle *h, ImapOption opt, gboolean state)
{
switch(opt) {
- case IMAP_OPT_ANONYMOUS: h->enable_anonymous = !!state; break;
case IMAP_OPT_BINARY: h->enable_binary = !!state; break;
case IMAP_OPT_CLIENT_SORT: h->enable_client_sort = !!state; break;
case IMAP_OPT_COMPRESS: h->enable_compress = !!state; break;
@@ -612,15 +612,26 @@ imap_handle_force_disconnect(ImapMboxHandle *h)
}
NetClientCryptMode
-imap_handle_set_tls_mode(ImapMboxHandle* r, NetClientCryptMode state)
+imap_handle_set_tls_mode(ImapMboxHandle* h, NetClientCryptMode state)
{
NetClientCryptMode res;
- g_return_val_if_fail(r,0);
- res = r->tls_mode;
- r->tls_mode = state;
+ g_return_val_if_fail(h,0);
+ res = h->tls_mode;
+ h->tls_mode = state;
return res;
}
+NetClientAuthMode
+imap_handle_set_auth_mode(ImapMboxHandle *h, NetClientAuthMode mode)
+{
+ NetClientAuthMode res;
+
+ g_return_val_if_fail(h != NULL, 0U);
+ res = h->auth_mode;
+ h->auth_mode = mode;
+ return res;
+}
+
const char* imap_msg_flags[6] = {
"seen", "answered", "flagged", "deleted", "draft", "recent"
};
@@ -2096,7 +2107,7 @@ ir_capability_data(ImapMboxHandle *handle)
/* ordered identically as ImapCapability constants */
static const char* capabilities[] = {
"IMAP4", "IMAP4rev1", "STATUS",
- "AUTH=ANONYMOUS", "AUTH=CRAM-MD5", "AUTH=GSSAPI", "AUTH=PLAIN",
+ "AUTH=ANONYMOUS", "AUTH=CRAM-MD5", "AUTH=GSSAPI", "AUTH=PLAIN", "AUTH=XOAUTH2",
"ACL", "RIGHTS=", "BINARY", "CHILDREN",
"COMPRESS=DEFLATE",
"ESEARCH", "IDLE", "LITERAL+",
@@ -4385,3 +4396,115 @@ mbox_view_get_str(MboxView *mv)
{
return mv->filter_str ? mv->filter_str : "";
}
+
+gboolean
+imap_server_probe(const gchar *host, guint timeout_secs, NetClientProbeResult *result, GCallback cert_cb,
GError **error)
+{
+ guint16 probe_ports[] = {993U, 143U, 0U}; /* imaps, imap */
+ gchar *host_only;
+ gchar *colon;
+ gboolean retval = FALSE;
+ gint check_id;
+
+ /* paranoia check */
+ g_return_val_if_fail((host != NULL) && (result != NULL), FALSE);
+
+ host_only = g_strdup(host);
+ colon = strchr(host_only, ':');
+ if (colon != NULL) {
+ colon[0] = '\0';
+ }
+
+ if (!net_client_host_reachable(host_only, error)) {
+ g_free(host_only);
+ return FALSE;
+ }
+
+ for (check_id = 0; !retval && (probe_ports[check_id] > 0U); check_id++) {
+ ImapMboxHandle *handle;
+
+ g_debug("%s: probing %s:%u…", __func__, host_only, probe_ports[check_id]);
+ handle = imap_mbox_handle_new();
+ handle->sio = net_client_siobuf_new(host_only, probe_ports[check_id]);
+ net_client_set_timeout(NET_CLIENT(handle->sio), timeout_secs);
+ if (net_client_connect(NET_CLIENT(handle->sio), NULL)) {
+ gboolean this_success;
+ ImapResponse resp;
+ gboolean can_starttls = FALSE;
+
+ if (cert_cb != NULL) {
+ g_signal_connect(handle->sio, "cert-check", cert_cb, handle->sio);
+ }
+ if (check_id == 0) { /* imaps */
+ this_success = net_client_start_tls(NET_CLIENT(handle->sio), NULL);
+ } else {
+ this_success = TRUE;
+ }
+
+ /* get the server greeting and initialise the capabilities */
+ if (this_success) {
+ handle->state = IMHS_CONNECTED;
+ resp = imap_cmd_step(handle, 0);
+ if (resp != IMR_UNTAGGED) {
+ imap_handle_disconnect(handle);
+ this_success = FALSE;
+ } else {
+ /* fetch capabilities */
+ can_starttls = imap_mbox_handle_can_do(handle, IMCAP_STARTTLS) != 0;
+ }
+ }
+
+ /* try to perform STARTTLS if supported */
+ if (this_success && can_starttls) {
+ can_starttls = FALSE;
+ resp = imap_cmd_exec(handle, "StartTLS");
+ if (resp == IMR_OK) {
+ if (net_client_start_tls(NET_CLIENT(handle->sio), NULL)) {
+ handle->has_capabilities = 0;
+ can_starttls = TRUE;
+ }
+ }
+ }
+
+ /* evaluate on success */
+ if (this_success) {
+ result->port = probe_ports[check_id];
+
+ if (check_id == 0) {
+ result->crypt_mode = NET_CLIENT_CRYPT_ENCRYPTED;
+ } else if (can_starttls) {
+ result->crypt_mode = NET_CLIENT_CRYPT_STARTTLS;
+ } else {
+ result->crypt_mode = NET_CLIENT_CRYPT_NONE;
+ }
+
+ result->auth_mode = 0U;
+ if (imap_mbox_handle_can_do(handle, IMCAP_AANONYMOUS) != 0) {
+ result->auth_mode |= NET_CLIENT_AUTH_ANONYMOUS;
+ }
+ if ((imap_mbox_handle_can_do(handle, IMCAP_ACRAM_MD5) != 0) ||
+ (imap_mbox_handle_can_do(handle, IMCAP_APLAIN) != 0)) {
+ result->auth_mode |= NET_CLIENT_AUTH_USER_PASS;
+ }
+ if (imap_mbox_handle_can_do(handle, IMCAP_AGSSAPI) != 0) {
+ result->auth_mode |= NET_CLIENT_AUTH_KERBEROS;
+ }
+ if (imap_mbox_handle_can_do(handle, IMCAP_AOAUTH2) != 0) {
+ result->auth_mode |= NET_CLIENT_AUTH_OAUTH2;
+ }
+ retval = TRUE;
+ }
+ }
+
+ g_object_unref(handle);
+ }
+
+ if (!retval) {
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, NET_CLIENT_PROBE_FAILED,
+ _("the server %s does not offer the IMAP service at port 993 or 143"), host_only);
+ }
+
+ g_free(host_only);
+
+ return retval;
+}
diff --git a/libbalsa/imap/imap-handle.h b/libbalsa/imap/imap-handle.h
index fad466a66..d79d54841 100644
--- a/libbalsa/imap/imap-handle.h
+++ b/libbalsa/imap/imap-handle.h
@@ -20,6 +20,7 @@
#include <glib.h>
#include "net-client.h"
+#include "net-client-utils.h"
#include "libimap.h"
typedef enum {
@@ -61,6 +62,7 @@ typedef enum
IMCAP_ACRAM_MD5, /* RFC 2195: CRAM-MD5 authentication */
IMCAP_AGSSAPI, /* RFC 1731: GSSAPI authentication */
IMCAP_APLAIN, /* RFC 2595: */
+ IMCAP_AOAUTH2, /* RFC 6749: OAUTH2 authentication */
IMCAP_ACL, /* RFC 2086: IMAP4 ACL extension */
IMCAP_RIGHTS, /* RFC 4314: IMAP4 RIGHTS= extension */
IMCAP_BINARY, /* RFC 3516 */
@@ -88,7 +90,6 @@ typedef enum
} ImapCapability;
typedef enum {
- IMAP_OPT_ANONYMOUS, /**< try anonymous authentication */
IMAP_OPT_CLIENT_SORT, /**< allow client-side sorting */
IMAP_OPT_BINARY, /**< enable binary=no-transfer-encoding msg transfer */
IMAP_OPT_IDLE, /**< enable IDLE */
@@ -135,6 +136,7 @@ ImapResult imap_mbox_handle_reconnect(ImapMboxHandle* r,
void imap_handle_force_disconnect(ImapMboxHandle *h);
NetClientCryptMode imap_handle_set_tls_mode(ImapMboxHandle *h, NetClientCryptMode option);
+NetClientAuthMode imap_handle_set_auth_mode(ImapMboxHandle *h, NetClientAuthMode mode);
/* int below is a boolean */
int imap_mbox_handle_can_do(ImapMboxHandle* handle, ImapCapability cap);
@@ -214,4 +216,6 @@ const char *mbox_view_get_str(MboxView *mv);
/* ================ END OF MBOX_VIEW FUNCTIONS ========================= */
+gboolean imap_server_probe(const gchar *host, guint timeout_secs, NetClientProbeResult *result, GCallback
cert_cb, GError **error);
+
#endif
diff --git a/libbalsa/imap/imap_private.h b/libbalsa/imap/imap_private.h
index 323d4dc75..11af1f8ca 100644
--- a/libbalsa/imap/imap_private.h
+++ b/libbalsa/imap/imap_private.h
@@ -115,6 +115,8 @@ struct _ImapMboxHandle {
guint idle_enable_id; /* callback to issue IDLE after a period of
inactivity */
NetClientCryptMode tls_mode; /* disabled, enabled, required */
+ NetClientAuthMode auth_mode;
+
enum { IDLE_INACTIVE, IDLE_RESPONSE_PENDING, IDLE_ACTIVE }
idle_state; /* IDLE State? */
unsigned op_cancelled:1; /* last op timed out and was cancelled by user */
@@ -122,7 +124,6 @@ struct _ImapMboxHandle {
unsigned can_fetch_body:1; /* set for servers that always respond
* correctly to FETCH x BODY[y]
* requests. */
- unsigned enable_anonymous:1; /**< try anonymous if possible */
unsigned enable_binary:1; /**< enable binary extension */
unsigned enable_client_sort:1; /**< client side sorting allowed */
unsigned enable_compress:1; /**< enable compress extension */
diff --git a/libbalsa/imap/imap_tst.c b/libbalsa/imap/imap_tst.c
index a180ae36e..7174f066f 100644
--- a/libbalsa/imap/imap_tst.c
+++ b/libbalsa/imap/imap_tst.c
@@ -124,7 +124,7 @@ get_handle(const char *host)
imap_handle_set_tls_mode(h, TestContext.tls_mode);
if(TestContext.anonymous)
- imap_handle_set_option(h, IMAP_OPT_ANONYMOUS, TRUE);
+ imap_handle_set_auth_mode(h, NET_CLIENT_AUTH_ANONYMOUS);
if(TestContext.compress)
imap_handle_set_option(h, IMAP_OPT_COMPRESS, TRUE);
diff --git a/libbalsa/mailbox_pop3.c b/libbalsa/mailbox_pop3.c
index 97f347fa0..f47e342ab 100644
--- a/libbalsa/mailbox_pop3.c
+++ b/libbalsa/mailbox_pop3.c
@@ -460,7 +460,6 @@ libbalsa_mailbox_pop3_startup(LibBalsaServer *server,
const gchar *host;
NetClientPop *pop;
GError *error = NULL;
- guint allow_auth;
/* create the mailbox connection */
security = libbalsa_server_get_security(server);
@@ -474,14 +473,8 @@ libbalsa_mailbox_pop3_startup(LibBalsaServer *server,
return NULL;
}
- /* configure the mailbox connection; allow all (including plain text) auth methods even for
unencrypted connections so using
- * e.g. popfile on localhost is possible, i.e. the user is responsible for choosing a proper security
mode */
- allow_auth = NET_CLIENT_POP_AUTH_ALL;
- if (mailbox_pop3->disable_apop) {
- allow_auth &= ~NET_CLIENT_POP_AUTH_APOP;
- }
- net_client_pop_allow_auth(pop, TRUE, allow_auth);
- net_client_pop_allow_auth(pop, FALSE, allow_auth);
+ /* configure the mailbox connection; allow configured auth methods */
+ net_client_pop_set_auth_mode(pop, libbalsa_server_get_auth_mode(server), mailbox_pop3->disable_apop);
net_client_set_timeout(NET_CLIENT(pop), 60U);
/* load client certificate if configured */
diff --git a/libbalsa/send.c b/libbalsa/send.c
index c456f51dc..01a322f97 100644
--- a/libbalsa/send.c
+++ b/libbalsa/send.c
@@ -763,6 +763,7 @@ lbs_process_queue_init_session(LibBalsaServer* server)
// FIXME - submission (587) is the standard, but most isp's use 25...
session = net_client_smtp_new(host, 587U, security);
}
+ net_client_smtp_set_auth_mode(session, libbalsa_server_get_auth_mode(server));
/* connect signals */
g_signal_connect(session, "cert-check", G_CALLBACK(libbalsa_server_check_cert), session);
diff --git a/libbalsa/server-config.c b/libbalsa/server-config.c
index 37360213d..1bf2f3659 100644
--- a/libbalsa/server-config.c
+++ b/libbalsa/server-config.c
@@ -24,6 +24,9 @@
#include <glib/gi18n.h>
#include "misc.h"
+#include "net-client-smtp.h"
+#include "net-client-pop.h"
+#include "imap-handle.h"
#include "server-config.h"
@@ -35,8 +38,9 @@ struct _LibBalsaServerCfg {
guint basic_rows; /* count of rows */
GtkWidget *name; /* descriptive name */
GtkWidget *host_port; /* host and optionally port */
+ GtkWidget *probe_host; /* button for probing the host */
GtkWidget *security; /* security (SSL/TLS/...) */
- GtkWidget *require_auth; /* require authentication */
+ GtkWidget *auth_mode; /* authentication mode */
GtkWidget *username; /* user name for authentication */
GtkWidget *password; /* password for authentication */
GtkWidget *remember_pass; /* remember password */
@@ -49,6 +53,9 @@ struct _LibBalsaServerCfg {
GtkWidget *cert_pass; /* client certificate pass phrase */
GtkWidget *remember_cert_pass; /* remember certificate pass phrase */
+ /* function for probing a server's capabilities */
+ gboolean (*probe_fn)(const gchar *, guint, NetClientProbeResult *, GCallback, GError **);
+
gboolean cfg_valid; /* whether the config options are valid (parent may
enable OK) */
};
@@ -57,14 +64,16 @@ G_DEFINE_TYPE(LibBalsaServerCfg, libbalsa_server_cfg, GTK_TYPE_NOTEBOOK)
static GtkWidget *server_cfg_add_entry(GtkWidget *grid, guint row, const gchar *label, const gchar *value,
GCallback callback,
- gpointer cb_data)
+ GtkWidget *button, gpointer cb_data)
G_GNUC_WARN_UNUSED_RESULT;
static GtkWidget *server_cfg_add_check(GtkWidget *grid, guint row, const gchar *label, gboolean value,
GCallback callback,
gpointer cb_data)
G_GNUC_WARN_UNUSED_RESULT;
static void server_cfg_add_widget(GtkWidget *grid, guint row, const gchar *text, GtkWidget *widget);
static GtkWidget *server_cfg_security_widget(LibBalsaServer *server);
+static GtkWidget *server_cfg_auth_widget(LibBalsaServer *server);
static void on_server_cfg_changed(GtkWidget *widget, LibBalsaServerCfg *server_cfg);
+static void on_server_probe(GtkWidget *widget, LibBalsaServerCfg *server_cfg);
static guint changed_sig;
@@ -87,8 +96,8 @@ LibBalsaServerCfg *
libbalsa_server_cfg_new(LibBalsaServer *server, const gchar *name)
{
LibBalsaServerCfg *server_cfg;
- const gchar *protocol;
- const gchar *cert_file;
+ const gchar *protocol;
+ const gchar *cert_file;
g_return_val_if_fail(LIBBALSA_IS_SERVER(server), NULL);
@@ -104,35 +113,46 @@ libbalsa_server_cfg_new(LibBalsaServer *server, const gchar *name)
/* server descriptive name */
server_cfg->name = server_cfg_add_entry(server_cfg->basic_grid, server_cfg->basic_rows++,
_("_Descriptive Name:"), name,
- G_CALLBACK(on_server_cfg_changed), server_cfg);
+ G_CALLBACK(on_server_cfg_changed), NULL, server_cfg);
+
+ /* probe button */
+ server_cfg->probe_host = gtk_button_new_from_icon_name("system-run", GTK_ICON_SIZE_MENU);
+ gtk_button_set_label(GTK_BUTTON(server_cfg->probe_host), _("probe…"));
+ gtk_button_set_always_show_image(GTK_BUTTON(server_cfg->probe_host), TRUE);
+ g_signal_connect(server_cfg->probe_host, "clicked", G_CALLBACK(on_server_probe), server_cfg);
+ protocol = libbalsa_server_get_protocol(server);
+ if (strcmp(protocol, "smtp") == 0) {
+ server_cfg->probe_fn = net_client_smtp_probe;
+ } else if (strcmp(protocol, "pop3") == 0) {
+ server_cfg->probe_fn = net_client_pop_probe;
+ } else if (strcmp(protocol, "imap") == 0) {
+ server_cfg->probe_fn = imap_server_probe;
+ } else {
+ g_assert_not_reached();
+ }
/* host and port */
server_cfg->host_port = server_cfg_add_entry(server_cfg->basic_grid, server_cfg->basic_rows++,
_("_Server:"),
- libbalsa_server_get_host(server),
- G_CALLBACK(on_server_cfg_changed), server_cfg);
+ libbalsa_server_get_host(server), G_CALLBACK(on_server_cfg_changed), server_cfg->probe_host,
server_cfg);
/* security settings */
server_cfg->security = server_cfg_security_widget(server);
server_cfg_add_widget(server_cfg->basic_grid, server_cfg->basic_rows++, _("Se_curity:"),
server_cfg->security);
g_signal_connect(server_cfg->security, "changed", G_CALLBACK(on_server_cfg_changed), server_cfg);
- /* check box for authentication or anonymous access - smtp and imap only */
- protocol = libbalsa_server_get_protocol(server);
- if ((strcmp(protocol, "smtp") == 0) || (strcmp(protocol, "imap") == 0)) {
- server_cfg->require_auth = server_cfg_add_check(server_cfg->basic_grid, server_cfg->basic_rows++,
- _("Server requires _authentication"),
- !libbalsa_server_get_try_anonymous(server),
- G_CALLBACK(on_server_cfg_changed), server_cfg);
- }
+ /* authentication mode */
+ server_cfg->auth_mode = server_cfg_auth_widget(server);
+ server_cfg_add_widget(server_cfg->basic_grid, server_cfg->basic_rows++, _("_Authentication:"),
server_cfg->auth_mode);
+ g_signal_connect(server_cfg->auth_mode, "changed", G_CALLBACK(on_server_cfg_changed), server_cfg);
/* user name and password */
server_cfg->username = server_cfg_add_entry(server_cfg->basic_grid, server_cfg->basic_rows++, _("_User
Name:"),
libbalsa_server_get_user(server),
- G_CALLBACK(on_server_cfg_changed), server_cfg);
+ G_CALLBACK(on_server_cfg_changed), NULL, server_cfg);
server_cfg->password = server_cfg_add_entry(server_cfg->basic_grid, server_cfg->basic_rows++, _("_Pass
Phrase:"),
libbalsa_server_get_password(server),
- G_CALLBACK(on_server_cfg_changed), server_cfg);
+
G_CALLBACK(on_server_cfg_changed), NULL, server_cfg);
g_object_set(server_cfg->password, "input-purpose", GTK_INPUT_PURPOSE_PASSWORD, NULL);
gtk_entry_set_visibility(GTK_ENTRY(server_cfg->password), FALSE);
@@ -158,8 +178,9 @@ libbalsa_server_cfg_new(LibBalsaServer *server, const gchar *name)
}
g_signal_connect(server_cfg->cert_file, "file-set", G_CALLBACK(on_server_cfg_changed), server_cfg);
- server_cfg->cert_pass = server_cfg_add_entry(server_cfg->advanced_grid, server_cfg->advanced_rows++,
_("Certificate _Pass Phrase:"),
- libbalsa_server_get_cert_passphrase(server), G_CALLBACK(on_server_cfg_changed), server_cfg);
+ server_cfg->cert_pass = server_cfg_add_entry(server_cfg->advanced_grid, server_cfg->advanced_rows++,
+ _("Certificate _Pass Phrase:"), libbalsa_server_get_cert_passphrase(server),
G_CALLBACK(on_server_cfg_changed), NULL,
+ server_cfg);
g_object_set(server_cfg->cert_pass, "input-purpose", GTK_INPUT_PURPOSE_PASSWORD, NULL);
gtk_entry_set_visibility(GTK_ENTRY(server_cfg->cert_pass), FALSE);
@@ -207,9 +228,10 @@ libbalsa_server_cfg_add_entry(LibBalsaServerCfg *server_cfg, gboolean basic, con
g_return_val_if_fail(LIBBALSA_IS_SERVER_CFG(server_cfg) && (label != NULL), NULL);
if (basic) {
- new_entry = server_cfg_add_entry(server_cfg->basic_grid, server_cfg->basic_rows++, label,
initval, callback, cb_data);
+ new_entry = server_cfg_add_entry(server_cfg->basic_grid, server_cfg->basic_rows++, label,
initval, callback, NULL, cb_data);
} else {
- new_entry = server_cfg_add_entry(server_cfg->advanced_grid, server_cfg->advanced_rows++,
label, initval, callback, cb_data);
+ new_entry = server_cfg_add_entry(server_cfg->advanced_grid, server_cfg->advanced_rows++,
label, initval, callback, NULL,
+ cb_data);
}
return new_entry;
}
@@ -267,21 +289,16 @@ libbalsa_server_cfg_get_name(LibBalsaServerCfg *server_cfg)
void
libbalsa_server_cfg_assign_server(LibBalsaServerCfg *server_cfg, LibBalsaServer *server)
{
- gchar *cert_file;
+ gchar *cert_file;
g_return_if_fail(LIBBALSA_IS_SERVER_CFG(server_cfg) && LIBBALSA_IS_SERVER(server));
-
/* host, post and security */
libbalsa_server_set_security(server, (NetClientCryptMode)
(gtk_combo_box_get_active(GTK_COMBO_BOX(server_cfg->security)) + 1));
libbalsa_server_set_host(server, gtk_entry_get_text(GTK_ENTRY(server_cfg->host_port)),
libbalsa_server_get_security(server));
/* authentication stuff */
- if (server_cfg->require_auth != NULL) {
- libbalsa_server_set_try_anonymous(server,
!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(server_cfg->require_auth)));
- } else {
- libbalsa_server_set_try_anonymous(server, FALSE);
- }
+ libbalsa_server_set_auth_mode(server,
atoi(gtk_combo_box_get_active_id(GTK_COMBO_BOX(server_cfg->auth_mode))));
libbalsa_server_set_username(server, gtk_entry_get_text(GTK_ENTRY(server_cfg->username)));
libbalsa_server_set_password(server, gtk_entry_get_text(GTK_ENTRY(server_cfg->password)), FALSE);
libbalsa_server_set_remember_password(server,
gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(server_cfg->remember_pass)));
@@ -314,12 +331,22 @@ libbalsa_server_cfg_init(LibBalsaServerCfg *self)
static GtkWidget *
-server_cfg_add_entry(GtkWidget *grid, guint row, const gchar *label, const gchar *value, GCallback callback,
gpointer cb_data)
+server_cfg_add_entry(GtkWidget *grid, guint row, const gchar *label, const gchar *value, GCallback callback,
GtkWidget *button,
+ gpointer cb_data)
{
GtkWidget *new_entry;
new_entry = gtk_entry_new();
- server_cfg_add_widget(grid, row, label, new_entry);
+ if (button != NULL) {
+ GtkWidget *hbox;
+
+ hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, HIG_PADDING / 2);
+ server_cfg_add_widget(grid, row, label, hbox);
+ gtk_box_pack_start(GTK_BOX(hbox), new_entry, TRUE, TRUE, 0U);
+ gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0U);
+ } else {
+ server_cfg_add_widget(grid, row, label, new_entry);
+ }
if (value != NULL) {
gtk_entry_set_text(GTK_ENTRY(new_entry), value);
}
@@ -381,27 +408,59 @@ server_cfg_security_widget(LibBalsaServer *server)
}
+static GtkWidget *
+server_cfg_auth_widget(LibBalsaServer *server)
+{
+ const gchar *protocol;
+ GtkWidget *combo_box = gtk_combo_box_text_new();
+ gchar id_buf[8];
+
+ protocol = libbalsa_server_get_protocol(server);
+ if ((strcmp(protocol, "smtp") == 0) || (strcmp(protocol, "imap") == 0)) {
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo_box), "1", _("none required"));
+ }
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo_box), "2", _("user name and pass phrase"));
+#if defined(HAVE_GSSAPI)
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo_box), "4", _("Kerberos (GSSAPI)"));
+#endif
+#if defined(HAVE_OAUTH2)
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo_box), "8", _("OAuth2"));
+#endif
+
+ snprintf(id_buf, sizeof(id_buf), "%d", (gint) libbalsa_server_get_auth_mode(server));
+ gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo_box), id_buf);
+
+ return combo_box;
+}
+
+
static void
on_server_cfg_changed(GtkWidget *widget, LibBalsaServerCfg *server_cfg)
{
+ const gchar *active_auth;
gboolean sensitive;
+ NetClientAuthMode auth_mode;
/* valid configuration only if a name and a host have been given */
server_cfg->cfg_valid = (*gtk_entry_get_text(GTK_ENTRY(server_cfg->name)) != '\0') &&
(*gtk_entry_get_text(GTK_ENTRY(server_cfg->host_port)) != '\0');
- /* user name/password only if authentication is required */
- if (server_cfg->require_auth != NULL) {
- sensitive = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(server_cfg->require_auth));
+ /* can probe only if name and host/port are given */
+ gtk_widget_set_sensitive(server_cfg->probe_host, server_cfg->cfg_valid);
+
+ /* user name/password depend upon auth mode */
+ active_auth = gtk_combo_box_get_active_id(GTK_COMBO_BOX(server_cfg->auth_mode));
+ if (active_auth != 0) {
+ auth_mode = atoi(active_auth);
} else {
- sensitive = TRUE;
+ auth_mode = 0;
}
- gtk_widget_set_sensitive(server_cfg->username, sensitive);
- gtk_widget_set_sensitive(server_cfg->password, sensitive);
- gtk_widget_set_sensitive(server_cfg->remember_pass, sensitive);
+ gtk_widget_set_sensitive(server_cfg->username, auth_mode != NET_CLIENT_AUTH_ANONYMOUS);
+ gtk_widget_set_sensitive(server_cfg->password, auth_mode == NET_CLIENT_AUTH_USER_PASS);
+ gtk_widget_set_sensitive(server_cfg->remember_pass, auth_mode == NET_CLIENT_AUTH_USER_PASS);
/* invalid configuration if authentication is required, but no user name given */
- if (sensitive && (*gtk_entry_get_text(GTK_ENTRY(server_cfg->username)) == '\0')) {
+ if ((auth_mode != NET_CLIENT_AUTH_ANONYMOUS) && (*gtk_entry_get_text(GTK_ENTRY(server_cfg->username))
== '\0')) {
server_cfg->cfg_valid = FALSE;
}
@@ -428,3 +487,88 @@ on_server_cfg_changed(GtkWidget *widget, LibBalsaServerCfg *server_cfg)
g_signal_emit(server_cfg, changed_sig, 0);
}
+
+
+static void
+on_server_probe(GtkWidget *widget, LibBalsaServerCfg *server_cfg)
+{
+ const gchar *server_name;
+ gboolean success;
+ NetClientProbeResult probe_res;
+ GError *error = NULL;
+ GtkWidget *msgdlg;
+ gint action;
+
+ server_name = gtk_entry_get_text(GTK_ENTRY(server_cfg->host_port));
+ g_assert(server_cfg->probe_fn != NULL);
+ success = server_cfg->probe_fn(server_name, 5, &probe_res, G_CALLBACK(libbalsa_server_check_cert),
&error);
+ if (success) {
+ const gchar *crypt_str;
+ const gchar *auth_str;
+
+ switch (probe_res.crypt_mode) {
+ case NET_CLIENT_CRYPT_ENCRYPTED:
+ crypt_str = _("yes (SSL/TLS)");
+ break;
+ case NET_CLIENT_CRYPT_STARTTLS:
+ crypt_str = _("yes (STARTTLS)");
+ break;
+ default:
+ crypt_str = _("no");
+ }
+
+ if ((probe_res.auth_mode & NET_CLIENT_AUTH_OAUTH2) != 0) {
+ auth_str = _("OAuth2");
+ probe_res.auth_mode = NET_CLIENT_AUTH_OAUTH2;
+ } else if ((probe_res.auth_mode & NET_CLIENT_AUTH_KERBEROS) != 0) {
+ auth_str = _("Kerberos (GSSAPI)");
+ probe_res.auth_mode = NET_CLIENT_AUTH_KERBEROS;
+ } else if ((probe_res.auth_mode & NET_CLIENT_AUTH_USER_PASS) != 0) {
+ auth_str = _("user name and pass phrase");
+ probe_res.auth_mode = NET_CLIENT_AUTH_USER_PASS;
+ } else {
+ auth_str = _("none");
+ probe_res.auth_mode = NET_CLIENT_AUTH_ANONYMOUS;
+ }
+ msgdlg = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_NONE,
+ _("Probe results for server %s\n"
+ "\342\200\242 Port: %u\n"
+ "\342\200\242 Encryption: %s\n"
+ "\342\200\242 Authentication: %s\n"),
+ server_name, probe_res.port, crypt_str, auth_str);
+ gtk_dialog_add_button(GTK_DIALOG(msgdlg), _("_Apply"), GTK_RESPONSE_APPLY);
+ gtk_dialog_add_button(GTK_DIALOG(msgdlg), _("_Close"), GTK_RESPONSE_CLOSE);
+ } else {
+ msgdlg = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT | libbalsa_dialog_flags(),
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Error probing server %s: %s"), server_name,
+ (error != NULL) ? error->message : _("unknown"));
+ }
+ g_clear_error(&error);
+
+ action = gtk_dialog_run(GTK_DIALOG(msgdlg));
+ gtk_widget_destroy(msgdlg);
+ if (action == GTK_RESPONSE_APPLY) {
+ gchar *buffer;
+ gchar id_buf[8];
+ const gchar *colon;
+
+ colon = strchr(server_name, ':');
+ if (colon == NULL) {
+ buffer = g_strdup_printf("%s:%u", server_name, probe_res.port);
+ } else {
+ buffer = g_strdup_printf("%.*s:%u", (int) (colon - server_name), server_name,
probe_res.port);
+ }
+ gtk_entry_set_text(GTK_ENTRY(server_cfg->host_port), buffer);
+ g_free(buffer);
+
+ gtk_combo_box_set_active(GTK_COMBO_BOX(server_cfg->security), probe_res.crypt_mode - 1);
+ snprintf(id_buf, sizeof(id_buf), "%d", probe_res.auth_mode);
+ gtk_combo_box_set_active_id(GTK_COMBO_BOX(server_cfg->auth_mode), id_buf);
+ }
+}
diff --git a/libbalsa/server.c b/libbalsa/server.c
index c28e9e181..c49cd2d0d 100644
--- a/libbalsa/server.c
+++ b/libbalsa/server.c
@@ -71,7 +71,7 @@ struct _LibBalsaServerPrivate {
gchar *cert_passphrase;
gboolean remember_passwd;
gboolean remember_cert_passphrase;
- gboolean try_anonymous; /* user wants anonymous access */
+ NetClientAuthMode auth_mode;
};
static void libbalsa_server_finalize(GObject * object);
@@ -288,6 +288,7 @@ libbalsa_server_load_security_config(LibBalsaServer *server)
want_ssl = libbalsa_conf_get_bool_with_default("SSL", ¬_found);
if (want_ssl && !not_found) {
priv->security = NET_CLIENT_CRYPT_ENCRYPTED;
+ libbalsa_conf_clean_key("SSL");
} else {
int want_tls;
@@ -305,10 +306,10 @@ libbalsa_server_load_security_config(LibBalsaServer *server)
default:
priv->security = NET_CLIENT_CRYPT_STARTTLS;
}
+ libbalsa_conf_clean_key("SSL");
}
}
}
-
}
@@ -444,7 +445,13 @@ libbalsa_server_load_config(LibBalsaServer * server)
priv->user = g_strdup(g_get_user_name());
}
- priv->try_anonymous = libbalsa_conf_get_bool("Anonymous=false");
+ priv->auth_mode = libbalsa_conf_get_int("AuthMode=2"); /* default NET_CLIENT_AUTH_USER_PASS */
+ if (libbalsa_conf_has_key("Anonymous")) {
+ if (libbalsa_conf_get_bool("Anonymous")) {
+ priv->auth_mode = NET_CLIENT_AUTH_ANONYMOUS;
+ }
+ libbalsa_conf_clean_key("Anonymous");
+ }
priv->remember_passwd = libbalsa_conf_get_bool("RememberPasswd=false");
priv->passwd = libbalsa_free_password(priv->passwd);
@@ -490,7 +497,7 @@ libbalsa_server_save_config(LibBalsaServer * server)
libbalsa_conf_set_string("Server", priv->host);
libbalsa_conf_private_set_string("Username", priv->user, FALSE);
- libbalsa_conf_set_bool("Anonymous", priv->try_anonymous);
+ libbalsa_conf_set_int("AuthMode", priv->auth_mode);
if (priv->remember_passwd && (priv->passwd != NULL)) {
libbalsa_conf_set_bool("RememberPasswd", TRUE);
@@ -556,7 +563,7 @@ libbalsa_server_get_auth(NetClient *client,
g_debug("%s: %p %p: encrypted = %d", __func__, client, user_data,
net_client_is_encrypted(client));
- if (!priv->try_anonymous || (strcmp(priv->protocol, "imap") == 0)) {
+ if (priv->auth_mode != NET_CLIENT_AUTH_ANONYMOUS) {
result = g_new0(gchar *, 3U);
result[0] = g_strdup(priv->user);
if (need_passwd) {
@@ -735,14 +742,14 @@ libbalsa_server_get_security(LibBalsaServer *server)
return priv->security;
}
-gboolean
-libbalsa_server_get_try_anonymous(LibBalsaServer *server)
+NetClientAuthMode
+libbalsa_server_get_auth_mode(LibBalsaServer *server)
{
LibBalsaServerPrivate *priv = libbalsa_server_get_instance_private(server);
- g_return_val_if_fail(LIBBALSA_IS_SERVER(server), FALSE);
+ g_return_val_if_fail(LIBBALSA_IS_SERVER(server), (NetClientAuthMode) 0);
- return priv->try_anonymous;
+ return priv->auth_mode;
}
gboolean
@@ -813,13 +820,13 @@ libbalsa_server_set_security(LibBalsaServer *server, NetClientCryptMode security
}
void
-libbalsa_server_set_try_anonymous(LibBalsaServer *server, gboolean try_anonymous)
+libbalsa_server_set_auth_mode(LibBalsaServer *server, NetClientAuthMode auth_mode)
{
LibBalsaServerPrivate *priv = libbalsa_server_get_instance_private(server);
g_return_if_fail(LIBBALSA_IS_SERVER(server));
- priv->try_anonymous = try_anonymous;
+ priv->auth_mode = auth_mode;
}
void
diff --git a/libbalsa/server.h b/libbalsa/server.h
index 6fa0ca5b1..34584ef31 100644
--- a/libbalsa/server.h
+++ b/libbalsa/server.h
@@ -98,7 +98,7 @@ const gchar * libbalsa_server_get_protocol(LibBalsaServer *server);
const gchar * libbalsa_server_get_password(LibBalsaServer *server);
const gchar * libbalsa_server_get_cert_passphrase(LibBalsaServer *server);
NetClientCryptMode libbalsa_server_get_security(LibBalsaServer *server);
-gboolean libbalsa_server_get_try_anonymous(LibBalsaServer *server);
+NetClientAuthMode libbalsa_server_get_auth_mode(LibBalsaServer *server);
gboolean libbalsa_server_get_client_cert(LibBalsaServer *server);
gboolean libbalsa_server_get_remember_password(LibBalsaServer *server);
gboolean libbalsa_server_get_remember_cert_passphrase(LibBalsaServer *server);
@@ -107,7 +107,7 @@ gboolean libbalsa_server_get_remember_cert_passphrase(LibBalsaServer *server);
void libbalsa_server_set_protocol(LibBalsaServer *server, const gchar *protocol);
void libbalsa_server_set_cert_file(LibBalsaServer *server, const gchar *cert_file);
void libbalsa_server_set_security(LibBalsaServer *server, NetClientCryptMode security);
-void libbalsa_server_set_try_anonymous(LibBalsaServer *server, gboolean try_anonymous);
+void libbalsa_server_set_auth_mode(LibBalsaServer *server, NetClientAuthMode auth_mode);
void libbalsa_server_set_remember_password(LibBalsaServer *server, gboolean remember_password);
void libbalsa_server_set_client_cert(LibBalsaServer *server, gboolean client_cert);
void libbalsa_server_set_remember_cert_passphrase(LibBalsaServer *server,
diff --git a/libnetclient/net-client-pop.c b/libnetclient/net-client-pop.c
index abf78b7cb..2f6456948 100644
--- a/libnetclient/net-client-pop.c
+++ b/libnetclient/net-client-pop.c
@@ -25,13 +25,49 @@ struct _NetClientPop {
NetClientCryptMode crypt_mode;
gchar *apop_banner;
- guint auth_allowed[2]; /** 0: encrypted, 1: unencrypted */
+ guint auth_enabled;
gboolean can_pipelining;
gboolean can_uidl;
gboolean use_pipelining;
};
+/** @name POP authentication methods
+ *
+ * Note that the availability of these authentication methods depends upon the result of the CAPABILITY
list. According to RFC
+ * 1939, Section 4, at least either APOP or USER/PASS @em must be supported.
+ * @{
+ */
+/** RFC 1939 "USER" and "PASS" authentication method. */
+#define NET_CLIENT_POP_AUTH_USER_PASS 0x01U
+/** RFC 1939 "APOP" authentication method. */
+#define NET_CLIENT_POP_AUTH_APOP 0x02U
+/** RFC 5034 SASL "LOGIN" authentication method. */
+#define NET_CLIENT_POP_AUTH_LOGIN 0x04U
+/** RFC 5034 SASL "PLAIN" authentication method. */
+#define NET_CLIENT_POP_AUTH_PLAIN 0x08U
+/** RFC 5034 SASL "CRAM-MD5" authentication method. */
+#define NET_CLIENT_POP_AUTH_CRAM_MD5 0x10U
+/** RFC 5034 SASL "CRAM-SHA1" authentication method. */
+#define NET_CLIENT_POP_AUTH_CRAM_SHA1 0x20U
+/** RFC 4752 "GSSAPI" authentication method. */
+#define NET_CLIENT_POP_AUTH_GSSAPI 0x40U
+/** RFC 6749 "XOAUTH2" authentication method. */
+#define NET_CLIENT_POP_AUTH_OAUTH2 0x80U
+
+/** Mask of all authentication methods requiring user name and password. */
+#define NET_CLIENT_POP_AUTH_PASSWORD \
+ (NET_CLIENT_POP_AUTH_USER_PASS | NET_CLIENT_POP_AUTH_APOP | NET_CLIENT_POP_AUTH_LOGIN |
NET_CLIENT_POP_AUTH_PLAIN | \
+ NET_CLIENT_POP_AUTH_CRAM_MD5 | NET_CLIENT_POP_AUTH_CRAM_SHA1)
+
+/** Mask of all authentication methods. */
+#define NET_CLIENT_POP_AUTH_ALL \
+ (NET_CLIENT_POP_AUTH_PASSWORD + NET_CLIENT_POP_AUTH_GSSAPI + NET_CLIENT_POP_AUTH_OAUTH2)
+/** Mask of all authentication methods which do not require a password (Note: OAuth2 requires the
authentication token). */
+#define NET_CLIENT_POP_AUTH_NO_PWD NET_CLIENT_POP_AUTH_GSSAPI
+/** @} */
+
+
/* Note: the maximum line length of a message body downloaded from the POP3 server may be up to 998 chars,
excluding the terminating
* CRLF, see RFC 5322, Sect. 2.1.1. However, it also states that "Receiving implementations would do well
to handle an arbitrarily
* large number of characters in a line for robustness sake", so we actually accept lines from POP3 of
unlimited length. */
@@ -68,6 +104,7 @@ static gboolean net_client_pop_auth_apop(NetClientPop *client, const gchar* user
static gboolean net_client_pop_auth_cram(NetClientPop *client, GChecksumType chksum_type, const gchar *user,
const gchar *passwd,
GError **error);
static gboolean net_client_pop_auth_gssapi(NetClientPop *client, const gchar *user, GError **error);
+static gboolean net_client_pop_auth_oauth2(NetClientPop *client, const gchar *user, const gchar
*access_token, GError **error);
static gboolean net_client_pop_retr_msg(NetClientPop *client, const NetClientPopMessageInfo *info,
NetClientPopMsgCb callback,
gpointer user_data, GError
**error);
@@ -96,16 +133,127 @@ net_client_pop_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mod
gboolean
-net_client_pop_allow_auth(NetClientPop *client, gboolean encrypted, guint allow_auth)
+net_client_pop_probe(const gchar *host, guint timeout_secs, NetClientProbeResult *result, GCallback cert_cb,
GError **error)
{
+ guint16 probe_ports[] = {995U, 110U, 0U}; /* pop3s, pop3 */
+ gchar *host_only;
+ gchar *colon;
+ gboolean retval = FALSE;
+ gint check_id;
+
/* paranoia check */
+ g_return_val_if_fail((host != NULL) && (result != NULL), FALSE);
+
+ host_only = g_strdup(host);
+ colon = strchr(host_only, ':');
+ if (colon != NULL) {
+ colon[0] = '\0';
+ }
+
+ if (!net_client_host_reachable(host_only, error)) {
+ g_free(host_only);
+ return FALSE;
+ }
+
+ for (check_id = 0; !retval && (probe_ports[check_id] > 0U); check_id++) {
+ NetClientPop *client;
+
+ g_debug("%s: probing %s:%u…", __func__, host_only, probe_ports[check_id]);
+ client = net_client_pop_new(host_only, probe_ports[check_id], NET_CLIENT_CRYPT_NONE, FALSE);
+ net_client_set_timeout(NET_CLIENT(client), timeout_secs);
+ if (net_client_connect(NET_CLIENT(client), NULL)) {
+ gboolean this_success;
+ guint auth_supported = 0U;
+ gboolean can_starttls = FALSE;
+
+ if (cert_cb != NULL) {
+ g_signal_connect(client, "cert-check", cert_cb, client);
+ }
+ if (check_id == 0) { /* pop3s */
+ this_success = net_client_start_tls(NET_CLIENT(client), NULL);
+ } else {
+ this_success = TRUE;
+ }
+
+ /* get the greeting */
+ if (this_success) {
+ this_success = net_client_pop_read_reply(client, NULL, error);
+ }
+
+ /* send CAPA and read the capabilities of the server */
+ if (this_success) {
+ net_client_pop_get_capa(client, &auth_supported);
+
+ }
+
+ /* try to perform STARTTLS unless we are already encrypted, and send CAPA again */
+ if (this_success && (check_id != 0)) {
+ can_starttls = net_client_pop_starttls(client, NULL);
+ if (can_starttls) {
+ net_client_pop_get_capa(client, &auth_supported);
+ }
+ }
+
+ /* evaluate on success */
+ if (this_success) {
+ result->port = probe_ports[check_id];
+
+ if (check_id == 0) {
+ result->crypt_mode = NET_CLIENT_CRYPT_ENCRYPTED;
+ } else if (can_starttls) {
+ result->crypt_mode = NET_CLIENT_CRYPT_STARTTLS;
+ } else {
+ result->crypt_mode = NET_CLIENT_CRYPT_NONE;
+ }
+
+ /* RFC 1939, Section 4, require at least either APOP or USER/PASS */
+ result->auth_mode = NET_CLIENT_AUTH_USER_PASS;
+ if ((auth_supported & NET_CLIENT_POP_AUTH_GSSAPI) != 0U) {
+ result->auth_mode |= NET_CLIENT_AUTH_KERBEROS;
+ }
+ if ((auth_supported & NET_CLIENT_POP_AUTH_OAUTH2) != 0U) {
+ result->auth_mode |= NET_CLIENT_AUTH_OAUTH2;
+ }
+ retval = TRUE;
+ }
+ }
+ g_object_unref(client);
+ }
+
+ if (!retval) {
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, NET_CLIENT_PROBE_FAILED,
+ _("the server %s does not offer the POP3 service at port 995 or 110"), host_only);
+ }
+
+ g_free(host_only);
+
+ return retval;
+}
+
+
+gboolean
+net_client_pop_set_auth_mode(NetClientPop *client, NetClientAuthMode auth_mode, gboolean disable_apop)
+{
g_return_val_if_fail(NET_IS_CLIENT_POP(client), FALSE);
- if (encrypted) {
- client->auth_allowed[0] = allow_auth;
- } else {
- client->auth_allowed[1] = allow_auth;
+
+ client->auth_enabled = 0U;
+ if ((auth_mode & NET_CLIENT_AUTH_USER_PASS) != 0U) {
+ client->auth_enabled |= NET_CLIENT_POP_AUTH_PASSWORD;
+ if (disable_apop) {
+ client->auth_enabled &= ~NET_CLIENT_POP_AUTH_APOP;
+ }
}
- return TRUE;
+#if defined(HAVE_GSSAPI)
+ if ((auth_mode & NET_CLIENT_AUTH_KERBEROS) != 0U) {
+ client->auth_enabled |= NET_CLIENT_POP_AUTH_GSSAPI;
+ }
+#endif
+#if defined (HAVE_OAUTH2)
+ if ((auth_mode & NET_CLIENT_AUTH_OAUTH2) != 0U) {
+ client->auth_enabled |= NET_CLIENT_POP_AUTH_OAUTH2;
+ }
+#endif
+ return (client->auth_enabled != 0U);
}
@@ -182,20 +330,6 @@ net_client_pop_connect(NetClientPop *client, gchar **greeting, GError **error)
result = net_client_pop_auth(client, auth_data[0], auth_data[1], auth_supported,
error);
net_client_free_authstr(auth_data[0]);
net_client_free_authstr(auth_data[1]);
-
- /* if passwordless authentication failed, try again with password */
- if (!result && !need_pwd) {
- g_debug("passwordless authentication failed, retry w/ password: emit 'auth'
signal for client %p", client);
- g_clear_error(error);
- g_free(auth_data);
- g_signal_emit_by_name(client, "auth", TRUE, &auth_data); /*lint !e730
passing a gboolean is intended here */
- if ((auth_data != NULL) && (auth_data[0] != NULL)) {
- result = net_client_pop_auth(client, auth_data[0], auth_data[1],
- auth_supported & ~NET_CLIENT_POP_AUTH_NO_PWD, error);
- net_client_free_authstr(auth_data[0]);
- net_client_free_authstr(auth_data[1]);
- }
- }
}
g_free(auth_data);
}
@@ -399,8 +533,7 @@ net_client_pop_class_init(NetClientPopClass *klass)
static void
net_client_pop_init(NetClientPop *self)
{
- self->auth_allowed[0] = NET_CLIENT_POP_AUTH_ALL;
- self->auth_allowed[1] = NET_CLIENT_POP_AUTH_SAFE;
+ self->auth_enabled = NET_CLIENT_POP_AUTH_ALL;
}
@@ -498,11 +631,8 @@ net_client_pop_auth(NetClientPop *client, const gchar *user, const gchar *passwd
gboolean result = FALSE;
guint auth_mask;
- if (net_client_is_encrypted(NET_CLIENT(client))) {
- auth_mask = client->auth_allowed[0] & auth_supported;
- } else {
- auth_mask = client->auth_allowed[1] & auth_supported;
- }
+ /* calculate the possible authentication methods */
+ auth_mask = client->auth_enabled & auth_supported;
if (auth_mask == 0U) {
g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
@@ -511,7 +641,9 @@ net_client_pop_auth(NetClientPop *client, const gchar *user, const gchar *passwd
g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
_("password required"));
} else {
/* first try authentication methods w/o password, then safe ones, and finally the plain-text
methods */
- if ((auth_mask & NET_CLIENT_POP_AUTH_GSSAPI) != 0U) {
+ if ((auth_mask & NET_CLIENT_POP_AUTH_OAUTH2) != 0U) {
+ result = net_client_pop_auth_oauth2(client, user, passwd, error);
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_GSSAPI) != 0U) {
result = net_client_pop_auth_gssapi(client, user, error);
} else if ((auth_mask & NET_CLIENT_POP_AUTH_CRAM_SHA1) != 0U) {
result = net_client_pop_auth_cram(client, G_CHECKSUM_SHA1, user, passwd, error);
@@ -698,6 +830,40 @@ net_client_pop_auth_gssapi(NetClientPop G_GNUC_UNUSED *client, const gchar G_GNU
#endif /* HAVE_GSSAPI */
+#if defined(HAVE_OAUTH2)
+
+static gboolean
+net_client_pop_auth_oauth2(NetClientPop *client, const gchar *user, const gchar *access_token, GError
**error)
+{
+ gboolean result ;
+ gchar *base64_buf;
+
+ base64_buf = net_client_auth_oauth2_calc(user, access_token);
+ if (base64_buf != NULL) {
+ result = net_client_pop_execute_sasl(client, "AUTH XOAUTH2", NULL, error);
+ if (result) {
+ result = net_client_pop_execute(client, "%s", NULL, error, base64_buf);
+ // FIXME - grab the JSON response on error
+ }
+ net_client_free_authstr(base64_buf);
+ } else {
+ result = FALSE;
+ }
+ return result;
+}
+
+#else
+
+static gboolean
+net_client_pop_auth_oauth2(NetClientPop G_GNUC_UNUSED *client, const gchar G_GNUC_UNUSED *user,
+ const gcharG_GNUC_UNUSED *access_token, GError G_GNUC_UNUSED **error)
+{
+ g_assert_not_reached(); /* this should never happen! */
+ return FALSE; /* never reached, make gcc happy */
+}
+
+#endif /* HAVE_OAUTH2 */
+
/* Note: if supplied, challenge is never NULL on success */
static gboolean
net_client_pop_execute_sasl(NetClientPop *client, const gchar *request_fmt, gchar **challenge, GError
**error, ...)
@@ -769,6 +935,10 @@ net_client_pop_get_capa(NetClientPop *client, guint *auth_supported)
#if defined(HAVE_GSSAPI)
} else if (strcmp(auth[n], "GSSAPI") == 0) {
*auth_supported |= NET_CLIENT_POP_AUTH_GSSAPI;
+#endif
+#if defined(HAVE_OAUTH2)
+ } else if (strcmp(auth[n], "XOAUTH2") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_OAUTH2;
#endif
} else {
/* other auth methods are ignored for the time being */
diff --git a/libnetclient/net-client-pop.h b/libnetclient/net-client-pop.h
index 527e009f1..58faeff60 100644
--- a/libnetclient/net-client-pop.h
+++ b/libnetclient/net-client-pop.h
@@ -17,6 +17,7 @@
#include "net-client.h"
+#include "net-client-utils.h"
G_BEGIN_DECLS
@@ -43,37 +44,6 @@ enum _NetClientPopError {
};
-/** @name POP authentication methods
- *
- * Note that the availability of these authentication methods depends upon the result of the CAPABILITY
list. According to RFC
- * 1939, Section 4, at least either APOP or USER/PASS @em must be supported.
- * @{
- */
-/** RFC 1939 "USER" and "PASS" authentication method. */
-#define NET_CLIENT_POP_AUTH_USER_PASS 0x01U
-/** RFC 1939 "APOP" authentication method. */
-#define NET_CLIENT_POP_AUTH_APOP 0x02U
-/** RFC 5034 SASL "LOGIN" authentication method. */
-#define NET_CLIENT_POP_AUTH_LOGIN 0x04U
-/** RFC 5034 SASL "PLAIN" authentication method. */
-#define NET_CLIENT_POP_AUTH_PLAIN 0x08U
-/** RFC 5034 SASL "CRAM-MD5" authentication method. */
-#define NET_CLIENT_POP_AUTH_CRAM_MD5 0x10U
-/** RFC 5034 SASL "CRAM-SHA1" authentication method. */
-#define NET_CLIENT_POP_AUTH_CRAM_SHA1 0x20U
-/** RFC 4752 "GSSAPI" authentication method. */
-#define NET_CLIENT_POP_AUTH_GSSAPI 0x40U
-/** Mask of all safe authentication methods, i.e. all methods which do not send the cleartext password. */
-#define NET_CLIENT_POP_AUTH_SAFE \
- (NET_CLIENT_POP_AUTH_APOP + NET_CLIENT_POP_AUTH_CRAM_MD5 + NET_CLIENT_POP_AUTH_CRAM_SHA1 +
NET_CLIENT_POP_AUTH_GSSAPI)
-/** Mask of all authentication methods. */
-#define NET_CLIENT_POP_AUTH_ALL \
- (NET_CLIENT_POP_AUTH_USER_PASS + NET_CLIENT_POP_AUTH_PLAIN + NET_CLIENT_POP_AUTH_LOGIN +
NET_CLIENT_POP_AUTH_SAFE)
-/** Mask of all authentication methods which do not require a password. */
-#define NET_CLIENT_POP_AUTH_NO_PWD NET_CLIENT_POP_AUTH_GSSAPI
-/** @} */
-
-
/** @brief Message information
*
* This structure is returned in a GList by net_client_pop_list() and contains information about on message
in the remote mailbox.
@@ -115,6 +85,21 @@ typedef gboolean (*NetClientPopMsgCb)(const gchar *buffer, gssize count, gsize l
gpointer user_data, GError **error);
+/** @brief Probe a POP3 server
+ *
+ * @param host host name or IP address of the server to probe
+ * @param timeout_secs time-out in seconds
+ * @param result filled with the probe results
+ * @param cert_cb optional server certificate acceptance callback
+ * @param error filled with error information if probing fails
+ * @return TRUE if probing the passed server was successful, FALSE if not
+ *
+ * Probe the passed server by trying to connect to the standard ports (in this order) 995 and 110.
+ */
+gboolean net_client_pop_probe(const gchar *host, guint timeout_secs, NetClientProbeResult *result, GCallback
cert_cb,
+ GError **error);
+
+
/** @brief Create a new POP network client
*
* @param host host name or IP address to connect
@@ -129,16 +114,17 @@ NetClientPop *net_client_pop_new(const gchar *host, guint16 port, NetClientCrypt
/** @brief Set allowed POP AUTH methods
*
* @param client POP network client object
- * @param encrypted set allowed methods for encrypted or unencrypted connections
- * @param allow_auth mask of allowed authentication methods
- * @return TRUE on success or FALSE on error
+ * @param auth_mode mask of allowed authentication methods
+ * @param disable_apop TRUE to disable APOP authentication which is not supported by some broken servers
+ * @return TRUE on success or FALSE on error or if no authentication method is allowed
*
- * Set the allowed authentication methods for the passed connection. The default is @ref
NET_CLIENT_POP_AUTH_ALL for both encrypted
- * and unencrypted connections.
+ * Set the allowed authentication methods for the passed connection. The default is to enable all
authentication methods.
*
- * @note Call this function @em before calling net_client_pop_connect().
+ * @note Call this function @em before calling net_client_pop_connect().\n
+ * POP3 does not allow anonymous access without authentication, i.e. the flag @ref
NET_CLIENT_AUTH_ANONYMOUS in the argument
+ * is ignored.
*/
-gboolean net_client_pop_allow_auth(NetClientPop *client, gboolean encrypted, guint allow_auth);
+gboolean net_client_pop_set_auth_mode(NetClientPop *client, NetClientAuthMode auth_mode, gboolean
disable_apop);
/** @brief Connect a POP network client
@@ -151,9 +137,10 @@ gboolean net_client_pop_allow_auth(NetClientPop *client, gboolean encrypted, gui
* Connect the remote POP server, initialise the encryption if requested, and emit the @ref auth signal to
request authentication
* information. Simply ignore the signal for an unauthenticated connection.
*
- * The function will try only @em one authentication method supported by the server and enabled for the
current encryption state
- * (see net_client_pop_allow_auth() and \ref NET_CLIENT_POP_AUTH_ALL etc.). The priority is, from highest
to lowest, GSSAPI (if
- * configured), CRAM-SHA1, CRAM-MD5, APOP, PLAIN, USER/PASS or LOGIN.
+ * The function will try only @em one authentication method which is both supported by the server and
enabled by calling
+ * net_client_pop_set_auth_mode(). The default is to try all methods, from highest to lowest, OAuth2 (if
configured), GSSAPI (if
+ * configured), CRAM-SHA1, CRAM-MD5, APOP, PLAIN, USER/PASS or LOGIN. It is up to the caller to ensure
encryption or a connection
+ * to @c localhost if one of the plain text methods shall be used.
*
* In order to shut down a successfully established connection, just call <tt>g_object_unref()</tt> on the
POP network client
* object.
diff --git a/libnetclient/net-client-smtp.c b/libnetclient/net-client-smtp.c
index 3d20fa17a..be0d41332 100644
--- a/libnetclient/net-client-smtp.c
+++ b/libnetclient/net-client-smtp.c
@@ -23,7 +23,7 @@ struct _NetClientSmtp {
NetClient parent;
NetClientCryptMode crypt_mode;
- guint auth_allowed[2]; /** 0: encrypted, 1: unencrypted */
+ guint auth_enabled; /* Note: 0 = anonymous connection w/o
authentication */
gboolean can_dsn;
gboolean data_state;
};
@@ -46,6 +46,36 @@ typedef struct {
} smtp_rcpt_t;
+/** @name SMTP authentication methods
+ * @{
+ */
+/** Anonymous access (no authentication) */
+#define NET_CLIENT_SMTP_AUTH_NONE 0x01U
+/** RFC 4616 "PLAIN" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_PLAIN 0x02U
+/** "LOGIN" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_LOGIN 0x04U
+/** RFC 2195 "CRAM-MD5" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_CRAM_MD5 0x08U
+/** RFC xxxx "CRAM-SHA1" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_CRAM_SHA1 0x10U
+/** RFC 4752 "GSSAPI" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_GSSAPI 0x20U
+/** RFC 6749 "XOAUTH2" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_OAUTH2 0x40U
+
+/** Mask of all authentication methods requiring user name and password. */
+#define NET_CLIENT_SMTP_AUTH_PASSWORD \
+ (NET_CLIENT_SMTP_AUTH_PLAIN | NET_CLIENT_SMTP_AUTH_LOGIN | NET_CLIENT_SMTP_AUTH_CRAM_MD5 |
NET_CLIENT_SMTP_AUTH_CRAM_SHA1)
+
+/** Mask of all authentication methods. */
+#define NET_CLIENT_SMTP_AUTH_ALL \
+ (NET_CLIENT_SMTP_AUTH_NONE | NET_CLIENT_SMTP_AUTH_PASSWORD | NET_CLIENT_SMTP_AUTH_GSSAPI |
NET_CLIENT_SMTP_AUTH_OAUTH2)
+/** Mask of all authentication methods which do not require a password (Note: OAuth2 requires the
authentication token). */
+#define NET_CLIENT_SMTP_AUTH_NO_PWD NET_CLIENT_SMTP_AUTH_GSSAPI
+/** @} */
+
+
/* Note: RFC 5321 defines a maximum line length of 512 octets, including the terminating CRLF. However, RFC
4954, Sect. 4. defines
* 12288 octets as safe maximum length for SASL authentication. */
#define MAX_SMTP_LINE_LEN 12288U
@@ -68,6 +98,7 @@ static gboolean net_client_smtp_auth_login(NetClientSmtp *client, const gchar *u
static gboolean net_client_smtp_auth_cram(NetClientSmtp *client, GChecksumType chksum_type, const gchar
*user, const gchar *passwd,
GError **error);
static gboolean net_client_smtp_auth_gssapi(NetClientSmtp *client, const gchar *user, GError **error);
+static gboolean net_client_smtp_auth_oauth2(NetClientSmtp *client, const gchar *user, const gchar
*access_token, GError **error);
static gboolean net_client_smtp_read_reply(NetClientSmtp *client, gint expect_code, gchar **last_reply,
GError **error);
static gboolean net_client_smtp_eval_rescode(gint res_code, const gchar *reply, GError **error);
static gchar *net_client_smtp_dsn_to_string(const NetClientSmtp *client, NetClientSmtpDsnMode dsn_mode);
@@ -97,16 +128,130 @@ net_client_smtp_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mo
gboolean
-net_client_smtp_allow_auth(NetClientSmtp *client, gboolean encrypted, guint allow_auth)
+net_client_smtp_set_auth_mode(NetClientSmtp *client, NetClientAuthMode auth_mode)
{
- /* paranoia check */
g_return_val_if_fail(NET_IS_CLIENT_SMTP(client), FALSE);
- if (encrypted) {
- client->auth_allowed[0] = allow_auth;
- } else {
- client->auth_allowed[1] = allow_auth;
+
+ client->auth_enabled = 0U;
+ if ((auth_mode & NET_CLIENT_AUTH_ANONYMOUS) != 0U) {
+ client->auth_enabled |= NET_CLIENT_SMTP_AUTH_NONE;
}
- return TRUE;
+ if ((auth_mode & NET_CLIENT_AUTH_USER_PASS) != 0U) {
+ client->auth_enabled |= NET_CLIENT_SMTP_AUTH_PASSWORD;
+ }
+#if defined(HAVE_GSSAPI)
+ if ((auth_mode & NET_CLIENT_AUTH_KERBEROS) != 0U) {
+ client->auth_enabled |= NET_CLIENT_SMTP_AUTH_GSSAPI;
+ }
+#endif
+#if defined (HAVE_OAUTH2)
+ if ((auth_mode & NET_CLIENT_AUTH_OAUTH2) != 0U) {
+ client->auth_enabled |= NET_CLIENT_SMTP_AUTH_OAUTH2;
+ }
+#endif
+ return (client->auth_enabled != 0U);
+}
+
+
+gboolean
+net_client_smtp_probe(const gchar *host, guint timeout_secs, NetClientProbeResult *result, GCallback
cert_cb, GError **error)
+{
+ guint16 probe_ports[] = {587U, 25U, 465U, 0U}; /* submission, smtp, smtps */
+ gchar *host_only;
+ gchar *colon;
+ gboolean retval = FALSE;
+ gint check_id;
+
+ /* paranoia check */
+ g_return_val_if_fail((host != NULL) && (result != NULL), FALSE);
+
+ host_only = g_strdup(host);
+ colon = strchr(host_only, ':');
+ if (colon != NULL) {
+ colon[0] = '\0';
+ }
+
+ if (!net_client_host_reachable(host_only, error)) {
+ g_free(host_only);
+ return FALSE;
+ }
+
+ for (check_id = 0; !retval && (probe_ports[check_id] > 0U); check_id++) {
+ NetClientSmtp *client;
+
+ g_debug("%s: probing %s:%u…", __func__, host_only, probe_ports[check_id]);
+ client = net_client_smtp_new(host_only, probe_ports[check_id], NET_CLIENT_CRYPT_NONE);
+ net_client_set_timeout(NET_CLIENT(client), timeout_secs);
+ if (net_client_connect(NET_CLIENT(client), NULL)) {
+ gboolean this_success;
+ guint auth_supported;
+ gboolean can_starttls;
+
+ if (cert_cb != NULL) {
+ g_signal_connect(client, "cert-check", cert_cb, client);
+ }
+ if (check_id == 2) { /* smtps */
+ this_success = net_client_start_tls(NET_CLIENT(client), NULL);
+ } else {
+ this_success = TRUE;
+ }
+
+ /* get the greeting */
+ if (this_success) {
+ this_success = net_client_smtp_read_reply(client, 220, NULL, NULL);
+ }
+
+ /* send EHLO and read the capabilities of the server */
+ if (this_success) {
+ this_success = net_client_smtp_ehlo(client, &auth_supported, &can_starttls,
NULL);
+ }
+
+ /* try to perform STARTTLS if supported, and send EHLO again */
+ if (this_success && can_starttls) {
+ can_starttls = net_client_smtp_starttls(client, NULL);
+ if (can_starttls) {
+ gboolean dummy;
+
+ can_starttls = net_client_smtp_ehlo(client, &auth_supported, &dummy,
NULL);
+ }
+ }
+
+ /* evaluate on success */
+ if (this_success) {
+ result->port = probe_ports[check_id];
+
+ if (check_id == 2) {
+ result->crypt_mode = NET_CLIENT_CRYPT_ENCRYPTED;
+ } else if (can_starttls) {
+ result->crypt_mode = NET_CLIENT_CRYPT_STARTTLS;
+ } else {
+ result->crypt_mode = NET_CLIENT_CRYPT_NONE;
+ }
+
+ result->auth_mode = 0U;
+ if ((auth_supported & NET_CLIENT_SMTP_AUTH_PASSWORD) != 0U) {
+ result->auth_mode |= NET_CLIENT_AUTH_USER_PASS;
+ }
+ if ((auth_supported & NET_CLIENT_SMTP_AUTH_GSSAPI) != 0U) {
+ result->auth_mode |= NET_CLIENT_AUTH_KERBEROS;
+ }
+ if ((auth_supported & NET_CLIENT_SMTP_AUTH_OAUTH2) != 0U) {
+ result->auth_mode |= NET_CLIENT_AUTH_OAUTH2;
+ }
+ retval = TRUE;
+ }
+ }
+ g_object_unref(client);
+ }
+
+ if (!retval) {
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, NET_CLIENT_PROBE_FAILED,
+ _("the server %s does not offer the SMTP service at port 587, 25 or 465"), host_only);
+ }
+
+ g_free(host_only);
+
+ return retval;
}
@@ -154,8 +299,8 @@ net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
}
}
- /* authenticate if we were successful so far */
- if (result) {
+ /* authenticate if we were successful so far, unless anonymous access is configured */
+ if (result && (client->auth_enabled != NET_CLIENT_SMTP_AUTH_NONE)) {
gchar **auth_data;
gboolean need_pwd;
@@ -167,20 +312,6 @@ net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
result = net_client_smtp_auth(client, auth_data[0], auth_data[1], auth_supported,
error);
net_client_free_authstr(auth_data[0]);
net_client_free_authstr(auth_data[1]);
-
- /* if passwordless authentication failed, try again with password */
- if (!result && !need_pwd) {
- g_debug("passwordless authentication failed, retry w/ password: emit 'auth'
signal for client %p", client);
- g_clear_error(error);
- g_free(auth_data);
- g_signal_emit_by_name(client, "auth", TRUE, &auth_data); /*lint !e730
passing a gboolean is intended here */
- if ((auth_data != NULL) && (auth_data[0] != NULL)) {
- result = net_client_smtp_auth(client, auth_data[0], auth_data[1],
- auth_supported & ~NET_CLIENT_SMTP_AUTH_NO_PWD, error);
- net_client_free_authstr(auth_data[0]);
- net_client_free_authstr(auth_data[1]);
- }
- }
}
g_free(auth_data);
}
@@ -360,8 +491,7 @@ net_client_smtp_class_init(NetClientSmtpClass *klass)
static void
net_client_smtp_init(NetClientSmtp *self)
{
- self->auth_allowed[0] = NET_CLIENT_SMTP_AUTH_ALL;
- self->auth_allowed[1] = NET_CLIENT_SMTP_AUTH_SAFE;
+ self->auth_enabled = NET_CLIENT_SMTP_AUTH_ALL;
}
@@ -402,11 +532,7 @@ net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *pass
guint auth_mask;
/* calculate the possible authentication methods */
- if (net_client_is_encrypted(NET_CLIENT(client))) {
- auth_mask = client->auth_allowed[0] & auth_supported;
- } else {
- auth_mask = client->auth_allowed[1] & auth_supported;
- }
+ auth_mask = client->auth_enabled & auth_supported;
if (auth_mask == 0U) {
g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
@@ -415,7 +541,9 @@ net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *pass
g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
_("password required"));
} else {
/* first try authentication methods w/o password, then safe ones, and finally the plain-text
methods */
- if ((auth_mask & NET_CLIENT_SMTP_AUTH_GSSAPI) != 0U) {
+ if ((auth_mask & NET_CLIENT_SMTP_AUTH_OAUTH2) != 0U) {
+ result = net_client_smtp_auth_oauth2(client, user, passwd, error);
+ } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_GSSAPI) != 0U) {
result = net_client_smtp_auth_gssapi(client, user, error);
} else if ((auth_mask & NET_CLIENT_SMTP_AUTH_CRAM_SHA1) != 0U) {
result = net_client_smtp_auth_cram(client, G_CHECKSUM_SHA1, user, passwd, error);
@@ -437,7 +565,7 @@ net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *pass
static gboolean
net_client_smtp_auth_plain(NetClientSmtp *client, const gchar *user, const gchar *passwd, GError **error)
{
- gboolean result ;
+ gboolean result;
gchar *base64_buf;
base64_buf = net_client_auth_plain_calc(user, passwd);
@@ -552,6 +680,40 @@ net_client_smtp_auth_gssapi(NetClientSmtp G_GNUC_UNUSED *client, const gchar G_G
#endif /* HAVE_GSSAPI */
+#if defined(HAVE_OAUTH2)
+
+static gboolean
+net_client_smtp_auth_oauth2(NetClientSmtp *client, const gchar *user, const gchar *access_token, GError
**error)
+{
+ gboolean result;
+ gchar *base64_buf;
+
+ base64_buf = net_client_auth_oauth2_calc(user, access_token);
+ if (base64_buf != NULL) {
+ result = net_client_smtp_execute(client, "AUTH XOAUTH2 %s", NULL, error, base64_buf);
+ // FIXME - grab the JSON response on error
+ net_client_free_authstr(base64_buf);
+ } else {
+ result = FALSE;
+ }
+
+ return result;
+}
+
+#else
+
+/*lint -e{715,818} */
+static gboolean
+net_client_smtp_auth_oauth2(NetClientSmtp G_GNUC_UNUSED *client, const gchar G_GNUC_UNUSED *user,
+ const gchar G_GNUC_UNUSED *access_token, GError G_GNUC_UNUSED **error)
+{
+ g_assert_not_reached(); /* this should never happen! */
+ return FALSE; /* never reached, make gcc happy */
+}
+
+#endif /* HAVE_GSSAPI */
+
+
/* note: if supplied, last_reply is never NULL on success */
static gboolean
net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gchar **last_reply, GError **error,
...)
@@ -621,6 +783,10 @@ net_client_smtp_ehlo(NetClientSmtp *client, guint *auth_supported, gboolean *can
#if defined(HAVE_GSSAPI)
} else if (strcmp(auth[n], "GSSAPI") == 0) {
*auth_supported |= NET_CLIENT_SMTP_AUTH_GSSAPI;
+#endif
+#if defined (HAVE_OAUTH2)
+ } else if (strcmp(auth[n], "XOAUTH2") == 0) {
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_OAUTH2;
#endif
} else {
/* other auth methods are ignored for the time being
*/
diff --git a/libnetclient/net-client-smtp.h b/libnetclient/net-client-smtp.h
index 0adabdbc7..c26d61c1a 100644
--- a/libnetclient/net-client-smtp.h
+++ b/libnetclient/net-client-smtp.h
@@ -17,6 +17,7 @@
#include "net-client.h"
+#include "net-client-utils.h"
G_BEGIN_DECLS
@@ -45,30 +46,6 @@ enum _NetClientSmtpError {
};
-/** @name SMTP authentication methods
- * @{
- */
-/** RFC 4616 "PLAIN" authentication method. */
-#define NET_CLIENT_SMTP_AUTH_PLAIN 0x01U
-/** "LOGIN" authentication method. */
-#define NET_CLIENT_SMTP_AUTH_LOGIN 0x02U
-/** RFC 2195 "CRAM-MD5" authentication method. */
-#define NET_CLIENT_SMTP_AUTH_CRAM_MD5 0x04U
-/** RFC xxxx "CRAM-SHA1" authentication method. */
-#define NET_CLIENT_SMTP_AUTH_CRAM_SHA1 0x08U
-/** RFC 4752 "GSSAPI" authentication method. */
-#define NET_CLIENT_SMTP_AUTH_GSSAPI 0x10U
-/** Mask of all safe authentication methods, i.e. all methods which do not send the cleartext password. */
-#define NET_CLIENT_SMTP_AUTH_SAFE \
- (NET_CLIENT_SMTP_AUTH_CRAM_MD5 + NET_CLIENT_SMTP_AUTH_CRAM_SHA1 + NET_CLIENT_SMTP_AUTH_GSSAPI)
-/** Mask of all authentication methods. */
-#define NET_CLIENT_SMTP_AUTH_ALL \
- (NET_CLIENT_SMTP_AUTH_PLAIN + NET_CLIENT_SMTP_AUTH_LOGIN + NET_CLIENT_SMTP_AUTH_SAFE)
-/** Mask of all authentication methods which do not require a password. */
-#define NET_CLIENT_SMTP_AUTH_NO_PWD NET_CLIENT_SMTP_AUTH_GSSAPI
-/** @} */
-
-
/** @brief Delivery Status Notification mode
*
* See <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a> for a description of Delivery Status
Notifications (DSNs). The
@@ -99,6 +76,21 @@ enum _NetClientSmtpDsnMode {
typedef gssize (*NetClientSmtpSendCb)(gchar *buffer, gsize count, gpointer user_data, GError **error);
+/** @brief Probe a SMTP server
+ *
+ * @param host host name or IP address of the server to probe
+ * @param timeout_secs time-out in seconds
+ * @param result filled with the probe results
+ * @param cert_cb optional server certificate acceptance callback
+ * @param error filled with error information if probing fails
+ * @return TRUE if probing the passed server was successful, FALSE if not
+ *
+ * Probe the passed server by trying to connect to the standard ports (in this order) 587, 25 and 465.
+ */
+gboolean net_client_smtp_probe(const gchar *host, guint timeout_secs, NetClientProbeResult *result,
GCallback cert_cb,
+ GError **error);
+
+
/** @brief Create a new SMTP network client
*
* @param host host name or IP address to connect
@@ -112,16 +104,14 @@ NetClientSmtp *net_client_smtp_new(const gchar *host, guint16 port, NetClientCry
/** @brief Set allowed SMTP AUTH methods
*
* @param client SMTP network client object
- * @param encrypted set allowed methods for encrypted or unencrypted connections
- * @param allow_auth mask of allowed authentication methods
+ * @param auth_mode mask of allowed authentication methods
* @return TRUE on success or FALSE on error
*
- * Set the allowed authentication methods for the passed connection. The default is @ref
NET_CLIENT_SMTP_AUTH_ALL for encrypted and
- * @ref NET_CLIENT_SMTP_AUTH_SAFE for unencrypted connections, respectively.
+ * Set the allowed authentication methods for the passed connection. The default is to enable all
authentication methods.
*
* @note Call this function @em before calling net_client_smtp_connect().
*/
-gboolean net_client_smtp_allow_auth(NetClientSmtp *client, gboolean encrypted, guint allow_auth);
+gboolean net_client_smtp_set_auth_mode(NetClientSmtp *client, NetClientAuthMode auth_mode);
/** @brief Connect a SMTP network client
@@ -132,11 +122,12 @@ gboolean net_client_smtp_allow_auth(NetClientSmtp *client, gboolean encrypted, g
* @return TRUE on success or FALSE if the connection failed
*
* Connect the remote SMTP server, initialise the encryption if requested, and emit the @ref auth signal to
request authentication
- * information. Simply ignore the signal for an unauthenticated connection.
+ * information unless anonymous access has been configured by calling net_client_smtp_set_auth_mode().
*
- * The function will try only @em one authentication method supported by the server and enabled for the
current encryption state
- * (see net_client_smtp_allow_auth() and \ref NET_CLIENT_SMTP_AUTH_ALL etc.). The priority is, from highest
to lowest, GSSAPI (if
- * configured), CRAM-SHA1, CRAM-MD5, PLAIN or LOGIN.
+ * The function will try only @em one authentication method which is both supported by the server and
enabled by calling
+ * net_client_smtp_set_auth_mode(). The default is to try all methods, from highest to lowest, OAuth2 (if
configured), GSSAPI (if
+ * configured), CRAM-SHA1, CRAM-MD5, PLAIN, LOGIN or anonymous. It is up to the caller to ensure encryption
or a connection to
+ * @c localhost if one of the plain text methods shall be used.
*
* In order to shut down a successfully established connection, just call <tt>g_object_unref()</tt> on the
SMTP network client
* object.
@@ -243,6 +234,7 @@ void net_client_smtp_msg_free(NetClientSmtpMessage *smtp_msg);
* - PLAIN according to <a href="https://tools.ietf.org/html/rfc4616">RFC 4616</a>
* - LOGIN
* - GSSAPI according to <a href="https://tools.ietf.org/html/rfc4752">RFC 4752</a> (if configured with
gssapi support)
+ * - XOAUTH2 according to <a href="https://tools.ietf.org/html/rfc6749">RFC 6749</a> (if configured with
OAuth2 support)
* - STARTTLS encryption according to <a href="https://tools.ietf.org/html/rfc3207">RFC 3207</a>
* - Delivery Status Notifications (DSNs) according to <a href="https://tools.ietf.org/html/rfc3461">RFC
3461</a>
*/
diff --git a/libnetclient/net-client-utils.c b/libnetclient/net-client-utils.c
index 8b6e252a3..04b8c422a 100644
--- a/libnetclient/net-client-utils.c
+++ b/libnetclient/net-client-utils.c
@@ -26,6 +26,24 @@
#endif /* HAVE_GSSAPI */
+gboolean
+net_client_host_reachable(const gchar *host, GError **error)
+{
+ GSocketConnectable *remote_address;
+ GNetworkMonitor *monitor;
+ gboolean success;
+
+ g_return_val_if_fail(host != NULL, FALSE);
+
+ remote_address = g_network_address_new(host, 1024U);
+ monitor = g_network_monitor_get_default();
+ success = g_network_monitor_can_reach(monitor, remote_address, NULL, error);
+ g_object_unref(remote_address);
+
+ return success;
+}
+
+
#if defined(HAVE_GSSAPI)
struct _NetClientGssCtx {
@@ -137,7 +155,7 @@ net_client_gss_ctx_new(const gchar *service, const gchar *host, const gchar *use
OM_uint32 maj_stat;
OM_uint32 min_stat;
- g_return_val_if_fail((service != NULL) && (host != NULL), NULL);
+ g_return_val_if_fail((service != NULL) && (host != NULL) && (user != NULL), NULL);
gss_ctx = g_new0(NetClientGssCtx, 1U);
service_str = g_strconcat(service, "@", host, NULL);
@@ -333,3 +351,21 @@ gss_error_string(OM_uint32 err_maj, OM_uint32 err_min)
}
#endif /* HAVE_GSSAPI */
+
+
+#if defined(HAVE_OAUTH2)
+
+gchar *
+net_client_auth_oauth2_calc(const gchar *user, const gchar *access_token)
+{
+ gchar *buffer;
+ gchar *result;
+
+ g_return_val_if_fail((user != NULL) && (access_token != NULL), NULL);
+ buffer = g_strdup_printf("user=%s\001auth=Bearer %s\001\001", user, access_token);
+ result = g_base64_encode(buffer, strlen(buffer));
+ g_free(buffer);
+ return result;
+}
+
+#endif /* HAVE_OAUTH2 */
diff --git a/libnetclient/net-client-utils.h b/libnetclient/net-client-utils.h
index 88e1a0ffa..b521d4c62 100644
--- a/libnetclient/net-client-utils.h
+++ b/libnetclient/net-client-utils.h
@@ -18,6 +18,7 @@
#include "config.h"
#include <gio/gio.h>
+#include "net-client.h"
G_BEGIN_DECLS
@@ -30,6 +31,25 @@ typedef struct _NetClientGssCtx NetClientGssCtx;
#endif /* HAVE_GSSAPI */
+typedef struct _NetClientProbeResult NetClientProbeResult;
+
+/** @brief Server probe results */
+struct _NetClientProbeResult {
+ guint16 port; /**< The port where the server
listens. */
+ NetClientCryptMode crypt_mode; /**< The encryption mode the server supports for the
returned port. */
+ NetClientAuthMode auth_mode; /**< The authentication modes the server supports for
the port and encryption mode. */
+};
+
+
+/** @brief Check is a host is resolvable and reachable
+ *
+ * @param host host name or IP address
+ * @param error filled with error information on error
+ * @return TRUE if the passed host name is resolvable and reachable
+ */
+gboolean net_client_host_reachable(const gchar *host, GError **error);
+
+
/** @brief Calculate a CRAM authentication string
*
* @param base64_challenge base64-encoded challenge sent by the server
@@ -143,9 +163,27 @@ void net_client_gss_ctx_free(NetClientGssCtx *gss_ctx);
#endif /* HAVE_GSSAPI */
+#if defined(HAVE_OAUTH2)
+
+/** @brief Calculate a OAuth2 authentication string
+ *
+ * @param user user name
+ * @param access_token access token
+ * @return a newly allocated string containing the base64-encoded authentication
+ *
+ * This helper function calculates the the base64-encoded authentication string from the user name and the
access token. The caller
+ * shall free the returned string when it is not needed any more.
+ *
+ * \sa <a href="https://developers.google.com/gmail/imap/xoauth2-protocol">Google Developers: OAuth 2.0
Mechanism</a>.
+ */
+gchar *net_client_auth_oauth2_calc(const gchar *user, const gchar *access_token)
+ G_GNUC_MALLOC;
+
+#endif /* HAVE_OAUTH2 */
+
/** @file
*
- * This module implements authentication-related helper functions for the network client library.
+ * This module implements probing and authentication-related helper functions for the network client library.
*/
diff --git a/libnetclient/net-client.h b/libnetclient/net-client.h
index e9bc7b57f..ab6102264 100644
--- a/libnetclient/net-client.h
+++ b/libnetclient/net-client.h
@@ -38,6 +38,7 @@ struct _NetClientClass {
typedef enum _NetClientError NetClientError;
typedef enum _NetClientCryptMode NetClientCryptMode;
+typedef enum _NetClientAuthMode NetClientAuthMode;
/** @brief Encryption mode */
@@ -49,6 +50,15 @@ enum _NetClientCryptMode {
};
+/** @brief Authentication mode */
+enum _NetClientAuthMode {
+ NET_CLIENT_AUTH_ANONYMOUS = 1, /**< No authentication required (e.g. local service,
user cert authentication). */
+ NET_CLIENT_AUTH_USER_PASS = 2, /**< Authenticate with user name and password. */
+ NET_CLIENT_AUTH_KERBEROS = 4, /**< Authenticate with user name and Kerberos ticket.
*/
+ NET_CLIENT_AUTH_OAUTH2 = 8 /**< OAuth2 authentication (RFC 6749). */
+};
+
+
/** @brief Error codes */
enum _NetClientError {
NET_CLIENT_ERROR_CONNECTED = 1, /**< The client is already connected. */
@@ -59,7 +69,8 @@ enum _NetClientError {
NET_CLIENT_ERROR_LINE_TOO_LONG, /**< The line is too long. */
NET_CLIENT_ERROR_GNUTLS, /**< A GnuTLS error occurred (bad certificate
or key data, or internal error). */
NET_CLIENT_ERROR_CERT_KEY_PASS, /**< GnuTLS could not decrypt the user certificate's
private key. */
- NET_CLIENT_ERROR_GSSAPI /**< A GSSAPI error occurred. */
+ NET_CLIENT_ERROR_GSSAPI, /**< A GSSAPI error occurred. */
+ NET_CLIENT_PROBE_FAILED /**< Probing a server failed. */
};
@@ -335,8 +346,9 @@ gboolean net_client_can_read(NetClient *client);
* @code gchar **get_auth(NetClient *client, gboolean need_passwd, gpointer user_data) @endcode
Authentication is required by the
* remote server. The signal handler shall return a NULL-terminated array of strings, containing the user
name in the first and
* the password in the second element. If the parameter @em need_passwd is FALSE, no password is required
(e.g. for kerberos
- * ticket-based authentication). In this case, the password element must be present in the reply, but it
is ignored an may be
- * NULL. The strings are wiped and freed when they are not needed any more. Return NULL if no
authentication is required.
+ * ticket-based or for OAuth2 authentication). In this case, the password element must be present in the
reply, but it is ignored
+ * an may be NULL. The strings are wiped and freed when they are not needed any more. Return NULL if no
authentication is
+ * required.
*/
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]