[balsa/rework-auth] Rework authentication, server probing and the wizard
- From: Albrecht Dreß <albrecht src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa/rework-auth] Rework authentication, server probing and the wizard
- Date: Sun, 11 Sep 2022 15:49:29 +0000 (UTC)
commit 538078a7c5ee419a5bec6f3ecdf5dea663c00417
Author: Albrecht Dreß <albrecht dress netcologne de>
Date: Sun Sep 11 17:47:19 2022 +0200
Rework authentication, server probing and the wizard
This patch aims at a unification of the mail (POP3, IMAP, SMTP) server
configurations, adds the capability of server probing, and simplifies the
initial server setup in the wizard. This includes preferring implicit TLS over
STARTTLS (see issue #55). This wizard tries to guess servers from DNS SRV
records (RFC 6186) or from "typical" names derived from the email address.
- libnetclient:
- net-client.h: add auth modes enum (none; user/pass; Kerberos; more to come);
add probing error code
- net-client-utils.[ch]: define probe results struct; add helper functions
net_client_host_reachable(), net_client_host_only(),
net_client_auth_anonymous_token()
- net-client-pop.[ch]: do not export POP auth modes; replace
net_client_pop_allow_auth() by net_client_pop_set_auth_mode(); add
net_client_pop_probe(); add SASL "ANONYMOUS" support
- net-client-smtp.[hc]: do not export SMTP auth modes; replace
net_client_smtp_allow_auth() by net_client_smtp_set_auth_mode();
net_client_smtp_probe(); be more strict checking the RFC 5321 server
response codes
- README: update testing comments re. INetSim
- libbalsa/imap:
- imap_private.h: _ImapMboxHandle: add auth_mode, remove enable_anonymous
- auth-cram.c, auth-gssapi.c: change "auth" signal value
- imap-handle.[ch]: remove IMAP_OPT_ANONYMOUS; add imap_handle_set_auth_mode()
and imap_server_probe()
- imap-auth.c; rework authentication code; change "auth" signal value
- imap_tst.c: use imap_handle_set_auth_mode() instead of
imap_handle_set_option()
- libbalsa:
- imap-server.c: replace imap_handle_set_option() by
imap_handle_set_auth_mode()
- mailbox_pop3.c: replace net_client_pop_allow_auth() by
net_client_pop_set_auth_mode()
- smtp-server.c: set log domain to libbalsa-server
- server.[ch]: implement libbalsa_server_[gs]et_auth_mode(); remove deprecated
libbalsa_server_[gs]et_try_anonymous(); store auth mode in the config, clean
deprecated keys; update libbalsa_server_get_auth() cb function; set log
domain to libbalsa-server
- server-config.c: refactor for auth modes; add probe button and callbacks
- libinit_balsa:
- assistant_page_server.[ch]: add wizard page for configuring the mail server
based upon the best guess from the user's mail address
- assistant_page_user.[ch]: remove all server-related settings
- assistant_init.c: add new wizard page; fix extremely wide wizard pages
- Makefile.am, meson.build: add server page sources
Signed-off-by: Albrecht Dreß <albrecht dress netcologne de>
libbalsa/imap-server.c | 2 +-
libbalsa/imap/auth-cram.c | 2 +-
libbalsa/imap/auth-gssapi.c | 2 +-
libbalsa/imap/imap-auth.c | 53 ++--
libbalsa/imap/imap-handle.c | 125 +++++++-
libbalsa/imap/imap-handle.h | 5 +-
libbalsa/imap/imap_private.h | 3 +-
libbalsa/imap/imap_tst.c | 2 +-
libbalsa/libhtmlfilter.la | 41 +++
libbalsa/libhtmlfilter_la-html-filter.lo | 12 +
libbalsa/mailbox_pop3.c | 11 +-
libbalsa/server-config.c | 222 +++++++++++---
libbalsa/server.c | 70 +++--
libbalsa/server.h | 10 +-
libbalsa/smtp-server.c | 7 +
libinit_balsa/Makefile.am | 2 +
libinit_balsa/assistant_init.c | 3 +
libinit_balsa/assistant_page_server.c | 503 +++++++++++++++++++++++++++++++
libinit_balsa/assistant_page_server.h | 55 ++++
libinit_balsa/assistant_page_user.c | 165 +---------
libinit_balsa/assistant_page_user.h | 12 -
libinit_balsa/meson.build | 2 +
libnetclient/README | 16 +-
libnetclient/net-client-pop.c | 286 +++++++++++++-----
libnetclient/net-client-pop.h | 77 ++---
libnetclient/net-client-smtp.c | 338 ++++++++++++++-------
libnetclient/net-client-smtp.h | 59 ++--
libnetclient/net-client-utils.c | 55 +++-
libnetclient/net-client-utils.h | 53 +++-
libnetclient/net-client.h | 22 +-
30 files changed, 1660 insertions(+), 555 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/auth-cram.c b/libbalsa/imap/auth-cram.c
index 6b11f72a2..18b9a18d3 100644
--- a/libbalsa/imap/auth-cram.c
+++ b/libbalsa/imap/auth-cram.c
@@ -42,7 +42,7 @@ imap_auth_cram(ImapMboxHandle* handle)
if (!imap_mbox_handle_can_do(handle, IMCAP_ACRAM_MD5))
return IMAP_AUTH_UNAVAIL;
- g_signal_emit_by_name(handle->sio, "auth", TRUE, &auth_data);
+ g_signal_emit_by_name(handle->sio, "auth", NET_CLIENT_AUTH_USER_PASS, &auth_data);
if((auth_data == NULL) || (auth_data[0] == NULL) || (auth_data[1] == NULL)) {
imap_mbox_handle_set_msg(handle, _("Authentication cancelled"));
g_strfreev(auth_data);
diff --git a/libbalsa/imap/auth-gssapi.c b/libbalsa/imap/auth-gssapi.c
index e5bcd6973..0a6323f9c 100644
--- a/libbalsa/imap/auth-gssapi.c
+++ b/libbalsa/imap/auth-gssapi.c
@@ -125,7 +125,7 @@ imap_auth_gssapi(ImapMboxHandle* handle)
return IMAP_AUTH_UNAVAIL;
}
- g_signal_emit_by_name(handle->sio, "auth", FALSE, &auth_data);
+ g_signal_emit_by_name(handle->sio, "auth", NET_CLIENT_AUTH_KERBEROS, &auth_data);
if((auth_data == NULL) || (auth_data[0] == NULL)) {
imap_mbox_handle_set_msg(handle, _("User name required, authentication cancelled"));
g_strfreev(auth_data);
diff --git a/libbalsa/imap/imap-auth.c b/libbalsa/imap/imap-auth.c
index d0f098540..a76653365 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,17 +56,21 @@ 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 (!imap_mbox_is_connected(handle)) { return IMAP_AUTH_CANCELLED; }
- 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_KERBEROS) != 0U) {
+ r = imap_auth_gssapi(handle);
+ } else if ((handle->auth_mode & NET_CLIENT_AUTH_NONE_ANON) != 0U) {
+ r = imap_auth_anonymous(handle);
+ } else {
+ 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);
+ } else if (r == IMAP_AUTH_UNAVAIL) {
+ 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;
}
@@ -86,7 +87,7 @@ imap_auth_login(ImapMboxHandle* handle)
if (imap_mbox_handle_can_do(handle, IMCAP_LOGINDISABLED))
return IMAP_AUTH_UNAVAIL;
- g_signal_emit_by_name(handle->sio, "auth", TRUE, &auth_data);
+ g_signal_emit_by_name(handle->sio, "auth", NET_CLIENT_AUTH_USER_PASS, &auth_data);
if((auth_data == NULL) || (auth_data[0] == NULL) || (auth_data[1] == NULL)) {
imap_mbox_handle_set_msg(handle, _("Authentication cancelled"));
g_strfreev(auth_data);
@@ -176,7 +177,7 @@ getmsg_plain(ImapMboxHandle *h, char **retmsg, int *retmsglen)
gchar **auth_data;
gboolean result;
- g_signal_emit_by_name(h->sio, "auth", TRUE, &auth_data);
+ g_signal_emit_by_name(h->sio, "auth", NET_CLIENT_AUTH_USER_PASS, &auth_data);
if ((auth_data == NULL) || (auth_data[0] == NULL) || (auth_data[1] == NULL)) {
result = FALSE;
} else {
@@ -201,31 +202,19 @@ imap_auth_plain(ImapMboxHandle* handle)
/* =================================================================== */
-/* SASL ANONYMOUS RFC-2245 */
+/* SASL ANONYMOUS RFC-4505 */
/* =================================================================== */
static gboolean
getmsg_anonymous(ImapMboxHandle *h, char **retmsg, int *retmsglen)
{
- gchar **auth_data;
- gboolean result;
-
- g_signal_emit_by_name(h->sio, "auth", FALSE, &auth_data);
- if((auth_data == NULL) || (auth_data[0] == NULL)) {
- result = FALSE;
- } else {
- *retmsg = g_base64_encode((const guchar *) auth_data[0], strlen(auth_data[0]));
- *retmsglen = strlen(*retmsg);
- result = TRUE;
- }
- g_strfreev(auth_data);
- return result;
+ *retmsg = net_client_auth_anonymous_token();
+ *retmsglen = strlen(*retmsg);
+ return TRUE;
}
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 37181d262..1dc471aad 100644
--- a/libbalsa/imap/imap-handle.c
+++ b/libbalsa/imap/imap-handle.c
@@ -80,6 +80,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;
handle->idle_state = IDLE_INACTIVE;
handle->enable_idle = 1;
@@ -166,7 +167,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;
@@ -606,15 +606,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"
};
@@ -4428,3 +4439,107 @@ 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;
+ gboolean retval = FALSE;
+ gint check_id;
+
+ /* paranoia check */
+ g_return_val_if_fail((host != NULL) && (result != NULL), FALSE);
+
+ host_only = net_client_host_only(host);
+
+ 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_NONE_ANON;
+ }
+ 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;
+ }
+ 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..889cb4b98 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 {
@@ -88,7 +89,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 +135,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 +215,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..01997f031 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_NONE_ANON);
if(TestContext.compress)
imap_handle_set_option(h, IMAP_OPT_COMPRESS, TRUE);
diff --git a/libbalsa/libhtmlfilter.la b/libbalsa/libhtmlfilter.la
new file mode 100644
index 000000000..2c7219fb7
--- /dev/null
+++ b/libbalsa/libhtmlfilter.la
@@ -0,0 +1,41 @@
+# libhtmlfilter.la - a libtool library file
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-14
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='libhtmlfilter.so'
+
+# Names of this library.
+library_names='libhtmlfilter.so libhtmlfilter.so libhtmlfilter.so'
+
+# The name of the static archive.
+old_library='libhtmlfilter.a'
+
+# Linker flags that cannot go in dependency_libs.
+inherited_linker_flags=' -pthread'
+
+# Libraries that this one depends upon.
+dependency_libs=' -lgmime-3.0 -lgthread-2.0 -lgnutls -lfribidi -lical -licalss -licalvcal -lwebkit2gtk-4.0
-lsoup-2.4 -ljavascriptcoregtk-4.0 -lgpgme -lassuan /usr/lib/x86_64-linux-gnu/libgpg-error.la -lxapp
/usr/lib/x86_64-linux-gnu/libsqlite3.la -L/usr/lib/x86_64-linux-gnu/mit-krb5 -lgssapi_krb5 -lkrb5 -lk5crypto
-lcom_err -lgcr-ui-3 -lgcr-base-3 -lgck-1 -lp11-kit -lcompface -lgtksourceview-4 -lcanberra-gtk3 -lX11
-lcanberra -lsecret-1 -lgspell-1 -lgtk-3 -lgdk-3 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -latk-1.0
-lcairo-gobject -lcairo -lgdk_pixbuf-2.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -lenchant-2 -lldap -llber
-lresolv'
+
+# Names of additional weak libraries provided by this library
+weak_library_names=''
+
+# Version information for libhtmlfilter.
+current=0
+age=0
+revision=0
+
+# Is this an already installed library?
+installed=no
+
+# Should we warn about portability when linking against -modules?
+shouldnotlink=yes
+
+# Files to dlopen/dlpreopen
+dlopen=''
+dlpreopen=''
+
+# Directory that this library needs to be installed in:
+libdir='/usr/local/lib/balsa'
diff --git a/libbalsa/libhtmlfilter_la-html-filter.lo b/libbalsa/libhtmlfilter_la-html-filter.lo
new file mode 100644
index 000000000..2ed54b82c
--- /dev/null
+++ b/libbalsa/libhtmlfilter_la-html-filter.lo
@@ -0,0 +1,12 @@
+# libhtmlfilter_la-html-filter.lo - a libtool object file
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-14
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# Name of the PIC object.
+pic_object='.libs/libhtmlfilter_la-html-filter.o'
+
+# Name of the non-PIC object
+non_pic_object='libhtmlfilter_la-html-filter.o'
+
diff --git a/libbalsa/mailbox_pop3.c b/libbalsa/mailbox_pop3.c
index c2116a291..08325310b 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/server-config.c b/libbalsa/server-config.c
index 37360213d..a04c28cf3 100644
--- a/libbalsa/server-config.c
+++ b/libbalsa/server-config.c
@@ -24,9 +24,18 @@
#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"
+#ifdef G_LOG_DOMAIN
+# undef G_LOG_DOMAIN
+#endif
+#define G_LOG_DOMAIN "libbalsa-server"
+
+
struct _LibBalsaServerCfg {
GtkNotebook parent;
@@ -35,8 +44,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 +59,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 +70,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 +102,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 +119,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 +184,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 +234,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 +295,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 +337,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 +414,58 @@ 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, "pop3") == 0) || (strcmp(protocol, "imap") == 0)) {
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo_box), "1", _("anonymous access")); /*
RFC 4505 */
+ } else {
+ 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
+
+ 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_NONE_ANON);
+ 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_NONE_ANON) && (*gtk_entry_get_text(GTK_ENTRY(server_cfg->username))
== '\0')) {
server_cfg->cfg_valid = FALSE;
}
@@ -428,3 +492,85 @@ 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_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_NONE_ANON;
+ }
+ 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..6a37a109c 100644
--- a/libbalsa/server.c
+++ b/libbalsa/server.c
@@ -36,6 +36,13 @@
#include "net-client-utils.h"
#include <glib/gi18n.h>
+
+#ifdef G_LOG_DOMAIN
+# undef G_LOG_DOMAIN
+#endif
+#define G_LOG_DOMAIN "libbalsa-server"
+
+
#if defined(HAVE_LIBSECRET)
static const SecretSchema server_schema = {
"org.gnome.Balsa.NetworkPassword", SECRET_SCHEMA_NONE,
@@ -71,7 +78,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 +295,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 +313,10 @@ libbalsa_server_load_security_config(LibBalsaServer *server)
default:
priv->security = NET_CLIENT_CRYPT_STARTTLS;
}
+ libbalsa_conf_clean_key("SSL");
}
}
}
-
}
@@ -444,7 +452,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_NONE_ANON;
+ }
+ libbalsa_conf_clean_key("Anonymous");
+ }
priv->remember_passwd = libbalsa_conf_get_bool("RememberPasswd=false");
priv->passwd = libbalsa_free_password(priv->passwd);
@@ -490,7 +504,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);
@@ -546,25 +560,30 @@ libbalsa_server_connect_signals(LibBalsaServer * server, GCallback cb,
gchar **
-libbalsa_server_get_auth(NetClient *client,
- gboolean need_passwd,
- gpointer user_data)
+libbalsa_server_get_auth(NetClient *client,
+ NetClientAuthMode mode,
+ gpointer user_data)
{
LibBalsaServer *server = LIBBALSA_SERVER(user_data);
LibBalsaServerPrivate *priv = libbalsa_server_get_instance_private(server);
gchar **result = NULL;
- 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)) {
+ g_debug("%s: %p %d %p: encrypted = %d", __func__, client, mode, user_data,
net_client_is_encrypted(client));
+ if (priv->auth_mode != NET_CLIENT_AUTH_NONE_ANON) {
result = g_new0(gchar *, 3U);
result[0] = g_strdup(priv->user);
- if (need_passwd) {
- if ((priv->passwd != NULL) && (priv->passwd[0] != '\0')) {
- result[1] = g_strdup(priv->passwd);
- } else {
- result[1] = lbs_get_password(server, NULL);
- }
+ switch (mode) {
+ case NET_CLIENT_AUTH_USER_PASS:
+ if ((priv->passwd != NULL) && (priv->passwd[0] != '\0')) {
+ result[1] = g_strdup(priv->passwd);
+ } else {
+ result[1] = lbs_get_password(server, NULL);
+ }
+ break;
+ case NET_CLIENT_AUTH_KERBEROS:
+ break; /* only user name required */
+ default:
+ g_assert_not_reached();
}
}
return result;
@@ -630,7 +649,6 @@ libbalsa_server_test_can_reach_full(LibBalsaServer * server,
LibBalsaServerPrivate *priv = libbalsa_server_get_instance_private(server);
CanReachInfo *info;
gchar *host;
- gchar *colon;
GNetworkMonitor *monitor;
GSocketConnectable *address;
@@ -641,11 +659,7 @@ libbalsa_server_test_can_reach_full(LibBalsaServer * server,
monitor = g_network_monitor_get_default();
- host = g_strdup(priv->host);
- colon = strchr(host, ':');
- if (colon != NULL) {
- colon[0] = '\0';
- }
+ host = net_client_host_only(priv->host);
address = g_network_address_new(host, 0);
g_free(host);
g_network_monitor_can_reach_async(monitor, address, NULL,
@@ -735,14 +749,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 +827,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..5dc4cc5c5 100644
--- a/libbalsa/server.h
+++ b/libbalsa/server.h
@@ -64,9 +64,9 @@ void libbalsa_server_save_config(LibBalsaServer * server);
/* NetClient related signal handlers */
-gchar **libbalsa_server_get_auth(NetClient *client,
- gboolean need_passwd,
- gpointer user_data);
+gchar **libbalsa_server_get_auth(NetClient *client,
+ NetClientAuthMode mode,
+ gpointer user_data);
gboolean libbalsa_server_check_cert(NetClient *client,
GTlsCertificate *peer_cert,
GTlsCertificateFlags errors,
@@ -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/libbalsa/smtp-server.c b/libbalsa/smtp-server.c
index a0ca8dd7c..22e3ec695 100644
--- a/libbalsa/smtp-server.c
+++ b/libbalsa/smtp-server.c
@@ -35,6 +35,13 @@
# include "macosx-helpers.h"
#endif
+
+#ifdef G_LOG_DOMAIN
+# undef G_LOG_DOMAIN
+#endif
+#define G_LOG_DOMAIN "libbalsa-server"
+
+
struct _LibBalsaSmtpServer {
LibBalsaServer server;
diff --git a/libinit_balsa/Makefile.am b/libinit_balsa/Makefile.am
index 0ed6c74f0..9185c69cc 100644
--- a/libinit_balsa/Makefile.am
+++ b/libinit_balsa/Makefile.am
@@ -7,6 +7,8 @@ libinit_balsa_a_SOURCES = \
assistant_page_directory.h \
assistant_page_finish.c \
assistant_page_finish.h \
+ assistant_page_server.c \
+ assistant_page_server.h \
assistant_page_user.c \
assistant_page_user.h \
assistant_page_welcome.c \
diff --git a/libinit_balsa/assistant_init.c b/libinit_balsa/assistant_init.c
index bce6509ce..fbffc5e3a 100644
--- a/libinit_balsa/assistant_init.c
+++ b/libinit_balsa/assistant_init.c
@@ -32,6 +32,7 @@
#include "assistant_page_welcome.h"
#include "assistant_page_user.h"
+#include "assistant_page_server.h"
#include "assistant_page_directory.h"
#include "assistant_page_defclient.h"
#include "assistant_page_finish.h"
@@ -111,6 +112,7 @@ balsa_initdruid(GtkAssistant * assistant)
balsa_druid_page_welcome(assistant);
balsa_druid_page_user(assistant);
+ balsa_druid_page_server(assistant);
balsa_druid_page_directory(assistant);
balsa_druid_page_defclient(assistant);
balsa_druid_page_finish(assistant);
@@ -132,6 +134,7 @@ balsa_init_begin(void)
assistant = gtk_assistant_new();
gtk_window_set_title(GTK_WINDOW(assistant), _("Configure Balsa"));
+ gtk_window_set_default_size(GTK_WINDOW(assistant), 1, 1);
balsa_initdruid(GTK_ASSISTANT(assistant));
gtk_widget_show_all(assistant);
diff --git a/libinit_balsa/assistant_page_server.c b/libinit_balsa/assistant_page_server.c
new file mode 100644
index 000000000..03f771e9a
--- /dev/null
+++ b/libinit_balsa/assistant_page_server.c
@@ -0,0 +1,503 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ * Copyright (C) 1997-2022 Stuart Parmenter and others,
+ * See the file AUTHORS for a list.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+#include "assistant_page_server.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+#include "imap/imap-handle.h"
+#include "imap-server.h"
+#include "smtp-server.h"
+#include "server.h"
+#include "balsa-app.h"
+#include "save-restore.h"
+#include "net-client-utils.h"
+#include "net-client-pop.h"
+#include "net-client-smtp.h"
+
+
+static void balsa_druid_page_server_init(BalsaDruidPageServer *server,
+ GtkWidget *page,
+ GtkAssistant *druid);
+static void balsa_druid_page_server_prepare(GtkAssistant *druid,
+ GtkWidget
*page,
+ BalsaDruidPageServer
*server);
+static void balsa_druid_page_server_next(GtkAssistant *druid,
+ GtkWidget *page,
+ BalsaDruidPageServer
*server);
+static void on_server_type_changed(GtkComboBox *widget,
+ gpointer user_data);
+static gchar **guess_servers(const gchar *mail_address)
+ G_GNUC_WARN_UNUSED_RESULT;
+static gchar *server_from_srv(GResolver *resolver,
+ const gchar * const *service,
+ const gchar *domain)
+ G_GNUC_WARN_UNUSED_RESULT;
+static gchar *server_by_name(GResolver *resolver,
+ const gchar * const *prefix,
+ const gchar *domain)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+
+static void
+balsa_druid_page_server_init(BalsaDruidPageServer *server, GtkWidget *page, GtkAssistant *druid)
+{
+ static const char *header =
+ N_("The pre-set server configuration is a best guess based "
+ "upon your email address. Balsa will automatically choose "
+ "optimum settings for encryption and user name, and select "
+ "password based authentication. Please note that probing the "
+ "server capabilities when you click “Next” will take a few seconds.\n"
+ "Check the “Edit \342\217\265 Preferences \342\217\265 Settings "
+ "\342\217\265 Mail options” menu item if you need to fine-tune them.");
+ static const char* server_types[] = { "IMAP", "POP3", NULL };
+ static const char* remember_passwd[] = {
+ N_("Yes, remember it"), N_("No, type it in every time"), NULL };
+ GtkGrid *grid;
+ GtkLabel *label;
+ gchar *preset;
+ int row = 0;
+
+ label = GTK_LABEL(gtk_label_new(_(header)));
+ gtk_label_set_line_wrap(label, TRUE);
+ gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(label), FALSE, TRUE, 0);
+
+ grid = GTK_GRID(gtk_grid_new());
+ gtk_grid_set_row_spacing(grid, 2);
+ gtk_grid_set_column_spacing(grid, 5);
+
+ balsa_init_add_grid_option(grid, row++, _("_Type of mail server:"),
+ server_types, druid, &(server->incoming_type));
+ g_signal_connect(server->incoming_type, "changed", G_CALLBACK(on_server_type_changed), server);
+
+ balsa_init_add_grid_entry(grid, row++,
+ _("Name of mail server for incoming _mail:"),
+ "", /* no guessing here */
+ NULL, druid, page, &(server->incoming_srv));
+
+ balsa_init_add_grid_entry(grid, row++, _("Your email _login name:"),
+ g_get_user_name(),
+ NULL, druid, page, &(server->login));
+ balsa_init_add_grid_entry(grid, row++, _("Your _password:"),
+ "",
+ NULL, druid, page, &(server->passwd));
+ gtk_entry_set_visibility(GTK_ENTRY(server->passwd), FALSE);
+ /* separator line here */
+
+ preset = "localhost:25";
+ balsa_init_add_grid_entry(grid, row++, _("_SMTP Server:"), preset,
+ NULL, druid, page, &(server->smtp));
+
+ balsa_init_add_grid_option(grid, row++,
+ _("_Remember your password:"),
+ remember_passwd, druid,
+ &(server->remember_passwd));
+
+ gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(grid), FALSE, FALSE, 3);
+
+ server->need_set = FALSE;
+}
+
+
+static void
+on_server_type_changed(GtkComboBox *widget, gpointer user_data)
+{
+ BalsaDruidPageServer *server = (BalsaDruidPageServer *) user_data;
+ const gchar **servers;
+ int idx;
+
+ servers = g_object_get_data(G_OBJECT(widget), "SERVERS");
+ idx = 1 + gtk_combo_box_get_active(widget);
+ gtk_entry_set_text(GTK_ENTRY(server->incoming_srv),
+ ((servers != NULL) && (servers[idx] != NULL)) ? servers[idx] : "");
+}
+
+
+void
+balsa_druid_page_server(GtkAssistant * druid)
+{
+ BalsaDruidPageServer *server;
+
+ server = g_new0(BalsaDruidPageServer, 1);
+ server->page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_assistant_append_page(druid, server->page);
+ gtk_assistant_set_page_title(druid, server->page, _("Server Settings"));
+ balsa_druid_page_server_init(server, server->page, druid);
+ g_signal_connect(druid, "prepare", G_CALLBACK(balsa_druid_page_server_prepare), server);
+ g_object_weak_ref(G_OBJECT(druid), (GWeakNotify) g_free, server);
+}
+
+
+static void
+balsa_druid_page_server_prepare(GtkAssistant *druid, GtkWidget *page, BalsaDruidPageServer *server)
+{
+ if (page != server->page) {
+ if (server->need_set) {
+ balsa_druid_page_server_next(druid, page, server);
+ server->need_set = FALSE;
+ }
+ return;
+ }
+
+ g_object_set_data(G_OBJECT(server->incoming_type), "SERVERS", NULL);
+ if (balsa_app.current_ident != NULL) {
+ InternetAddress *ia;
+ const gchar *address;
+
+ ia = libbalsa_identity_get_address(balsa_app.current_ident); /* should never be NULL */
+ address = internet_address_mailbox_get_addr(INTERNET_ADDRESS_MAILBOX(ia));
+ if (address != NULL) {
+ const gchar *at_sign;
+
+ at_sign = strchr(address, '@');
+ if (at_sign != NULL) {
+ gchar *login;
+ gchar **servers;
+
+ /* wild guess: login is the part up to the '@' in the mail address */
+ login = g_strndup(address, at_sign - address);
+ gtk_entry_set_text(GTK_ENTRY(server->login), login);
+ g_free(login);
+
+ /* try to guess proper servers */
+ servers = guess_servers(address);
+ if (servers != NULL) {
+ g_object_set_data_full(G_OBJECT(server->incoming_type), "SERVERS",
servers, (GDestroyNotify) g_strfreev);
+ if (servers[0] != NULL) {
+ gtk_entry_set_text(GTK_ENTRY(server->smtp), servers[0]);
+ }
+ gtk_combo_box_set_active(GTK_COMBO_BOX(server->incoming_type), -1);
/* force update */
+ gtk_combo_box_set_active(GTK_COMBO_BOX(server->incoming_type),
(servers[1] != NULL) ? 0 : 1);
+ }
+ }
+ }
+ }
+
+ gtk_assistant_set_page_complete(druid, page, TRUE);
+ gtk_widget_grab_focus(server->incoming_srv);
+ server->need_set = TRUE;
+}
+
+
+static void
+create_pop3_mbx(const gchar *host, const gchar *login, const gchar *passwd, gboolean save_pwd)
+{
+ gchar *host_only;
+ gchar *host_port;
+ NetClientProbeResult probe_res;
+ LibBalsaMailboxPOP3 *mailbox_pop3;
+ LibBalsaMailbox *mailbox;
+ LibBalsaServer *server;
+ GError *error = NULL;
+
+ host_only = net_client_host_only(host);
+ if (net_client_pop_probe(host_only, 5, &probe_res, G_CALLBACK(libbalsa_server_check_cert), &error)) {
+ host_port = g_strdup_printf("%s:%hu", host_only, probe_res.port);
+ g_debug("probe result for server %s: port %hu, crypt mode %d", host, probe_res.port,
probe_res.crypt_mode);
+ } else {
+ g_debug("failed to probe server %s, fall back to POP3S: %s", host, error->message);
+ host_port = g_strdup_printf("%s:995", host_only);
+ probe_res.crypt_mode = NET_CLIENT_CRYPT_ENCRYPTED;
+ }
+ g_clear_error(&error);
+
+ mailbox_pop3 = libbalsa_mailbox_pop3_new();
+ mailbox = LIBBALSA_MAILBOX(mailbox_pop3);
+ server = LIBBALSA_MAILBOX_REMOTE_GET_SERVER(mailbox);
+ libbalsa_server_set_host(server, host_port, probe_res.crypt_mode);
+ libbalsa_server_set_username(server, login);
+ libbalsa_server_set_password(server, passwd, FALSE);
+ libbalsa_server_set_auth_mode(server, NET_CLIENT_AUTH_USER_PASS);
+ libbalsa_server_set_remember_password(server, save_pwd);
+ libbalsa_mailbox_set_name(mailbox, host_only);
+
+ libbalsa_mailbox_pop3_set_check(mailbox_pop3, TRUE);
+ libbalsa_mailbox_pop3_set_disable_apop(mailbox_pop3, FALSE);
+ libbalsa_mailbox_pop3_set_delete_from_server(mailbox_pop3, TRUE);
+ libbalsa_mailbox_pop3_set_filter(mailbox_pop3, FALSE);
+ libbalsa_mailbox_pop3_set_filter_cmd(mailbox_pop3, "procmail -f -");
+ config_mailbox_add(mailbox, NULL);
+
+ g_free(host_only);
+ g_free(host_port);
+}
+
+
+static void
+create_imap_mbx(const gchar *host, const gchar *login, const gchar *passwd, gboolean save_pwd)
+{
+ gchar *host_only;
+ gchar *host_port;
+ NetClientProbeResult probe_res;
+ BalsaMailboxNode *mbnode;
+ LibBalsaServer *server;
+ GError *error = NULL;
+
+ host_only = net_client_host_only(host);
+ server = LIBBALSA_SERVER(libbalsa_imap_server_new(login, host));
+ if (imap_server_probe(host_only, 5, &probe_res, G_CALLBACK(libbalsa_server_check_cert), &error)) {
+ host_port = g_strdup_printf("%s:%hu", host_only, probe_res.port);
+ g_debug("probe result for server %s: port %hu, crypt mode %d", host, probe_res.port,
probe_res.crypt_mode);
+ } else {
+ g_debug("failed to probe server %s, fall back to IMAPS: %s", host, error->message);
+ host_port = g_strdup_printf("%s:993", host_only);
+ probe_res.crypt_mode = NET_CLIENT_CRYPT_ENCRYPTED;
+ }
+ g_clear_error(&error);
+ libbalsa_server_set_host(server, host_port, probe_res.crypt_mode);
+ libbalsa_server_set_username(server, login);
+ libbalsa_server_set_password(server, passwd, FALSE);
+ libbalsa_server_set_auth_mode(server, NET_CLIENT_AUTH_USER_PASS);
+ libbalsa_server_set_remember_password(server, save_pwd);
+ mbnode = balsa_mailbox_node_new_imap_folder(server, NULL);
+ balsa_mailbox_node_set_name(mbnode, host_only);
+ config_folder_add(mbnode, NULL);
+ g_object_unref(mbnode);
+ g_free(host_only);
+ g_free(host_port);
+}
+
+
+static void
+config_smtp(const gchar *host, LibBalsaServer *smtpserver)
+{
+ gchar *host_only;
+ gchar *host_port;
+ NetClientProbeResult probe_res;
+ GError *error = NULL;
+
+ host_only = net_client_host_only(host);
+ if (net_client_smtp_probe(host_only, 5, &probe_res, G_CALLBACK(libbalsa_server_check_cert), &error)) {
+ host_port = g_strdup_printf("%s:%hu", host_only, probe_res.port);
+ g_debug("probe result for server %s: port %hu, crypt mode %d", host, probe_res.port,
probe_res.crypt_mode);
+ } else {
+ /* Note: the better choice in this case would be SUBMISSIONS (RFC 8314), but in practice some
ISP's do not support it. */
+ g_debug("failed to probe server %s, fall back to SUBMISSION w/ STARTTLS: %s", host,
error->message);
+ host_port = g_strdup_printf("%s:587", host_only);
+ probe_res.crypt_mode = NET_CLIENT_CRYPT_STARTTLS;
+ }
+ libbalsa_server_set_host(smtpserver, host_port, probe_res.crypt_mode);
+ g_clear_error(&error);
+ g_free(host_only);
+ g_free(host_port);
+}
+
+
+static void
+balsa_druid_page_server_next(GtkAssistant *druid, GtkWidget *page, BalsaDruidPageServer *server)
+{
+ const gchar *login;
+ const gchar *passwd;
+ gboolean save_pwd;
+ const gchar *host;
+ LibBalsaServer *lbserver;
+ LibBalsaSmtpServer *smtp_server;
+
+ /* common stuff: user name, password, remember password */
+ login = gtk_entry_get_text(GTK_ENTRY(server->login));
+ passwd = gtk_entry_get_text(GTK_ENTRY(server->passwd));
+ save_pwd = balsa_option_get_active(server->remember_passwd) == 0;
+
+ /* incoming mail - skip if nothing has been entered */
+ host = gtk_entry_get_text(GTK_ENTRY(server->incoming_srv));
+ if ((host != NULL) && (host[0] != '\0')) {
+ switch (balsa_option_get_active(server->incoming_type)) {
+ case 0: /* IMAP */
+ create_imap_mbx(host, login, passwd, save_pwd);
+ break;
+ case 1: /* POP */
+ create_pop3_mbx(host, login, passwd, save_pwd);
+ break;
+ default:
+ g_assert_not_reached(); /* internal error */
+ }
+ }
+
+ /* outgoing mail */
+ if (balsa_app.smtp_servers == NULL) {
+ smtp_server = libbalsa_smtp_server_new();
+ libbalsa_smtp_server_set_name(smtp_server, libbalsa_smtp_server_get_name(NULL));
+ balsa_app.smtp_servers = g_slist_prepend(NULL, smtp_server);
+ } else {
+ smtp_server = balsa_app.smtp_servers->data;
+ }
+ lbserver = LIBBALSA_SERVER(smtp_server);
+ host = gtk_entry_get_text(GTK_ENTRY(server->smtp));
+ if ((host != NULL) && (host[0] != '\0') && (strncmp(host, "localhost", 9U) != 0)) {
+ config_smtp(host, lbserver);
+ libbalsa_server_set_username(lbserver, login);
+ libbalsa_server_set_password(lbserver, passwd, FALSE);
+ libbalsa_server_set_auth_mode(lbserver, NET_CLIENT_AUTH_USER_PASS);
+ libbalsa_server_set_remember_password(lbserver, save_pwd);
+ } else {
+ /* localhost at port 25, without encryption and authentication */
+ libbalsa_server_set_host(lbserver, "localhost:25", NET_CLIENT_CRYPT_NONE);
+ libbalsa_server_set_auth_mode(lbserver, NET_CLIENT_AUTH_NONE_ANON);
+ }
+}
+
+
+/** @brief Guess the email servers for a given email address
+ *
+ * @param[in] mail_address email address
+ * @return an NULL-terminated array containing the submission, imap and pop3 servers on success, @em must be
freed by the caller
+ *
+ * Strategy for guessing the server names:
+ * - some ISP's have "strange" server names (inter alia Microsoft for outlook.com, Yahoo for yahoo.com):
return them from a
+ * hard-wired lookup table;
+ * - do a DNS lookup for SRV records (see https://www.rfc-editor.org/rfc/rfc6186.html, e.g.
_imaps._tcp.<domain>);
+ * - try to guess servers by name (e.g. imap.<domain>).
+ */
+static gchar **
+guess_servers(const gchar *mail_address)
+{
+ static const gchar * const srv_names[3][3] = {
+ {"submissions", "submission", NULL},
+ {"imaps", "imap", NULL},
+ {"pop3s", "pop3", NULL}
+ };
+ static const gchar * const dns_prefix[3][4] = {
+ {"smtp", "mail", "submission", NULL},
+ {"imap", "imap4", "mail", NULL},
+ {"pop", "pop3", "mail", NULL}
+ };
+ /* FIXME - this list probably needs to be extended */
+ static const gchar * const specials[2][4] = {
+ {"outlook.com", "outlook.office365.com", "outlook.office365.com", "outlook.office365.com"},
+ {"yahoo.com", "smtp.mail.yahoo.com", "imap.mail.yahoo.com", "pop.mail.yahoo.com"}
+ };
+ GResolver *resolver;
+ const gchar *at_sign;
+ gchar *domain;
+ gchar **result;
+ size_t n;
+
+ at_sign = strchr(mail_address, '@');
+ g_return_val_if_fail(at_sign != NULL, NULL);
+
+ result = g_new0(gchar *, 4UL);
+
+ /* a few mail providers are known to be special... */
+ for (n = 0U; n < G_N_ELEMENTS(specials); n++) {
+ if (g_ascii_strcasecmp(&at_sign[1], specials[n][0]) == 0) {
+ result[0] = g_strdup(specials[n][1]);
+ result[1] = g_strdup(specials[n][2]);
+ result[2] = g_strdup(specials[n][3]);
+ return result;
+ }
+ }
+
+ /* try to resolve the proper servers */
+ domain = g_ascii_strdown(&at_sign[1], -1);
+ resolver = g_resolver_get_default();
+ for (n = 0; n < 3; n++) {
+ result[n] = server_from_srv(resolver, srv_names[n], domain);
+ if (result[n] == NULL) {
+ result[n] = server_by_name(resolver, dns_prefix[n], domain);
+ }
+ }
+ g_object_unref(resolver);
+
+ g_free(domain);
+
+ return result;
+}
+
+
+/** @brief Try to look up a server by DNS SRV record
+ *
+ * @param[in] resolver resolver object
+ * @param[in] service NULL-terminated array of services to look up
+ * @param[in] domain email domain
+ * @return the name of a suitable server on success, NULL if none could be found
+ */
+static gchar *
+server_from_srv(GResolver *resolver, const gchar * const *service, const gchar *domain)
+{
+ int n;
+ gchar *result = NULL;
+
+ for (n = 0; (result == NULL) && (service[n] != NULL); n++) {
+ gchar *srv_val;
+ GList *res;
+ GError *error = NULL;
+
+ srv_val = g_strdup_printf("_%s._tcp.%s", service[n], domain);
+ res = g_resolver_lookup_records(resolver, srv_val, G_RESOLVER_RECORD_SRV, NULL, &error);
+ if (res != NULL) {
+ GVariant *srv = (GVariant *) res->data;
+ guint16 port;
+
+ g_variant_get(srv, "(qqqs)", NULL, NULL, &port, &result);
+ if ((port == 0U) || (result == NULL) || (result[0] == '\0')) {
+ g_free(result);
+ result = NULL;
+ } else {
+ g_debug("lookup for %s: %s:%hu", srv_val, result, port);
+ }
+ g_list_free_full(res, (GDestroyNotify) g_variant_unref);
+ } else if (error != NULL) {
+ g_debug("lookup for %s failed: %s", srv_val, error->message);
+ }
+ g_clear_error(&error);
+ g_free(srv_val);
+ }
+
+ return result;
+}
+
+
+/** @brief Try to look up a server by DNS name
+ *
+ * @param[in] resolver resolver object
+ * @param[in] service NULL-terminated array of server names to look up
+ * @param[in] domain email domain
+ * @return the name of a suitable server on success, NULL if none could be found
+ */
+static gchar *
+server_by_name(GResolver *resolver, const gchar * const *prefix, const gchar *domain)
+{
+ int n;
+ gchar *fullname = NULL;
+
+ for (n = 0; (fullname == NULL) && (prefix[n] != NULL); n++) {
+ GList *res;
+ GError *error = NULL;
+
+ fullname = g_strconcat(prefix[n], ".", domain, NULL);
+ res = g_resolver_lookup_by_name(resolver, fullname, NULL, &error);
+ if (res == NULL) {
+ g_debug("lookup of %s failed: %s", fullname, error->message);
+ g_free(fullname);
+ fullname = NULL;
+ } else {
+ g_debug("lookup of %s: success", fullname);
+ g_resolver_free_addresses(res);
+ }
+ g_clear_error(&error);
+ }
+
+ return fullname;
+}
diff --git a/libinit_balsa/assistant_page_server.h b/libinit_balsa/assistant_page_server.h
new file mode 100644
index 000000000..fb53a7d42
--- /dev/null
+++ b/libinit_balsa/assistant_page_server.h
@@ -0,0 +1,55 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ * Copyright (C) 1997-2022 Stuart Parmenter and others,
+ * See the file AUTHORS for a list.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __BALSA_DRUID_PAGE_SERVER_H__
+#define __BALSA_DRUID_PAGE_SERVER_H__
+
+#include <gtk/gtk.h>
+#include "assistant_helper.h"
+#include "assistant_init.h"
+
+G_BEGIN_DECLS
+
+/*
+ * Main object structure
+ */
+#ifndef __TYPEDEF_BALSA_DRUID_PAGE_SERVER__
+#define __TYPEDEF_BALSA_DRUID_PAGE_SERVER__
+typedef struct _BalsaDruidPageServer BalsaDruidPageServer;
+#endif
+#define BALSA_DRUID_PAGE_SERVER(obj) ((BalsaDruidPageServer *) obj)
+struct _BalsaDruidPageServer {
+ GtkWidget *page;
+ GtkWidget *incoming_type;
+ GtkWidget *incoming_srv;
+ GtkWidget *login;
+ GtkWidget *passwd;
+ GtkWidget *smtp;
+ GtkWidget *remember_passwd;
+ gboolean need_set;
+};
+
+/*
+ * Public methods
+ */
+void balsa_druid_page_server(GtkAssistant * druid);
+
+G_END_DECLS
+
+#endif
diff --git a/libinit_balsa/assistant_page_user.c b/libinit_balsa/assistant_page_user.c
index c8f3160f2..7e751a294 100644
--- a/libinit_balsa/assistant_page_user.c
+++ b/libinit_balsa/assistant_page_user.c
@@ -54,24 +54,8 @@ balsa_druid_page_user_init(BalsaDruidPageUser * user,
static const char *header2 =
N_("The following settings are also needed "
"(and you can find them later, if need be, in the Email "
- "application in the “Preferences” and “Identities” "
- "menu items)");
-#if 0
- static const char *header21 =
- N_(" Whoever provides your email account should be able "
- "to give you the following information (if you have "
- "a Network Administrator, they may already have set "
- "this up for you):");
-#endif
- static const char* server_types[] = { "POP3", "IMAP", NULL };
- static const gchar *security_modes[] = {
- N_("SSL"),
- N_("TLS required"),
- N_("TLS if possible (not recommended)"),
- N_("None (not recommended)"),
- NULL };
- static const char* remember_passwd[] = {
- N_("Yes, remember it"), N_("No, type it in every time"), NULL };
+ "application in the “Edit \342\217\265 Preferences \342\217\265 Identities…” "
+ "menu item)");
GtkGrid *grid;
GtkLabel *label;
gchar *preset;
@@ -83,8 +67,6 @@ balsa_druid_page_user_init(BalsaDruidPageUser * user,
user->ed0.controller = &(user->econtroller);
user->ed1.controller = &(user->econtroller);
user->ed2.controller = &(user->econtroller);
- user->ed3.controller = &(user->econtroller);
- user->ed4.controller = &(user->econtroller);
label = GTK_LABEL(gtk_label_new(_(header2)));
gtk_label_set_line_wrap(label, TRUE);
gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(label), FALSE, TRUE, 0);
@@ -93,63 +75,22 @@ balsa_druid_page_user_init(BalsaDruidPageUser * user,
gtk_grid_set_row_spacing(grid, 2);
gtk_grid_set_column_spacing(grid, 5);
-#if 0
- label = GTK_LABEL(gtk_label_new(_(header21)));
- gtk_label_set_justify(label, GTK_JUSTIFY_CENTER);
- gtk_label_set_line_wrap(label, TRUE);
- gtk_grid_attach(grid, GTK_WIDGET(label), 0, 2, 0, 1,
- GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 8, 4);
-#endif
- /* 2.1 */
- balsa_init_add_grid_entry(grid, row++,
- _("Name of mail server for incoming _mail:"),
- "", /* no guessing here */
- NULL, druid, page, &(user->incoming_srv));
-
- balsa_init_add_grid_option(grid, row++,
- _("_Type of mail server:"),
- server_types, druid, &(user->incoming_type));
-
- balsa_init_add_grid_option(grid, row++,
- _("Connection _Security:"),
- security_modes, druid, &(user->security));
- gtk_combo_box_set_active(GTK_COMBO_BOX(user->security), NET_CLIENT_CRYPT_STARTTLS - 1);
-
- balsa_init_add_grid_entry(grid, row++, _("Your email _login name:"),
- g_get_user_name(),
- NULL, druid, page, &(user->login));
- balsa_init_add_grid_entry(grid, row++, _("Your _password:"),
- "",
- NULL, druid, page, &(user->passwd));
- gtk_entry_set_visibility(GTK_ENTRY(user->passwd), FALSE);
- /* separator line here */
-
- preset = "localhost:25";
- balsa_init_add_grid_entry(grid, row++, _("_SMTP Server:"), preset,
- &(user->ed2), druid, page, &(user->smtp));
-
/* 2.1 */
balsa_init_add_grid_entry(grid, row++, _("Your real _name:"),
g_get_real_name(),
&(user->ed0), druid, page, &(user->name));
- preset = libbalsa_guess_email_address();
balsa_init_add_grid_entry
(grid, row++, _("Your _email address for this email account:"),
- preset, &(user->ed1), druid, page, &(user->email));
- g_free(preset);
+ "", &(user->ed1), druid, page, &(user->email));
- balsa_init_add_grid_option(grid, row++,
- _("_Remember your password:"),
- remember_passwd, druid,
- &(user->remember_passwd));
-
- preset = g_strconcat(g_get_home_dir(), "/mail", NULL);
+ preset = g_build_filename(g_get_home_dir(), "mail", NULL);
balsa_init_add_grid_entry(grid, row++, _("_Local mail directory:"),
preset,
- &(user->ed4), druid, page,
+ &(user->ed2), druid, page,
&(user->localmaildir));
g_free(preset);
+
gtk_box_pack_start(GTK_BOX(page), GTK_WIDGET(grid), FALSE, FALSE, 3);
user->need_set = FALSE;
@@ -188,98 +129,21 @@ balsa_druid_page_user_prepare(GtkAssistant * druid, GtkWidget * page,
gtk_assistant_set_page_complete(druid, page,
ENTRY_CONTROLLER_DONE(&user->econtroller));
- gtk_widget_grab_focus(user->incoming_srv);
+ gtk_widget_grab_focus(user->email);
user->need_set = TRUE;
}
-static LibBalsaMailbox*
-create_pop3_mbx(const gchar *name, const gchar* host, gint security,
- const gchar *login, const gchar *passwd,
- gboolean remember)
-{
- LibBalsaMailboxPOP3 *mailbox_pop3 = libbalsa_mailbox_pop3_new();
- LibBalsaMailbox *mailbox = LIBBALSA_MAILBOX(mailbox_pop3);
- LibBalsaServer *server = LIBBALSA_MAILBOX_REMOTE_GET_SERVER(mailbox);
- gchar *mailbox_name;
-
- libbalsa_server_set_username(server, login);
- libbalsa_server_set_password(server, passwd, FALSE);
- libbalsa_server_set_host(server, host, security);
- libbalsa_server_set_remember_password(server, remember);
-
- mailbox_name = g_strdup(name != NULL && name[0] != '\0' ? name : host);
- libbalsa_mailbox_set_name(mailbox, mailbox_name);
- g_free(mailbox_name);
-
- libbalsa_mailbox_pop3_set_check(mailbox_pop3, TRUE);
- libbalsa_mailbox_pop3_set_disable_apop(mailbox_pop3, FALSE);
- libbalsa_mailbox_pop3_set_delete_from_server(mailbox_pop3, TRUE);
- libbalsa_mailbox_pop3_set_filter(mailbox_pop3, FALSE);
- libbalsa_mailbox_pop3_set_filter_cmd(mailbox_pop3, "procmail -f -");
-
- return mailbox;
-}
-
-static void
-create_imap_mbx(const gchar *name, const gchar* host, NetClientCryptMode security,
- const gchar *login, const gchar *passwd,
- gboolean remember)
-{
- BalsaMailboxNode *mbnode;
- LibBalsaServer *server =
- LIBBALSA_SERVER(libbalsa_imap_server_new(login, host));
- libbalsa_server_set_username(server, login);
- libbalsa_server_set_password(server, passwd, FALSE);
- libbalsa_server_set_host(server, host, security);
- libbalsa_server_set_remember_password(server, remember);
- mbnode = balsa_mailbox_node_new_imap_folder(server, NULL);
- balsa_mailbox_node_set_name(mbnode, name != NULL && name[0] != '\0' ? name : host);
-
- config_folder_add(mbnode, NULL);
- /* memory leak? */
- g_object_unref(mbnode);
-}
-
static void
balsa_druid_page_user_next(GtkAssistant * druid, GtkWidget * page,
BalsaDruidPageUser * user)
{
- const gchar *host, *mailbox;
+ const gchar *mailbox;
gchar *uhoh;
LibBalsaIdentity *ident;
InternetAddress *ia;
- LibBalsaSmtpServer *smtp_server;
-#if 0
- printf("USER next ENTER %p %p\n", page, user->page);
- if(page != user->page)
- return;
-#endif
-
- /* incoming mail */
- host = gtk_entry_get_text(GTK_ENTRY(user->incoming_srv));
- if(host && *host) {
- LibBalsaMailbox *mbx = NULL;
- const gchar *login = gtk_entry_get_text(GTK_ENTRY(user->login));
- const gchar *passwd = gtk_entry_get_text(GTK_ENTRY(user->passwd));
- NetClientCryptMode security = balsa_option_get_active(user->security) + NET_CLIENT_CRYPT_ENCRYPTED;
- gboolean remember =
- balsa_option_get_active(user->remember_passwd) == 0;
- switch(balsa_option_get_active(user->incoming_type)) {
- case 0: /* POP */
- mbx = create_pop3_mbx(host, host, security, login, passwd, remember);
- if(mbx)
- config_mailbox_add(mbx, NULL);
- break;
- case 1: /* IMAP */
- create_imap_mbx(host, host, security, login, passwd, remember);
- break;
- default: /* hm */;
- }
- }
/* identity */
-
mailbox = gtk_entry_get_text(GTK_ENTRY(user->name));
if (balsa_app.identities == NULL) {
gchar *domain = strrchr(mailbox, '@');
@@ -296,19 +160,6 @@ balsa_druid_page_user_next(GtkAssistant * druid, GtkWidget * page,
ia = internet_address_mailbox_new (mailbox, gtk_entry_get_text(GTK_ENTRY(user->email)));
libbalsa_identity_set_address (ident, ia);
- /* outgoing mail */
- if (balsa_app.smtp_servers == NULL) {
- smtp_server = libbalsa_smtp_server_new();
- libbalsa_smtp_server_set_name(smtp_server,
- libbalsa_smtp_server_get_name(NULL));
- balsa_app.smtp_servers = g_slist_prepend(NULL, smtp_server);
- } else {
- smtp_server = balsa_app.smtp_servers->data;
- }
- libbalsa_server_set_host(LIBBALSA_SERVER(smtp_server),
- gtk_entry_get_text(GTK_ENTRY(user->smtp)),
- FALSE); // FIXME!!
-
g_free(balsa_app.local_mail_directory);
balsa_app.local_mail_directory =
gtk_editable_get_chars(GTK_EDITABLE(user->localmaildir), 0, -1);
diff --git a/libinit_balsa/assistant_page_user.h b/libinit_balsa/assistant_page_user.h
index 7681096cb..bfa6b5fb2 100644
--- a/libinit_balsa/assistant_page_user.h
+++ b/libinit_balsa/assistant_page_user.h
@@ -36,13 +36,6 @@ G_BEGIN_DECLS
#define BALSA_DRUID_PAGE_USER(obj) ((BalsaDruidPageUser *) obj)
struct _BalsaDruidPageUser {
GtkWidget *page;
- GtkWidget *incoming_srv;
- GtkWidget *incoming_type;
- GtkWidget *security;
- GtkWidget *login;
- GtkWidget *passwd;
- GtkWidget *remember_passwd;
- GtkWidget *smtp;
GtkWidget *name;
GtkWidget *email;
GtkWidget *localmaildir;
@@ -50,11 +43,6 @@ G_BEGIN_DECLS
EntryData ed0;
EntryData ed1;
EntryData ed2;
- EntryData ed3;
- EntryData ed4;
- EntryData ed5;
- EntryData ed6;
- EntryData ed7;
gboolean need_set;
};
diff --git a/libinit_balsa/meson.build b/libinit_balsa/meson.build
index 6594128a6..95795b6fb 100644
--- a/libinit_balsa/meson.build
+++ b/libinit_balsa/meson.build
@@ -7,6 +7,8 @@ libinit_balsa_sources = [
'assistant_page_directory.h',
'assistant_page_finish.c',
'assistant_page_finish.h',
+ 'assistant_page_server.c',
+ 'assistant_page_server.h',
'assistant_page_user.c',
'assistant_page_user.h',
'assistant_page_welcome.c',
diff --git a/libnetclient/README b/libnetclient/README
index 45100e3f2..bc1fb732d 100644
--- a/libnetclient/README
+++ b/libnetclient/README
@@ -76,9 +76,14 @@ are required for running them:
Note that most of these requirements are typically available pre-packaged
for your favorite distribution.
-Unfortunately, INetSim 1.2.6 and 1.2.7 have two little bugs in POP3 handling
-and do not offer pipelining. The file test/inetsim-1.2.6-POP3.diff contains
-a patch which fixes these issues.
+Unfortunately, INetSim 1.3.2 has two bugs in POP3 handling and does not
+offer pipelining. The file test/inetsim-1.3.2.diff contains a patch which
+fixes these issues.
+
+Some gnutls-serv versions will replace received CRLF line endings by just
+LF, breaking several tests. A fixed version is available from the GnuTLS
+gitlab repository only (see https://gitlab.com/gnutls/gnutls/-/issues/1073
+and https://gitlab.com/gnutls/gnutls/-/merge_requests/1314).
For running the tests, open two terminal windows, and cd to the test folder
of this package.
@@ -87,8 +92,9 @@ In the first terminal, call
./start-test-env.sh
-which will launch several dummy servers required for testing. Note that
-INetSim requires root permissions and is thus called via sudo.
+which will launch several dummy servers and two INetSim instances required
+for testing. Note that INetSim requires root permissions and is thus
+called via sudo.
Then, in the second terminal, call
diff --git a/libnetclient/net-client-pop.c b/libnetclient/net-client-pop.c
index 6141c9b2d..d8dea3612 100644
--- a/libnetclient/net-client-pop.c
+++ b/libnetclient/net-client-pop.c
@@ -25,13 +25,48 @@ 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 0x001U
+/** RFC 1939 "APOP" authentication method. */
+#define NET_CLIENT_POP_AUTH_APOP 0x002U
+/** RFC 5034 SASL "LOGIN" authentication method. */
+#define NET_CLIENT_POP_AUTH_LOGIN 0x004U
+/** RFC 5034 SASL "PLAIN" authentication method. */
+#define NET_CLIENT_POP_AUTH_PLAIN 0x008U
+/** RFC 5034 SASL "CRAM-MD5" authentication method. */
+#define NET_CLIENT_POP_AUTH_CRAM_MD5 0x010U
+/** RFC 5034 SASL "CRAM-SHA1" authentication method. */
+#define NET_CLIENT_POP_AUTH_CRAM_SHA1 0x020U
+/** RFC 4752 "GSSAPI" authentication method. */
+#define NET_CLIENT_POP_AUTH_GSSAPI 0x040U
+/** RFC 4505 "ANONYMOUS" authentication method. */
+#define NET_CLIENT_POP_AUTH_ANONYMOUS 0x200U
+
+
+/** 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_ANONYMOUS)
+/** @} */
+
+
/* 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. */
@@ -59,14 +94,14 @@ static gboolean net_client_pop_starttls(NetClientPop *client, GError **error);
static gboolean net_client_pop_execute(NetClientPop *client, const gchar *request_fmt, gchar **last_reply,
GError **error, ...)
G_GNUC_PRINTF(2, 5);
static gboolean net_client_pop_execute_sasl(NetClientPop *client, const gchar *request_fmt, gchar
**challenge, GError **error, ...);
-static gboolean net_client_pop_auth(NetClientPop *client, const gchar *user, const gchar *passwd, guint
auth_supported,
- GError **error);
+static gboolean net_client_pop_auth(NetClientPop *client, guint auth_supported, GError **error);
static gboolean net_client_pop_auth_plain(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
static gboolean net_client_pop_auth_login(NetClientPop *client, const gchar *user, const gchar *passwd,
GError **error);
+static gboolean net_client_pop_auth_anonymous(NetClientPop *client, GError **error);
static gboolean net_client_pop_auth_user_pass(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
static gboolean net_client_pop_auth_apop(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
static gboolean net_client_pop_auth_cram(NetClientPop *client, GChecksumType chksum_type, const gchar *user,
const gchar *passwd,
- GError **error);
+ GError **error);
static gboolean net_client_pop_auth_gssapi(NetClientPop *client, const gchar *user, GError **error);
static gboolean net_client_pop_retr_msg(NetClientPop *client, const NetClientPopMessageInfo *info,
NetClientPopMsgCb callback,
gpointer user_data, GError
**error);
@@ -92,16 +127,116 @@ 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;
+ gboolean retval = FALSE;
+ gint check_id;
+
/* paranoia check */
+ g_return_val_if_fail((host != NULL) && (result != NULL), FALSE);
+
+ host_only = net_client_host_only(host);
+ 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;
+ }
+ 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_NONE_ANON) != 0U) {
+ client->auth_enabled |= NET_CLIENT_POP_AUTH_ANONYMOUS;
+ }
+ 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;
+ }
+ }
+#if defined(HAVE_GSSAPI)
+ if ((auth_mode & NET_CLIENT_AUTH_KERBEROS) != 0U) {
+ client->auth_enabled |= NET_CLIENT_POP_AUTH_GSSAPI;
}
- return TRUE;
+#endif
+ return (client->auth_enabled != 0U);
}
@@ -167,33 +302,7 @@ net_client_pop_connect(NetClientPop *client, gchar **greeting, GError **error)
/* authenticate if we were successful so far */
if (result) {
- gchar **auth_data;
- gboolean need_pwd;
-
- auth_data = NULL;
- need_pwd = (auth_supported & NET_CLIENT_POP_AUTH_NO_PWD) == 0U;
- g_debug("emit 'auth' signal for client %p, need pwd %d", client, need_pwd);
- g_signal_emit_by_name(client, "auth", need_pwd, &auth_data);
- if ((auth_data != NULL) && (auth_data[0] != NULL)) {
- 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);
+ result = net_client_pop_auth(client, auth_supported, error);
}
return result;
@@ -395,8 +504,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;
}
@@ -489,47 +597,69 @@ net_client_pop_starttls(NetClientPop *client, GError **error)
static gboolean
-net_client_pop_auth(NetClientPop *client, const gchar *user, const gchar *passwd, guint auth_supported,
GError **error)
+net_client_pop_auth(NetClientPop *client, guint auth_supported, GError **error)
{
gboolean result = FALSE;
guint auth_mask;
+ gchar **auth_data = NULL;
- 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;
+ /* try, in this order, enabled modes: anonymous; GSSAPI/Kerberos; user name and password */
if (auth_mask == 0U) {
g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
_("no suitable authentication mechanism"));
- } else if (((auth_mask & NET_CLIENT_POP_AUTH_NO_PWD) == 0U) && (passwd == NULL)) {
- g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
_("password required"));
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_ANONYMOUS) != 0U) {
+ /* Anonymous authentication - nothing required */
+ result = net_client_pop_auth_anonymous(client, error);
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_GSSAPI) != 0U) {
+ /* GSSAPI aka Kerberos authentication - user name required */
+ g_signal_emit_by_name(client, "auth", NET_CLIENT_AUTH_KERBEROS, &auth_data);
+ if ((auth_data == NULL) || (auth_data[0] == NULL)) {
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
_("user name required"));
+ } else {
+ result = net_client_pop_auth_gssapi(client, auth_data[0], error);
+ }
} 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) {
- 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);
- } else if ((auth_mask & NET_CLIENT_POP_AUTH_CRAM_MD5) != 0U) {
- result = net_client_pop_auth_cram(client, G_CHECKSUM_MD5, user, passwd, error);
- } else if ((auth_mask & NET_CLIENT_POP_AUTH_APOP) != 0U) {
- result = net_client_pop_auth_apop(client, user, passwd, error);
- } else if ((auth_mask & NET_CLIENT_POP_AUTH_PLAIN) != 0U) {
- result = net_client_pop_auth_plain(client, user, passwd, error);
- } else if ((auth_mask & NET_CLIENT_POP_AUTH_USER_PASS) != 0U) {
- result = net_client_pop_auth_user_pass(client, user, passwd, error);
- } else if ((auth_mask & NET_CLIENT_POP_AUTH_LOGIN) != 0U) {
- result = net_client_pop_auth_login(client, user, passwd, error);
+ /* user name and password authentication methods */
+ g_signal_emit_by_name(client, "auth", NET_CLIENT_AUTH_USER_PASS, &auth_data);
+ if ((auth_data == NULL) || (auth_data[0] == NULL) || (auth_data[1] == NULL)) {
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
+ _("user name and password required"));
} else {
- g_assert_not_reached();
+ /* first check for safe (hashed) authentication methods, used plain-text ones if they
are not supported */
+ if ((auth_mask & NET_CLIENT_POP_AUTH_CRAM_SHA1) != 0U) {
+ result = net_client_pop_auth_cram(client, G_CHECKSUM_SHA1, auth_data[0],
auth_data[1], error);
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_CRAM_MD5) != 0U) {
+ result = net_client_pop_auth_cram(client, G_CHECKSUM_MD5, auth_data[0],
auth_data[1], error);
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_APOP) != 0U) {
+ result = net_client_pop_auth_apop(client, auth_data[0], auth_data[1], error);
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_PLAIN) != 0U) {
+ result = net_client_pop_auth_plain(client, auth_data[0], auth_data[1], error);
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_LOGIN) != 0U) {
+ result = net_client_pop_auth_login(client, auth_data[0], auth_data[1], error);
+ } else if ((auth_mask & NET_CLIENT_POP_AUTH_USER_PASS) != 0U) {
+ result = net_client_pop_auth_user_pass(client, auth_data[0], auth_data[1],
error);
+ } else {
+ g_assert_not_reached();
+ }
}
+ }
+
+ /* POP3 does not define a mechanism to indicate that the authentication failed due to a too weak
mechanism or wrong
+ * credentials, so we treat all server -ERR responses as authentication failures */
+ if (!result && (error != NULL) && (*error != NULL) && ((*error)->code == (gint)
NET_CLIENT_ERROR_POP_SERVER_ERR)) {
+ (*error)->code = (gint) NET_CLIENT_ERROR_POP_AUTHFAIL;
+ }
- /* POP3 does not define a mechanism to indicate that the authentication failed due to a too
weak mechanism or wrong
- * credentials, so we treat all server -ERR responses as authentication failures */
- if (!result && (*error != NULL) && ((*error)->code == (gint)
NET_CLIENT_ERROR_POP_SERVER_ERR)) {
- (*error)->code = (gint) NET_CLIENT_ERROR_POP_AUTHFAIL;
+ /* clean up any auth data */
+ if (auth_data != NULL) {
+ if (auth_data[0] != NULL) {
+ net_client_free_authstr(auth_data[0]);
+ net_client_free_authstr(auth_data[1]);
}
+ free(auth_data);
}
return result;
@@ -580,6 +710,24 @@ net_client_pop_auth_login(NetClientPop *client, const gchar *user, const gchar *
}
+static gboolean
+net_client_pop_auth_anonymous(NetClientPop *client, GError **error)
+{
+ gboolean result;
+
+ result = net_client_pop_execute_sasl(client, "AUTH ANONYMOUS", NULL, error);
+ if (result) {
+ gchar *base64_buf;
+
+ base64_buf = net_client_auth_anonymous_token();
+ result = net_client_pop_execute(client, "%s", NULL, error, base64_buf);
+ net_client_free_authstr(base64_buf);
+ }
+
+ return result;
+}
+
+
static gboolean
net_client_pop_auth_user_pass(NetClientPop *client, const gchar *user, const gchar *passwd, GError **error)
{
@@ -762,12 +910,14 @@ net_client_pop_get_capa(NetClientPop *client, guint *auth_supported)
*auth_supported |= NET_CLIENT_POP_AUTH_CRAM_MD5;
} else if (strcmp(auth[n], "CRAM-SHA1") == 0) {
*auth_supported |= NET_CLIENT_POP_AUTH_CRAM_SHA1;
+ } else if (strcmp(auth[n], "ANONYMOUS") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_ANONYMOUS;
#if defined(HAVE_GSSAPI)
} else if (strcmp(auth[n], "GSSAPI") == 0) {
*auth_supported |= NET_CLIENT_POP_AUTH_GSSAPI;
#endif
} else {
- /* other auth methods are ignored for the time being */
+ /* other auth methods are ignored for the time being (see
MISRA C:2012, Rule 15.7) */
}
}
g_strfreev(auth);
diff --git a/libnetclient/net-client-pop.h b/libnetclient/net-client-pop.h
index 527e009f1..bb3e587ac 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 precedence is: ANONYMOUS, GSSAPI (Kerberos), user name and password.
For the latter, the
+ * order is CRAM-SHA1, CRAM-MD5, APOP, PLAIN, LOGIN or USER/PASS. 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.
@@ -240,10 +227,10 @@ void net_client_pop_msg_info_free(NetClientPopMessageInfo *info);
* - the <i>STAT</i>, <i>LIST</i>, <i>RETR</i> and <i>DELE</i> commands as defined in RFC 1939;
* - support for <i>PIPELINING</i> and <i>UIDL</i> as defined by <a
href="https://tools.ietf.org/html/rfc2449">RFC 2449</a>;
* - <i>STLS</i> encryption as defined by <a href="https://tools.ietf.org/html/rfc2595">RFC 2595</a>;
- * - authentication using <i>APOP</i>, <i>USER/PASS</i> (both RFC 1939) or the SASL methods <i>PLAIN</i>,
<i>LOGIN</i>,
- * <i>CRAM-MD5</i>, <i>CRAM-SHA1</i> or <i>GSSAPI</i> (see <a
href="https://tools.ietf.org/html/rfc4752">RFC 4752</a> and
- * <a href="https://tools.ietf.org/html/rfc5034">RFC 5034</a>), depending upon the capabilities reported
by the server. Note the
- * <i>GSSAPI</i> is available only if configured with gssapi support.
+ * - authentication using <i>APOP</i>, <i>USER/PASS</i> (both RFC 1939) or the SASL methods
<i>ANONYMOUS</i>, <i>PLAIN</i>,
+ * <i>LOGIN</i>, <i>CRAM-MD5</i>, <i>CRAM-SHA1</i> and <i>GSSAPI</i> (see <a
href="https://tools.ietf.org/html/rfc4752">RFC
+ * 4752</a> depending upon the capabilities reported by the server. Note that <i>GSSAPI</i> is available
only if
+ * configured with the respective support.
*/
diff --git a/libnetclient/net-client-smtp.c b/libnetclient/net-client-smtp.c
index 559c9f086..87f523ded 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,33 @@ 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
+
+
+/** 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)
+/** @} */
+
+
/* 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
@@ -59,17 +86,17 @@ G_DEFINE_TYPE(NetClientSmtp, net_client_smtp, NET_CLIENT_TYPE)
static void net_client_smtp_finalise(GObject *object);
static gboolean net_client_smtp_ehlo(NetClientSmtp *client, guint *auth_supported, gboolean *can_starttls,
GError **error);
static gboolean net_client_smtp_starttls(NetClientSmtp *client, GError **error);
-static gboolean net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gchar **last_reply,
GError **error, ...)
- G_GNUC_PRINTF(2, 5);
-static gboolean net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *passwd, guint
auth_supported,
- GError **error);
+static gboolean net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gint expect_code,
gchar **last_reply,
+ GError **error, ...)
+ G_GNUC_PRINTF(2, 6);
+static gboolean net_client_smtp_auth(NetClientSmtp *client, guint auth_supported, GError **error);
static gboolean net_client_smtp_auth_plain(NetClientSmtp *client, const gchar *user, const gchar *passwd,
GError **error);
static gboolean net_client_smtp_auth_login(NetClientSmtp *client, const gchar *user, const gchar *passwd,
GError **error);
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_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 gboolean net_client_smtp_eval_rescode(gint res_code, gint expect_code, const gchar *reply, GError
**error);
static gchar *net_client_smtp_dsn_to_string(const NetClientSmtp *client, NetClientSmtpDsnMode dsn_mode);
static void smtp_rcpt_free(smtp_rcpt_t *rcpt);
@@ -93,16 +120,117 @@ 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_NONE_ANON) != 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
+ 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[] = {465U, 587U, 25U, 0U}; /* submissions, submission, smtp */
+ gchar *host_only;
+ gboolean retval = FALSE;
+ gint check_id;
+
+ /* paranoia check */
+ g_return_val_if_fail((host != NULL) && (result != NULL), FALSE);
+
+ host_only = net_client_host_only(host);
+
+ 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 == 0) { /* submissions */
+ 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 == 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 ((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;
+ }
+ 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 465, 587 or 25"), host_only);
+ }
+
+ g_free(host_only);
+
+ return retval;
}
@@ -122,7 +250,7 @@ net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
result = net_client_start_tls(NET_CLIENT(client), error);
}
- /* get the greeting */
+ /* get the greeting, RFC 5321 requires status 220 */
if (result) {
(void) net_client_set_timeout(NET_CLIENT(client), 5U * 60U); /* RFC 5321, Sect.
4.5.3.2.1.: 5 minutes timeout */
result = net_client_smtp_read_reply(client, 220, greeting, error);
@@ -150,35 +278,9 @@ net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
}
}
- /* authenticate if we were successful so far */
- if (result) {
- gchar **auth_data;
- gboolean need_pwd;
-
- auth_data = NULL;
- need_pwd = (auth_supported & NET_CLIENT_SMTP_AUTH_NO_PWD) == 0U;
- g_debug("emit 'auth' signal for client %p, need pwd %d", client, need_pwd);
- g_signal_emit_by_name(client, "auth", need_pwd, &auth_data);
- if ((auth_data != NULL) && (auth_data[0] != NULL)) {
- 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);
+ /* authenticate if we were successful so far, unless anonymous access is configured */
+ if (result && ((client->auth_enabled & NET_CLIENT_SMTP_AUTH_NONE) == 0U)) {
+ result = net_client_smtp_auth(client, auth_supported, error);
}
return result;
@@ -203,19 +305,19 @@ net_client_smtp_send_msg(NetClientSmtp *client, const NetClientSmtpMessage *mess
g_return_val_if_fail(NET_IS_CLIENT_SMTP(client) && (message != NULL) && (message->sender != NULL) &&
(message->recipients != NULL) && (message->data_callback != NULL), FALSE);
- /* set the RFC 5321 sender and recipient(s) */
+ /* set the RFC 5321 sender and recipient(s); Sect. 3.3 requires status 250 */
netclient = NET_CLIENT(client); /* convenience pointer */
(void) net_client_set_timeout(netclient, 5U * 60U); /* RFC 5321, Sect. 4.5.3.2.2., 4.5.3.2.3.: 5
minutes timeout */
if (client->can_dsn && message->have_dsn_rcpt) {
if (message->dsn_envid != NULL) {
- result = net_client_smtp_execute(client, "MAIL FROM:<%s> RET=%s ENVID=%s", NULL,
error, message->sender,
+ result = net_client_smtp_execute(client, "MAIL FROM:<%s> RET=%s ENVID=%s", 250, NULL,
error, message->sender,
(message->dsn_ret_full) ? "FULL" : "HDRS", message->dsn_envid);
} else {
- result = net_client_smtp_execute(client, "MAIL FROM:<%s> RET=%s", NULL, error,
message->sender,
+ result = net_client_smtp_execute(client, "MAIL FROM:<%s> RET=%s", 250, NULL, error,
message->sender,
(message->dsn_ret_full) ? "FULL" : "HDRS");
}
} else {
- result = net_client_smtp_execute(client, "MAIL FROM:<%s>", NULL, error, message->sender);
+ result = net_client_smtp_execute(client, "MAIL FROM:<%s>", 250, NULL, error, message->sender);
}
rcpt = message->recipients;
while (result && (rcpt != NULL)) {
@@ -224,15 +326,15 @@ net_client_smtp_send_msg(NetClientSmtp *client, const NetClientSmtpMessage *mess
/* create the RFC 3461 DSN string */
dsn_opts = net_client_smtp_dsn_to_string(client, this_rcpt->dsn_mode);
- result = net_client_smtp_execute(client, "RCPT TO:<%s>%s", NULL, error,
this_rcpt->rfc5321_addr, dsn_opts);
+ result = net_client_smtp_execute(client, "RCPT TO:<%s>%s", 250, NULL, error,
this_rcpt->rfc5321_addr, dsn_opts);
g_free(dsn_opts);
rcpt = rcpt->next;
}
- /* initialise sending the message data */
+ /* initialise sending the message data; Sect. 3.3 requires status 354 */
if (result) {
(void) net_client_set_timeout(netclient, 2U * 60U); /* RFC 5321, Sect. 4.5.3.2.4.: 2
minutes timeout */
- result = net_client_smtp_execute(client, "DATA", NULL, error);
+ result = net_client_smtp_execute(client, "DATA", 354, NULL, error);
}
/* call the data callback until all data has been transmitted or an error occurs */
@@ -356,8 +458,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;
}
@@ -382,7 +483,8 @@ net_client_smtp_starttls(NetClientSmtp *client, GError **error)
{
gboolean result;
- result = net_client_smtp_execute(client, "STARTTLS", NULL, error);
+ /* RFC 3207, Sect. 4 requires status 220 */
+ result = net_client_smtp_execute(client, "STARTTLS", 220, NULL, error);
if (result) {
result = net_client_start_tls(NET_CLIENT(client), error);
}
@@ -392,40 +494,58 @@ net_client_smtp_starttls(NetClientSmtp *client, GError **error)
static gboolean
-net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *passwd, guint auth_supported,
GError **error)
+net_client_smtp_auth(NetClientSmtp *client, guint auth_supported, GError **error)
{
gboolean result = FALSE;
guint auth_mask;
+ gchar **auth_data = NULL;
/* 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;
+ /* try, in this order, enabled modes: GSSAPI/Kerberos; user name and password */
if (auth_mask == 0U) {
g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
_("no suitable authentication mechanism"));
- } else if (((auth_mask & NET_CLIENT_SMTP_AUTH_NO_PWD) == 0U) && (passwd == NULL)) {
- g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
_("password required"));
+ } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_GSSAPI) != 0U) {
+ /* GSSAPI aka Kerberos authentication - user name required */
+ g_signal_emit_by_name(client, "auth", NET_CLIENT_AUTH_KERBEROS, &auth_data);
+ if ((auth_data == NULL) || (auth_data[0] == NULL)) {
+ g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
_("user name required"));
+ } else {
+ result = net_client_smtp_auth_gssapi(client, auth_data[0], error);
+ }
} 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) {
- 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);
- } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_CRAM_MD5) != 0U) {
- result = net_client_smtp_auth_cram(client, G_CHECKSUM_MD5, user, passwd, error);
- } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_PLAIN) != 0U) {
- result = net_client_smtp_auth_plain(client, user, passwd, error);
- } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_LOGIN) != 0U) {
- result = net_client_smtp_auth_login(client, user, passwd, error);
+ /* user name and password authentication methods */
+ g_signal_emit_by_name(client, "auth", NET_CLIENT_AUTH_USER_PASS, &auth_data);
+ if ((auth_data == NULL) || (auth_data[0] == NULL) || (auth_data[1] == NULL)) {
+ g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
+ _("user name and password required"));
} else {
- g_assert_not_reached();
+ /* first check for safe (hashed) authentication methods, used plain-text ones if they
are not supported */
+ if ((auth_mask & NET_CLIENT_SMTP_AUTH_CRAM_SHA1) != 0U) {
+ result = net_client_smtp_auth_cram(client, G_CHECKSUM_SHA1, auth_data[0],
auth_data[1], error);
+ } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_CRAM_MD5) != 0U) {
+ result = net_client_smtp_auth_cram(client, G_CHECKSUM_MD5, auth_data[0],
auth_data[1], error);
+ } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_PLAIN) != 0U) {
+ result = net_client_smtp_auth_plain(client, auth_data[0], auth_data[1],
error);
+ } else if ((auth_mask & NET_CLIENT_SMTP_AUTH_LOGIN) != 0U) {
+ result = net_client_smtp_auth_login(client, auth_data[0], auth_data[1],
error);
+ } else {
+ g_assert_not_reached();
+ }
}
}
+ /* clean up any auth data */
+ if (auth_data != NULL) {
+ if (auth_data[0] != NULL) {
+ net_client_free_authstr(auth_data[0]);
+ net_client_free_authstr(auth_data[1]);
+ }
+ free(auth_data);
+ }
+
return result;
}
@@ -433,12 +553,13 @@ 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);
if (base64_buf != NULL) {
- result = net_client_smtp_execute(client, "AUTH PLAIN %s", NULL, error, base64_buf);
+ /* RFC 4954, Sect. 6 requires status 235 */
+ result = net_client_smtp_execute(client, "AUTH PLAIN %s", 235, NULL, error, base64_buf);
net_client_free_authstr(base64_buf);
} else {
result = FALSE;
@@ -454,12 +575,13 @@ net_client_smtp_auth_login(NetClientSmtp *client, const gchar *user, const gchar
gboolean result;
gchar *base64_buf;
+ /* RFC 4954, Sect. 4 requires status 334 for the challenge; Sect. 6 requires status 235 */
base64_buf = g_base64_encode((const guchar *) user, strlen(user));
- result = net_client_smtp_execute(client, "AUTH LOGIN %s", NULL, error, base64_buf);
+ result = net_client_smtp_execute(client, "AUTH LOGIN %s", 334, NULL, error, base64_buf);
net_client_free_authstr(base64_buf);
if (result) {
base64_buf = g_base64_encode((const guchar *) passwd, strlen(passwd));
- result = net_client_smtp_execute(client, "%s", NULL, error, base64_buf);
+ result = net_client_smtp_execute(client, "%s", 235, NULL, error, base64_buf);
net_client_free_authstr(base64_buf);
}
@@ -473,13 +595,14 @@ net_client_smtp_auth_cram(NetClientSmtp *client, GChecksumType chksum_type, cons
gboolean result;
gchar *challenge = NULL;
- result = net_client_smtp_execute(client, "AUTH CRAM-%s", &challenge, error,
net_client_chksum_to_str(chksum_type));
+ /* RFC 4954, Sect. 4 requires status 334 for the challenge; Sect. 6 requires status 235 */
+ result = net_client_smtp_execute(client, "AUTH CRAM-%s", 334, &challenge, error,
net_client_chksum_to_str(chksum_type));
if (result) {
gchar *auth_buf;
auth_buf = net_client_cram_calc(challenge, chksum_type, user, passwd);
if (auth_buf != NULL) {
- result = net_client_smtp_execute(client, "%s", NULL, error, auth_buf);
+ result = net_client_smtp_execute(client, "%s", 235, NULL, error, auth_buf);
net_client_free_authstr(auth_buf);
} else {
result = FALSE;
@@ -499,6 +622,7 @@ net_client_smtp_auth_gssapi(NetClientSmtp *client, const gchar *user, GError **e
NetClientGssCtx *gss_ctx;
gboolean result = FALSE;
+ /* RFC 4954, Sect. 4 requires status 334 for the challenges; Sect. 6 requires status 235 */
gss_ctx = net_client_gss_ctx_new("smtp", net_client_get_host(NET_CLIENT(client)), user, error);
if (gss_ctx != NULL) {
gint state;
@@ -512,10 +636,10 @@ net_client_smtp_auth_gssapi(NetClientSmtp *client, const gchar *user, GError **e
input_token = NULL;
if (state >= 0) {
if (initial) {
- result = net_client_smtp_execute(client, "AUTH GSSAPI %s",
&input_token, error, output_token);
+ result = net_client_smtp_execute(client, "AUTH GSSAPI %s", 334,
&input_token, error, output_token);
initial = FALSE;
} else {
- result = net_client_smtp_execute(client, "%s", &input_token, error,
output_token);
+ result = net_client_smtp_execute(client, "%s", 334, &input_token,
error, output_token);
}
}
g_free(output_token);
@@ -524,7 +648,7 @@ net_client_smtp_auth_gssapi(NetClientSmtp *client, const gchar *user, GError **e
if (state == 1) {
output_token = net_client_gss_auth_finish(gss_ctx, input_token, error);
if (output_token != NULL) {
- result = net_client_smtp_execute(client, "%s", NULL, error, output_token);
+ result = net_client_smtp_execute(client, "%s", 235, NULL, error, output_token);
g_free(output_token);
}
}
@@ -550,7 +674,7 @@ net_client_smtp_auth_gssapi(NetClientSmtp G_GNUC_UNUSED *client, const gchar G_G
/* 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,
...)
+net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gint expect_code, gchar
**last_reply, GError **error, ...)
{
va_list args;
gboolean result;
@@ -560,7 +684,7 @@ net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gchar *
va_end(args);
if (result) {
- result = net_client_smtp_read_reply(client, -1, last_reply, error);
+ result = net_client_smtp_read_reply(client, expect_code, last_reply, error);
}
return result;
@@ -644,31 +768,33 @@ net_client_smtp_ehlo(NetClientSmtp *client, guint *auth_supported, gboolean *can
static gboolean
net_client_smtp_read_reply(NetClientSmtp *client, gint expect_code, gchar **last_reply, GError **error)
{
- gint rescode;
gboolean done;
gboolean result;
done = FALSE;
- rescode = expect_code;
do {
gchar *reply;
+ GError *this_error = NULL;
- result = net_client_read_line(NET_CLIENT(client), &reply, error);
+ result = net_client_read_line(NET_CLIENT(client), &reply, &this_error);
if (result) {
gint this_rescode;
- gchar *endptr;
- this_rescode = strtol(reply, &endptr, 10);
- if (rescode == -1) {
- rescode = this_rescode;
- result = net_client_smtp_eval_rescode(rescode, reply, error);
- } else if (rescode != this_rescode) {
- g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_SMTP_PROTOCOL,
- _("bad server reply: %s"), reply);
- result = FALSE;
- } else {
- /* nothing to do (see MISRA C:2012, Rule 15.7) */
+ this_rescode = strtol(reply, NULL, 10);
+ result = net_client_smtp_eval_rescode(this_rescode, expect_code, reply, &this_error);
+
+ if (!result) {
+ if ((error != NULL) && (*error != NULL)) {
+ g_prefix_error(&this_error, "%s ", (*error)->message);
+ g_clear_error(error);
+ }
+ g_propagate_error(error, this_error);
+ }
+
+ if (expect_code == -1) {
+ expect_code = this_rescode;
}
+
if ((strlen(reply) > 3UL) && (reply[3] == ' ')) {
done = TRUE;
if (last_reply != NULL) {
@@ -677,22 +803,32 @@ net_client_smtp_read_reply(NetClientSmtp *client, gint expect_code, gchar **last
}
g_free(reply);
+ } else {
+ g_clear_error(error);
+ g_propagate_error(error, this_error);
+ done = TRUE;
}
- } while (result && !done);
+ } while (!done);
return result;
}
static gboolean
-net_client_smtp_eval_rescode(gint res_code, const gchar *reply, GError **error)
+net_client_smtp_eval_rescode(gint res_code, gint expect_code, const gchar *reply, GError **error)
{
gboolean result;
switch (res_code / 100) {
case 2:
case 3:
- result = TRUE;
+ if ((expect_code == -1) || (res_code == expect_code)) {
+ result = TRUE;
+ } else {
+ g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_SMTP_TRANSIENT,
+ _("unexpected reply: %s"), reply);
+ result = FALSE;
+ }
break;
case 4:
g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_TRANSIENT,
diff --git a/libnetclient/net-client-smtp.h b/libnetclient/net-client-smtp.h
index 0adabdbc7..51eab76b2 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,23 @@ 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) 465, 587 and 25.
+ *
+ * \sa https://www.rfc-editor.org/rfc/rfc8314.html#section-3.3
+ */
+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 +106,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 +124,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 precedence is: no authentication, GSSAPI (Kerberos), user name and
password. For the
+ * latter, the order is CRAM-SHA1, CRAM-MD5, PLAIN, 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
SMTP network client
* object.
diff --git a/libnetclient/net-client-utils.c b/libnetclient/net-client-utils.c
index 8b6e252a3..3261528f2 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 {
@@ -111,6 +129,41 @@ net_client_auth_plain_calc(const gchar *user, const gchar *passwd)
}
+gchar *
+net_client_auth_anonymous_token(void)
+{
+ gchar *buffer;
+ GChecksum *hash;
+ const gchar *hash_str;
+
+ buffer = g_strdup_printf("%s@%s:%ld", g_get_user_name(), g_get_host_name(), (long) time(NULL));
+ hash = g_checksum_new(G_CHECKSUM_SHA256);
+ g_checksum_update(hash, (const guchar *) buffer, strlen(buffer));
+ g_free(buffer);
+
+ hash_str = g_checksum_get_string(hash);
+ buffer = g_base64_encode((const guchar *) hash_str, strlen(hash_str));
+ g_checksum_free(hash);
+ return buffer;
+}
+
+
+gchar *
+net_client_host_only(const gchar *host_and_port)
+{
+ gchar *result;
+ gchar *colon;
+
+ g_return_val_if_fail(host_and_port != NULL, NULL);
+ result = g_strdup(host_and_port);
+ colon = strchr(result, ':');
+ if (colon != NULL) {
+ colon[0] = '\0';
+ }
+ return result;
+}
+
+
void
net_client_free_authstr(gchar *str)
{
@@ -137,7 +190,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);
diff --git a/libnetclient/net-client-utils.h b/libnetclient/net-client-utils.h
index 88e1a0ffa..24d93e82a 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
@@ -46,7 +66,7 @@ typedef struct _NetClientGssCtx NetClientGssCtx;
* \sa <a href="https://tools.ietf.org/html/rfc2195">RFC 2195</a>.
*/
gchar *net_client_cram_calc(const gchar *base64_challenge, GChecksumType chksum_type, const gchar *user,
const gchar *passwd)
- G_GNUC_MALLOC;
+ G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
/** @brief Get the checksum type as string
@@ -63,12 +83,12 @@ const gchar *net_client_chksum_to_str(GChecksumType chksum_type);
* @param passwd password
* @return a newly allocated string containing the base64-encoded authentication
*
- * This helper function calculates the the base64-encoded SASL AUTH PLAIN authentication string from the
user name and the password
+ * This helper function calculates the base64-encoded SASL AUTH PLAIN authentication string from the user
name and the password
* according to <a href="https://tools.ietf.org/html/rfc4616">RFC 4616</a>. The caller shall free the
returned string when it is
* not needed any more.
*/
gchar *net_client_auth_plain_calc(const gchar *user, const gchar *passwd)
- G_GNUC_MALLOC;
+ G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
/** @brief Safely free an authentication string
@@ -81,6 +101,31 @@ gchar *net_client_auth_plain_calc(const gchar *user, const gchar *passwd)
void net_client_free_authstr(gchar *str);
+/** @brief Create a token for anonymous authentication
+ *
+ * @return a newly allocated string containing the base64-encoded authentication
+ *
+ * This helper function calculates a base64-encoded SASL AUTH ANONYMOUS authentication token which is used
as trace information by
+ * the server. As recommended by RFC 4505, the token does not contain personal data. The returned value is
the encoded SHA256 hex
+ * hash of the string <c>user-name@host-name:time<c> (time is the creation time stamp as returned by
time()). This token will be
+ * unique, but makes it impossible for the server to extract user and host. However, the client @em may
store to token if linking
+ * server to client operations is required, e.g. for debugging purposes.
+ *
+ * The caller shall free the returned string when it is not needed any more.
+ */
+gchar *net_client_auth_anonymous_token(void)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+
+/** \brief Return the host part of a host:port string
+ *
+ * @param[in] host_and_port a string containing a host name, optionally followed by a colon and a port number
+ * @return a newly allocated string containing the host part only
+ */
+gchar *net_client_host_only(const gchar *host_and_port)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+
#if defined(HAVE_GSSAPI)
/** @brief Create a GSSAPI authentication context
@@ -145,7 +190,7 @@ void net_client_gss_ctx_free(NetClientGssCtx *gss_ctx);
/** @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..7e20da224 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,14 @@ enum _NetClientCryptMode {
};
+/** @brief Authentication mode */
+enum _NetClientAuthMode {
+ NET_CLIENT_AUTH_NONE_ANON = 1, /**< No authentication (SMTP); anonymous
authentication (RFC 4505 for POP3, IMAP). */
+ NET_CLIENT_AUTH_USER_PASS = 2, /**< Authenticate with user name and password. */
+ NET_CLIENT_AUTH_KERBEROS = 4 /**< Authenticate with user name and Kerberos ticket.
*/
+};
+
+
/** @brief Error codes */
enum _NetClientError {
NET_CLIENT_ERROR_CONNECTED = 1, /**< The client is already connected. */
@@ -59,7 +68,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. */
};
@@ -332,11 +342,11 @@ gboolean net_client_can_read(NetClient *client);
* @endcode The server certificate is not trusted. The received certificate and the errors which occurred
during the check are
* passed to the signal handler. The handler shall return TRUE to accept the certificate, or FALSE to
reject it.
* - @anchor auth auth
- * @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.
+ * @code gchar **get_auth(NetClient *client, NetClientAuthMode mode, 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 (mode @ref NET_CLIENT_AUTH_USER_PASS) in the second element. For @ref
NET_CLIENT_AUTH_KERBEROS, no password
+ * is required. In this case, the second element must be present in the reply, but it is ignored and
should be NULL. The
+ * strings are wiped and freed when they are not needed any more.
*/
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]