[balsa] Implement GSSAPI single sign-on for SMTP, POP3
- From: Peter Bloomfield <peterb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa] Implement GSSAPI single sign-on for SMTP, POP3
- Date: Wed, 26 Apr 2017 01:11:56 +0000 (UTC)
commit 705c456dbfea84fa97881edd73c78dfc4154db81
Author: Albrecht Dreß <albrecht dress arcor de>
Date: Tue Apr 25 21:08:04 2017 -0400
Implement GSSAPI single sign-on for SMTP, POP3
* libbalsa/server.[ch]: changed auth signal handler footprint;
check if a password is needed.
* libnetclient/net-client-pop.h, libnetclient/net-client-smtp.h, libnetclient/README,
libnetclient/libnetclient.dox: documentation updates.
* libnetclient/net-client-pop.[ch], libnetclient/net-client-smtp.[ch]:
implement GSSAPI authentication.
* libnetclient/net-client-utils.[ch]: implement GSSAPI authentication helper functions.
* libnetclient/net-client.[ch]: use a GString instead of a fixed-length line buffer,
change auth signal handler footprint.
* libnetclient/test/tests.c: fix unit tests.
Signed-off-by: Peter Bloomfield <PeterBloomfield bellsouth net>
libbalsa/server.c | 11 +-
libbalsa/server.h | 1 +
libnetclient/README | 6 +
libnetclient/libnetclient.dox | 2 +-
libnetclient/net-client-pop.c | 120 ++++++++++++++++----
libnetclient/net-client-pop.h | 25 +++-
libnetclient/net-client-smtp.c | 115 ++++++++++++++++----
libnetclient/net-client-smtp.h | 20 +++-
libnetclient/net-client-utils.c | 233 +++++++++++++++++++++++++++++++++++++++
libnetclient/net-client-utils.h | 73 ++++++++++++-
libnetclient/net-client.c | 24 ++--
libnetclient/net-client.h | 14 ++-
libnetclient/test/tests.c | 18 ++--
13 files changed, 573 insertions(+), 89 deletions(-)
---
diff --git a/libbalsa/server.c b/libbalsa/server.c
index ca11eff..6dcd95e 100644
--- a/libbalsa/server.c
+++ b/libbalsa/server.c
@@ -605,6 +605,7 @@ libbalsa_server_connect_signals(LibBalsaServer * server, GCallback cb,
gchar **
libbalsa_server_get_auth(NetClient *client,
+ gboolean need_passwd,
gpointer user_data)
{
LibBalsaServer *server = LIBBALSA_SERVER(user_data);
@@ -615,10 +616,12 @@ libbalsa_server_get_auth(NetClient *client,
if (server->try_anonymous == 0U) {
result = g_new0(gchar *, 3U);
result[0] = g_strdup(server->user);
- if ((server->passwd != NULL) && (server->passwd[0] != '\0')) {
- result[1] = g_strdup(server->passwd);
- } else {
- result[1] = libbalsa_server_get_password(server, NULL);
+ if (need_passwd) {
+ if ((server->passwd != NULL) && (server->passwd[0] != '\0')) {
+ result[1] = g_strdup(server->passwd);
+ } else {
+ result[1] = libbalsa_server_get_password(server, NULL);
+ }
}
}
return result;
diff --git a/libbalsa/server.h b/libbalsa/server.h
index bab0018..7b43ee5 100644
--- a/libbalsa/server.h
+++ b/libbalsa/server.h
@@ -119,6 +119,7 @@ void libbalsa_server_user_cb(ImapUserEventType ue, void *arg, ...);
/* NetClient related signal handlers */
gchar **libbalsa_server_get_auth(NetClient *client,
+ gboolean need_passwd,
gpointer user_data);
gboolean libbalsa_server_check_cert(NetClient *client,
GTlsCertificate *peer_cert,
diff --git a/libnetclient/README b/libnetclient/README
index cdebc57..72e04c4 100644
--- a/libnetclient/README
+++ b/libnetclient/README
@@ -38,6 +38,9 @@ Requirements
Apart from GLib/GIO (at least version 2.40.0) it requires GnuTLS.
+For Kerberos V5 single sign-on (GSSAPI, RFC 4752) authentication, a Kerberos
+library (e.g. Heimdal) is required.
+
API Documentation
=================
@@ -105,4 +108,7 @@ INetSim will dump further information in its output files (typically in
/var/log/inetsim). You should verify that INetSim recorded the requested
operations properly.
+Note that no unit tests are available for the GSSAPI authentication methods
+as they are not supported by INetSim.
+
-oOo-
diff --git a/libnetclient/libnetclient.dox b/libnetclient/libnetclient.dox
index 699e013..d4689a5 100644
--- a/libnetclient/libnetclient.dox
+++ b/libnetclient/libnetclient.dox
@@ -217,7 +217,7 @@ ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
-INCLUDE_PATH =
+INCLUDE_PATH = ..
INCLUDE_FILE_PATTERNS =
PREDEFINED = G_GNUC_MALLOC= G_GNUC_PRINTF(x,y)=
EXPAND_AS_DEFINED =
diff --git a/libnetclient/net-client-pop.c b/libnetclient/net-client-pop.c
index ccd4076..6e032ff 100644
--- a/libnetclient/net-client-pop.c
+++ b/libnetclient/net-client-pop.c
@@ -63,6 +63,7 @@ static gboolean net_client_pop_auth_user_pass(NetClientPop *client, const gchar*
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);
+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);
@@ -135,8 +136,8 @@ net_client_pop_connect(NetClientPop *client, gchar **greeting, GError **error)
ang_close = strchr(ang_open, '>'); /*lint !e9034 accept char literal as int */
if (ang_close != NULL) {
- /*lint -e{946,947} allowed exception according to MISRA Rules 18.2 and
18.3 */
- client->priv->apop_banner = g_strndup(ang_open, (ang_close - ang_open) + 1);
+ /*lint -e{737,946,947,9029} allowed exception according to MISRA Rules
18.2 and 18.3 */
+ client->priv->apop_banner = g_strndup(ang_open, (ang_close - ang_open) + 1U);
auth_supported = NET_CLIENT_POP_AUTH_APOP;
}
}
@@ -166,14 +167,18 @@ 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", client);
- g_signal_emit_by_name(client, "auth", &auth_data);
- if ((auth_data != NULL) && (auth_data[0] != NULL) && (auth_data[1] != NULL)) {
+ 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);
memset(auth_data[0], 0, strlen(auth_data[0]));
- memset(auth_data[1], 0, strlen(auth_data[1]));
+ if (auth_data[1] != NULL) {
+ memset(auth_data[1], 0, strlen(auth_data[1]));
+ }
}
g_strfreev(auth_data);
}
@@ -263,6 +268,7 @@ net_client_pop_list(NetClientPop *client, GList **msg_list, gboolean with_uid, G
}
if (!result) {
+ /*lint -e{9074,9087} accept sane pointer conversion */
g_list_free_full(*msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
}
@@ -470,33 +476,37 @@ 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)
{
- gboolean result;
+ gboolean result = FALSE;
guint auth_mask;
- g_return_val_if_fail(NET_IS_CLIENT_POP(client) && (user != NULL) && (passwd != NULL), FALSE);
-
if (net_client_is_encrypted(NET_CLIENT(client))) {
auth_mask = client->priv->auth_allowed[0] & auth_supported;
} else {
auth_mask = client->priv->auth_allowed[1] & auth_supported;
}
- 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);
+ 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 {
- g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
- _("no suitable authentication mechanism"));
- result = FALSE;
+ /* 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);
+ } else {
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
+ _("no suitable authentication mechanism"));
+ }
}
return result;
@@ -607,6 +617,66 @@ net_client_pop_auth_cram(NetClientPop *client, GChecksumType chksum_type, const
}
+#if defined(HAVE_GSSAPI)
+
+static gboolean
+net_client_pop_auth_gssapi(NetClientPop *client, const gchar *user, GError **error)
+{
+ NetClientGssCtx *gss_ctx;
+ gboolean result = FALSE;
+
+ gss_ctx = net_client_gss_ctx_new("pop", net_client_get_host(NET_CLIENT(client)), user, error);
+ if (gss_ctx != NULL) {
+ gint state;
+ gboolean initial = TRUE;
+ gchar *input_token = NULL;
+ gchar *output_token = NULL;
+
+ do {
+ state = net_client_gss_auth_step(gss_ctx, input_token, &output_token, error);
+ g_free(input_token);
+ input_token = NULL;
+ if (state >= 0) {
+ if (initial) {
+ /* split the initial auth command as the initial-response argument
will typically exceed the 255-octet limit on
+ * the length of a single command, see RFC 5034, Sect. 4 */
+ initial = FALSE;
+ result = net_client_pop_execute_sasl(client, "AUTH GSSAPI =", NULL,
error);
+ }
+ if (result) {
+ result = net_client_pop_execute_sasl(client, "%s", &input_token,
error, output_token);
+ }
+ }
+ g_free(output_token);
+ } while (result && (state == 0));
+
+ if (state == 1) {
+ output_token = net_client_gss_auth_finish(gss_ctx, input_token, error);
+ if (output_token != NULL) {
+ result = net_client_pop_execute(client, "%s", NULL, error, output_token);
+ g_free(output_token);
+ }
+ }
+ g_free(input_token);
+ net_client_gss_ctx_free(gss_ctx);
+ }
+
+ return result;
+}
+
+#else
+
+/*lint -e{715} -e{818} */
+static gboolean
+net_client_pop_auth_gssapi(NetClientPop G_GNUC_UNUSED *client, const gchar G_GNUC_UNUSED *user, GError
G_GNUC_UNUSED **error)
+{
+ g_assert_not_reached(); /* this should never happen! */
+ return FALSE; /* never reached, make gcc happy */
+}
+
+#endif /* HAVE_GSSAPI */
+
+
/* Note: if supplied, challenge is never NULL on success */
static gboolean
net_client_pop_execute_sasl(NetClientPop *client, const gchar *request_fmt, gchar **challenge, GError
**error, ...)
@@ -675,6 +745,10 @@ 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;
+#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 */
}
diff --git a/libnetclient/net-client-pop.h b/libnetclient/net-client-pop.h
index 63ff340..c999a31 100644
--- a/libnetclient/net-client-pop.h
+++ b/libnetclient/net-client-pop.h
@@ -66,12 +66,16 @@ enum _NetClientPopError {
#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_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
/** @} */
@@ -149,8 +153,8 @@ NetClientPop *net_client_pop_new(const gchar *host, guint16 port, NetClientCrypt
* @param allow_auth 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_POP_AUTH_ALL for encrypted and
- * @ref NET_CLIENT_POP_AUTH_SAFE for unencrypted connections, respectively.
+ * Set the allowed authentication methods for the passed connection. The default is @ref
NET_CLIENT_POP_AUTH_ALL for both encrypted
+ * and unencrypted connections.
*
* @note Call this function @em before calling net_client_pop_connect().
*/
@@ -165,8 +169,14 @@ gboolean net_client_pop_allow_auth(NetClientPop *client, gboolean encrypted, gui
* @return TRUE on success or FALSE if the connection failed
*
* 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. In order to shut down a
successfully established
- * connection, just call <tt>g_object_unref()</tt> on the POP network client object.
+ * 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.
+ *
+ * In order to shut down a successfully established connection, just call <tt>g_object_unref()</tt> on the
POP network client
+ * object.
*
* @note The caller must free the returned greeting when it is not needed any more.
*/
@@ -251,8 +261,9 @@ void net_client_pop_msg_info_free(NetClientPopMessageInfo *info);
* - 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> or <i>CRAM-SHA1</i> (see <a href="https://tools.ietf.org/html/rfc5034">RFC 5034</a>),
depending upon the
- * capabilities reported by the server.
+ * <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.
*/
diff --git a/libnetclient/net-client-smtp.c b/libnetclient/net-client-smtp.c
index 1d4dec1..80ed064 100644
--- a/libnetclient/net-client-smtp.c
+++ b/libnetclient/net-client-smtp.c
@@ -43,7 +43,9 @@ typedef struct {
} smtp_rcpt_t;
-#define MAX_SMTP_LINE_LEN 512U
+/* 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
#define SMTP_DATA_BUF_SIZE 8192U
@@ -57,10 +59,11 @@ static gboolean net_client_smtp_execute(NetClientSmtp *client, const gchar *requ
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_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_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 gchar *net_client_smtp_dsn_to_string(const NetClientSmtp *client, NetClientSmtpDsnMode dsn_mode);
@@ -149,14 +152,18 @@ 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", client);
- g_signal_emit_by_name(client, "auth", &auth_data);
- if ((auth_data != NULL) && (auth_data[0] != NULL) && (auth_data[1] != NULL)) {
+ 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);
memset(auth_data[0], 0, strlen(auth_data[0]));
- memset(auth_data[1], 0, strlen(auth_data[1]));
+ if (auth_data[1] != NULL) {
+ memset(auth_data[1], 0, strlen(auth_data[1]));
+ }
}
g_strfreev(auth_data);
}
@@ -370,29 +377,34 @@ 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)
{
- gboolean result;
+ gboolean result = FALSE;
guint auth_mask;
- g_return_val_if_fail(NET_IS_CLIENT_SMTP(client) && (user != NULL) && (passwd != NULL), FALSE);
-
+ /* calculate the possible authentication methods */
if (net_client_is_encrypted(NET_CLIENT(client))) {
auth_mask = client->priv->auth_allowed[0] & auth_supported;
} else {
auth_mask = client->priv->auth_allowed[1] & auth_supported;
}
- 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);
+ 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 {
- g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
- _("no suitable authentication mechanism"));
- result = FALSE;
+ /* 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);
+ } else {
+ g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
+ _("no suitable authentication mechanism"));
+ }
}
return result;
@@ -464,6 +476,63 @@ net_client_smtp_auth_cram(NetClientSmtp *client, GChecksumType chksum_type, cons
}
+#if defined(HAVE_GSSAPI)
+
+static gboolean
+net_client_smtp_auth_gssapi(NetClientSmtp *client, const gchar *user, GError **error)
+{
+ NetClientGssCtx *gss_ctx;
+ gboolean result = FALSE;
+
+ gss_ctx = net_client_gss_ctx_new("smtp", net_client_get_host(NET_CLIENT(client)), user, error);
+ if (gss_ctx != NULL) {
+ gint state;
+ gboolean initial = TRUE;
+ gchar *input_token = NULL;
+ gchar *output_token = NULL;
+
+ do {
+ state = net_client_gss_auth_step(gss_ctx, input_token, &output_token, error);
+ g_free(input_token);
+ input_token = NULL;
+ if (state >= 0) {
+ if (initial) {
+ result = net_client_smtp_execute(client, "AUTH GSSAPI %s",
&input_token, error, output_token);
+ initial = FALSE;
+ } else {
+ result = net_client_smtp_execute(client, "%s", &input_token, error,
output_token);
+ }
+ }
+ g_free(output_token);
+ } while (result && (state == 0));
+
+ 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);
+ g_free(output_token);
+ }
+ }
+ g_free(input_token);
+ net_client_gss_ctx_free(gss_ctx);
+ }
+
+ return result;
+}
+
+#else
+
+/*lint -e{715} -e{818} */
+static gboolean
+net_client_smtp_auth_gssapi(NetClientSmtp G_GNUC_UNUSED *client, const gchar G_GNUC_UNUSED *user, GError
G_GNUC_UNUSED **error)
+{
+ g_assert_not_reached(); /* this should never happen! */
+ return FALSE; /* never reached, make gcc happy */
+}
+
+#endif /* HAVE_GSSAPI */
+
+
/* note: if supplied, last_reply is never NULL on success */
static gboolean
net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gchar **last_reply, GError **error,
...)
@@ -530,6 +599,10 @@ net_client_smtp_ehlo(NetClientSmtp *client, guint *auth_supported, gboolean *can
*auth_supported |= NET_CLIENT_SMTP_AUTH_CRAM_MD5;
} else if (strcmp(auth[n], "CRAM-SHA1") == 0) {
*auth_supported |= NET_CLIENT_SMTP_AUTH_CRAM_SHA1;
+#if defined(HAVE_GSSAPI)
+ } else if (strcmp(auth[n], "GSSAPI") == 0) {
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_GSSAPI;
+#endif
} else {
/* other auth methods are ignored for the time being
*/
}
@@ -639,6 +712,7 @@ net_client_smtp_dsn_to_string(const NetClientSmtp *client, NetClientSmtpDsnMode
dsn_buf = g_string_new(" NOTIFY=");
start_len = dsn_buf->len;
+ /*lint -save -e655 -e9027 -e9029 accept logical AND for enum, MISRA C:2012 Rules 10.1,
10.4 */
if ((dsn_mode & NET_CLIENT_SMTP_DSN_DELAY) == NET_CLIENT_SMTP_DSN_DELAY) {
dsn_buf = g_string_append(dsn_buf, "DELAY");
}
@@ -654,6 +728,7 @@ net_client_smtp_dsn_to_string(const NetClientSmtp *client, NetClientSmtpDsnMode
}
dsn_buf = g_string_append(dsn_buf, "SUCCESS");
}
+ /*lint -restore */
result = g_string_free(dsn_buf, FALSE);
} else {
result = g_strdup("");
diff --git a/libnetclient/net-client-smtp.h b/libnetclient/net-client-smtp.h
index 1eb35fc..5634c81 100644
--- a/libnetclient/net-client-smtp.h
+++ b/libnetclient/net-client-smtp.h
@@ -60,11 +60,16 @@ enum _NetClientSmtpError {
#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)
+#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_CRAM_MD5 +
NET_CLIENT_SMTP_AUTH_CRAM_SHA1)
+ (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
/** @} */
@@ -146,8 +151,14 @@ 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. In order to shut down a
successfully established
- * connection, just call <tt>g_object_unref()</tt> on the SMTP network client object.
+ * 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_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.
+ *
+ * In order to shut down a successfully established connection, just call <tt>g_object_unref()</tt> on the
SMTP network client
+ * object.
*
* @note The caller must free the returned greeting when it is not needed any more.
*/
@@ -249,6 +260,7 @@ void net_client_smtp_msg_free(NetClientSmtpMessage *smtp_msg);
* - CRAM-SHA1 according to <a href="https://tools.ietf.org/html/rfc_TBD">TBD</a>
* - PLAIN according to <a href="https://tools.ietf.org/html/rfc4616">RFC 4616</a>
* - LOGIN
+ * - GSSAPI according to <a href="https://tools.ietf.org/html/rfc4752">RFC 4752</a> (if configured with
gssapi support)
* - STARTTLS encryption according to <a href="https://tools.ietf.org/html/rfc3207">RFC 3207</a>
* - Delivery Status Notifications (DSNs) according to <a href="https://tools.ietf.org/html/rfc3461">RFC
3461</a>
*/
diff --git a/libnetclient/net-client-utils.c b/libnetclient/net-client-utils.c
index 79d1677..bcc4e80 100644
--- a/libnetclient/net-client-utils.c
+++ b/libnetclient/net-client-utils.c
@@ -14,8 +14,35 @@
#include <string.h>
#include <stdio.h>
+#include <glib/gi18n.h>
+#include "net-client.h"
#include "net-client-utils.h"
+#if defined(HAVE_GSSAPI)
+# if defined(HAVE_HEIMDAL)
+# include <gssapi.h>
+# else
+# include <gssapi/gssapi.h>
+# endif
+#endif /* HAVE_GSSAPI */
+
+
+#if defined(HAVE_GSSAPI)
+
+struct _NetClientGssCtx {
+ gchar *user;
+ gss_ctx_id_t context;
+ gss_name_t target_name;
+ OM_uint32 req_flags;
+};
+
+
+static gchar *gss_error_string(OM_uint32 err_maj, OM_uint32 err_min)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+#endif /* HAVE_GSSAPI */
+
+
gchar *
net_client_cram_calc(const gchar *base64_challenge, GChecksumType chksum_type, const gchar *user, const
gchar *passwd)
{
@@ -85,3 +112,209 @@ net_client_auth_plain_calc(const gchar *user, const gchar *passwd)
return base64_buf;
}
+
+
+#if defined(HAVE_GSSAPI)
+
+NetClientGssCtx *
+net_client_gss_ctx_new(const gchar *service, const gchar *host, const gchar *user, GError **error)
+{
+ NetClientGssCtx *gss_ctx;
+ gchar *service_str;
+ gchar *colon;
+ gss_buffer_desc request;
+ OM_uint32 maj_stat;
+ OM_uint32 min_stat;
+
+ g_return_val_if_fail((service != NULL) && (host != NULL), NULL);
+
+ gss_ctx = g_new0(NetClientGssCtx, 1U);
+ service_str = g_strconcat(service, "@", host, NULL);
+ colon = strchr(service_str, ':'); /*lint !e9034 accept char literal as int */
+ if (colon != NULL) {
+ colon[0] = '\0'; /* strip off any port specification */
+ }
+ request.value = service_str;
+ request.length = strlen(service_str) + 1U;
+ maj_stat = gss_import_name(&min_stat, &request, GSS_C_NT_HOSTBASED_SERVICE, &gss_ctx->target_name);
+ if (GSS_ERROR(maj_stat) != 0U) {
+ gchar *gss_err = gss_error_string(maj_stat, min_stat);
+
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GSSAPI, _("importing GSS service
name %s failed: %s"),
+ service_str, gss_err);
+ g_free(gss_err);
+ g_free(gss_ctx);
+ gss_ctx = NULL;
+ } else {
+ /* configure the context according to RFC 4752, Sect. 3.1 */
+ gss_ctx->req_flags = GSS_C_INTEG_FLAG + GSS_C_MUTUAL_FLAG + GSS_C_SEQUENCE_FLAG + GSS_C_CONF_FLAG;
+ gss_ctx->user = g_strdup(user);
+ }
+
+ g_free(service_str);
+ return gss_ctx;
+}
+
+
+gint
+net_client_gss_auth_step(NetClientGssCtx *gss_ctx, const gchar *in_token, gchar **out_token, GError **error)
+{
+ OM_uint32 maj_stat;
+ OM_uint32 min_stat;
+ gss_buffer_desc input_token;
+ gss_buffer_desc output_token;
+ gint result;
+
+ g_return_val_if_fail((gss_ctx != NULL) && (out_token != NULL), -1);
+
+ if (in_token != NULL) {
+ gsize out_len;
+
+ input_token.value = g_base64_decode(in_token, &out_len);
+ input_token.length = out_len;
+ } else {
+ input_token.value = NULL;
+ input_token.length = 0U;
+ }
+
+ maj_stat = gss_init_sec_context(&min_stat, GSS_C_NO_CREDENTIAL, &gss_ctx->context,
gss_ctx->target_name, GSS_C_NO_OID,
+ gss_ctx->req_flags, 0U, GSS_C_NO_CHANNEL_BINDINGS, &input_token, NULL, &output_token, NULL,
NULL);
+
+ if ((maj_stat != GSS_S_COMPLETE) && (maj_stat != GSS_S_CONTINUE_NEEDED)) {
+ gchar *gss_err = gss_error_string(maj_stat, min_stat);
+
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GSSAPI, _("cannot initialize GSS
security context: %s"),
+ gss_err);
+ g_free(gss_err);
+ result = -1;
+ } else {
+ if (output_token.length > 0U) {
+ *out_token = g_base64_encode(output_token.value, output_token.length);
/*lint !e9079 (MISRA C:2012 Rule 11.5) */
+ } else {
+ *out_token = g_strdup("");
+ }
+ (void) gss_release_buffer(&min_stat, &output_token);
+ if (maj_stat == GSS_S_COMPLETE) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ }
+ (void) gss_release_buffer(&min_stat, &input_token);
+
+ return result;
+}
+
+
+gchar *
+net_client_gss_auth_finish(const NetClientGssCtx *gss_ctx, const gchar *in_token, GError **error)
+{
+ OM_uint32 maj_stat;
+ OM_uint32 min_stat;
+ gsize out_len;
+ gss_buffer_desc input_token;
+ gss_buffer_desc output_token;
+ gchar *result = NULL;
+
+ input_token.value = g_base64_decode(in_token, &out_len);
+ input_token.length = out_len;
+ maj_stat = gss_unwrap(&min_stat, gss_ctx->context, &input_token, &output_token, NULL, NULL);
+ if (maj_stat != GSS_S_COMPLETE) {
+ gchar *gss_err = gss_error_string(maj_stat, min_stat);
+
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GSSAPI, _("malformed GSS security
token: %s"), gss_err);
+ g_free(gss_err);
+ } else {
+ const guchar *src;
+
+ /* RFC 4752 requires a token length of 4, and a first octet == 0x01 */
+ src = (unsigned char *) output_token.value; /*lint !e9079 (MISRA C:2012 Rule
11.5) */
+ if ((output_token.length != 4U) || (src[0] != 0x01U)) {
+ (void) gss_release_buffer(&min_stat, &output_token);
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GSSAPI, _("malformed GSS
security token"));
+ } else {
+ guchar *dst;
+
+ (void) gss_release_buffer(&min_stat, &input_token);
+ input_token.length = strlen(gss_ctx->user) + 4U;
+ input_token.value = g_malloc(input_token.length);
+ dst = input_token.value; /*lint !e9079 (MISRA C:2012 Rule 11.5) */
+ memcpy(input_token.value, output_token.value, 4U);
+ (void) gss_release_buffer(&min_stat, &output_token);
+ memcpy(&dst[4], gss_ctx->user, input_token.length - 4U);
+
+ maj_stat = gss_wrap(&min_stat, gss_ctx->context, 0, GSS_C_QOP_DEFAULT, &input_token,
NULL, &output_token);
+ if (maj_stat != GSS_S_COMPLETE) {
+ gchar *gss_err = gss_error_string(maj_stat, min_stat);
+
+ g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GSSAPI,
_("cannot create GSS login request: %s"),
+ gss_err);
+ g_free(gss_err);
+ } else {
+ result = g_base64_encode(output_token.value, output_token.length);
/*lint !e9079 (MISRA C:2012 Rule 11.5) */
+ (void) gss_release_buffer(&min_stat, &output_token);
+ }
+ }
+ }
+
+ (void) gss_release_buffer(&min_stat, &input_token);
+ return result;
+}
+
+
+void
+net_client_gss_ctx_free(NetClientGssCtx *gss_ctx)
+{
+ if (gss_ctx != NULL) {
+ OM_uint32 min_stat;
+
+ if (gss_ctx->context != NULL) {
+ (void) gss_delete_sec_context(&min_stat, &gss_ctx->context, GSS_C_NO_BUFFER);
+ }
+ if (gss_ctx->target_name != NULL) {
+ (void) gss_release_name(&min_stat, &gss_ctx->target_name);
+ }
+ g_free(gss_ctx->user);
+ g_free(gss_ctx);
+ }
+}
+
+
+static gchar *
+gss_error_string(OM_uint32 err_maj, OM_uint32 err_min)
+{
+ OM_uint32 maj_stat;
+ OM_uint32 min_stat;
+ OM_uint32 msg_ctx;
+ gss_buffer_desc status_string;
+ GString *message = g_string_new(NULL);
+ gchar *result;
+
+ do {
+ maj_stat = gss_display_status(&min_stat, err_maj, GSS_C_GSS_CODE, GSS_C_NO_OID, &msg_ctx,
&status_string);
+ if (GSS_ERROR(maj_stat) == 0U) {
+ if (message->len > 0U) {
+ message = g_string_append(message, "; ");
+ }
+ message = g_string_append(message, (const gchar *) status_string.value); /*lint !e9079
(MISRA C:2012 Rule 11.5) */
+ (void) gss_release_buffer(&min_stat, &status_string);
+
+ maj_stat = gss_display_status(&min_stat, err_min, GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx,
&status_string);
+ if (GSS_ERROR(maj_stat) == 0U) {
+ message = g_string_append(message, ": ");
+ message = g_string_append(message, (const gchar *) status_string.value); /*lint
!e9079 (MISRA C:2012 Rule 11.5) */
+ (void) gss_release_buffer(&min_stat, &status_string);
+ }
+ }
+ } while ((GSS_ERROR(maj_stat) == 0U) && (msg_ctx != 0U));
+
+ if (message->len > 0U) {
+ result = g_string_free(message, FALSE);
+ } else {
+ (void) g_string_free(message, TRUE);
+ result = g_strdup_printf(_("unknown error code %u:%u"), (unsigned) err_maj, (unsigned) err_min);
+ }
+ return result;
+}
+
+#endif /* HAVE_GSSAPI */
diff --git a/libnetclient/net-client-utils.h b/libnetclient/net-client-utils.h
index c3065bb..f4c0a8f 100644
--- a/libnetclient/net-client-utils.h
+++ b/libnetclient/net-client-utils.h
@@ -16,12 +16,20 @@
#define NET_CLIENT_UTILS_H_
+#include "config.h"
#include <gio/gio.h>
G_BEGIN_DECLS
+#if defined(HAVE_GSSAPI)
+
+typedef struct _NetClientGssCtx NetClientGssCtx;
+
+#endif /* HAVE_GSSAPI */
+
+
/** @brief Calculate a CRAM authentication string
*
* @param base64_challenge base64-encoded challenge sent by the server
@@ -56,13 +64,74 @@ const gchar *net_client_chksum_to_str(GChecksumType chksum_type);
* @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
- * according to the <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.
+ * 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;
+#if defined(HAVE_GSSAPI)
+
+/** @brief Create a GSSAPI authentication context
+ *
+ * @param service service name (<i>smtp</i>, <i>imap</i> or <i>pop</i>)
+ * @param host full-qualified host name of the machine providing the service
+ * @param user user name
+ * @param error filled with error information on error
+ * @return a newly allocated and initialised GSSAPI context on success, or NULL on error
+ *
+ * Create a new authentication context for Kerberos v5 SASL AUTH GSSAPI based authentication for the passed
service and host
+ * according to <a href="https://tools.ietf.org/html/rfc4752">RFC 4752</a>. The returned context shall be
used for the
+ * authentication process and must be freed by calling net_client_gss_ctx_free().
+ *
+ * @note The host may optionally contain a port definition (e.g. <tt>smtp.mydom.org:25</tt>) which will be
omitted.
+ * @sa net_client_gss_auth_step(), net_client_gss_auth_finish()
+ */
+NetClientGssCtx *net_client_gss_ctx_new(const gchar *service, const gchar *host, const gchar *user, GError
**error)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+
+/** @brief Perform a GSSAPI authentication step
+ *
+ * @param gss_ctx GSSAPI authentication context
+ * @param in_token base64-encoded input token, or NULL for the initial authentication step
+ * @param out_token filled with the base64-encoded output token on success
+ * @param error filled with error information on error
+ * @return 0 if an additional step has to be performed, 1 if net_client_gss_auth_finish() shall be called,
or -1 on error
+ *
+ * Initially, the function shall be called with a NULL input token. The resulting output token shall be
sent to the remote server
+ * to obtain a new input token until the function returns 1. Then, net_client_gss_auth_finish() shall be
called to finish the
+ * authentication process.
+ */
+gint net_client_gss_auth_step(NetClientGssCtx *gss_ctx, const gchar *in_token, gchar **out_token, GError
**error)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+
+/** @brief Finish the GSSAPI authentication
+ *
+ * @param gss_ctx GSSAPI authentication context
+ * @param in_token base64-encoded input token, received in the final net_client_gss_auth_step()
+ * @param error filled with error information on error
+ * @return the base64-encoded final authentication token on success, or NULL on error
+ *
+ * Create the final token which has to be sent to the remote server to finalise the GSSAPI authentication
process.
+ */
+gchar *net_client_gss_auth_finish(const NetClientGssCtx *gss_ctx, const gchar *in_token, GError **error)
+ G_GNUC_WARN_UNUSED_RESULT;
+
+
+/** @brief Free a GSSAPI authentication context
+ *
+ * @param gss_ctx GSSAPI authentication context
+ *
+ * Free all resources in the passed GSSAPI authentication context by net_client_gss_ctx_new() and the
context itself.
+ */
+void net_client_gss_ctx_free(NetClientGssCtx *gss_ctx);
+
+#endif /* HAVE_GSSAPI */
+
+
/** @file
*
* This module implements authentication-related helper functions for the network client library.
diff --git a/libnetclient/net-client.c b/libnetclient/net-client.c
index ed58d44..8fabc37 100644
--- a/libnetclient/net-client.c
+++ b/libnetclient/net-client.c
@@ -19,9 +19,6 @@
#include "net-client.h"
-#define LINE_BUF_LEN 1024U
-
-
struct _NetClientPrivate {
gchar *host_and_port;
guint16 default_port;
@@ -92,10 +89,11 @@ net_client_configure(NetClient *client, const gchar *host_and_port, guint16 defa
const gchar *
-net_client_get_host(NetClient *client)
+net_client_get_host(const NetClient *client)
{
const gchar *result;
+ /*lint -e{9005} cast'ing away const in the next statement is fine */
if (NET_IS_CLIENT(client)) {
result = client->priv->host_and_port;
} else {
@@ -202,7 +200,7 @@ net_client_write_buffer(NetClient *client, const gchar *buffer, gsize count, GEr
} else {
gsize bytes_written;
- if ((count > 2U) && (buffer[count - 1U] == '\n')) {
+ if ((count >= 2U) && (buffer[count - 1U] == '\n')) {
g_debug("W '%.*s'", (int) count - 2, buffer);
} else {
g_debug("W '%.*s'", (int) count, buffer);
@@ -221,20 +219,20 @@ gboolean
net_client_vwrite_line(NetClient *client, const gchar *format, va_list args, GError **error)
{
gboolean result;
- gchar buffer[LINE_BUF_LEN];
- gint buf_len;
+ GString *buffer;
g_return_val_if_fail(NET_IS_CLIENT(client) && (format != NULL), FALSE);
- buf_len = g_vsnprintf(buffer, client->priv->max_line_len - 2U, format, args);
- if ((buf_len < 0) || ((client->priv->max_line_len > 0U) && ((gsize) buf_len >
(client->priv->max_line_len - 2U)))) {
+ buffer = g_string_new(NULL);
+ g_string_vprintf(buffer, format, args);
+ if ((client->priv->max_line_len > 0U) && (buffer->len > client->priv->max_line_len)) {
g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_LINE_TOO_LONG, _("line too
long"));
result = FALSE;
} else {
- buffer[buf_len] = '\r';
- buffer[buf_len + 1] = '\n';
- result = net_client_write_buffer(client, buffer, (gsize) buf_len + 2U, error);
+ buffer = g_string_append(buffer, "\r\n");
+ result = net_client_write_buffer(client, buffer->str, buffer->len, error);
}
+ (void) g_string_free(buffer, TRUE);
return result;
}
@@ -460,7 +458,7 @@ net_client_class_init(NetClientClass *klass)
gobject_class->finalize = net_client_finalise;
signals[0] = g_signal_new("cert-check", NET_CLIENT_TYPE, G_SIGNAL_RUN_LAST, 0U, NULL, NULL, NULL,
G_TYPE_BOOLEAN, 2U,
G_TYPE_TLS_CERTIFICATE, G_TYPE_TLS_CERTIFICATE_FLAGS);
- signals[1] = g_signal_new("auth", NET_CLIENT_TYPE, G_SIGNAL_RUN_LAST, 0U, NULL, NULL, NULL,
G_TYPE_STRV, 0U);
+ signals[1] = g_signal_new("auth", NET_CLIENT_TYPE, G_SIGNAL_RUN_LAST, 0U, NULL, NULL, NULL,
G_TYPE_STRV, 1U, G_TYPE_BOOLEAN);
signals[2] = g_signal_new("cert-pass", NET_CLIENT_TYPE, G_SIGNAL_RUN_LAST, 0U, NULL, NULL, NULL,
G_TYPE_STRING, 1U,
G_TYPE_BYTE_ARRAY);
}
diff --git a/libnetclient/net-client.h b/libnetclient/net-client.h
index be89df1..ea6e756 100644
--- a/libnetclient/net-client.h
+++ b/libnetclient/net-client.h
@@ -68,7 +68,8 @@ enum _NetClientError {
NET_CLIENT_ERROR_CONNECTION_LOST, /**< The connection is lost. */
NET_CLIENT_ERROR_TLS_ACTIVE, /**< TLS is already active for the connection. */
NET_CLIENT_ERROR_LINE_TOO_LONG, /**< The line is too long. */
- NET_CLIENT_ERROR_GNUTLS /**< A GnuTLS error occurred. */
+ NET_CLIENT_ERROR_GNUTLS, /**< A GnuTLS error occurred. */
+ NET_CLIENT_ERROR_GSSAPI /**< A GSSAPI error occurred. */
};
@@ -112,7 +113,7 @@ gboolean net_client_configure(NetClient *client, const gchar *host_and_port, gui
*
* @note The function returns the value of @em host_and_port set by net_client_new() or
net_client_configure().
*/
-const gchar *net_client_get_host(NetClient *client);
+const gchar *net_client_get_host(const NetClient *client);
/** @brief Connect a network client
@@ -294,10 +295,11 @@ gboolean net_client_set_timeout(NetClient *client, guint timeout_secs);
* @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, 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. 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, 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.
*/
diff --git a/libnetclient/test/tests.c b/libnetclient/test/tests.c
index 9213174..6675007 100644
--- a/libnetclient/test/tests.c
+++ b/libnetclient/test/tests.c
@@ -282,14 +282,14 @@ msg_data_cb(gchar *buffer, gsize count, gpointer user_data, GError **error)
static gchar **
-get_auth(NetClient *client, gpointer user_data)
+get_auth(NetClient *client, gboolean need_pwd, gpointer user_data)
{
gchar ** result;
- g_message("%s(%p, %p)", __func__, client, user_data);
+ g_message("%s(%p, %d, %p)", __func__, client, need_pwd, user_data);
result = g_new0(gchar *, 3U);
result[0] = g_strdup("john.doe");
- if (user_data != NULL) {
+ if (need_pwd && (user_data != NULL)) {
result[1] = g_strdup("@ C0mplex P@sswd");
}
return result;
@@ -376,7 +376,7 @@ test_smtp(void)
// no password: anonymous
sput_fail_unless((smtp = net_client_smtp_new("localhost", 65025, NET_CLIENT_CRYPT_NONE)) != NULL,
"localhost:65025");
g_signal_connect(G_OBJECT(smtp), "auth", G_CALLBACK(get_auth), NULL);
- sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL), "connect: anonymous ok (NULL passwd)");
+ sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL) == FALSE, "connect: password required");
g_object_unref(smtp);
// unencrypted, PLAIN auth
@@ -518,15 +518,15 @@ test_pop3(void)
op_res = net_client_pop_stat(pop, NULL, NULL, &error);
sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_POP_SERVER_ERR), "STAT not
allowed w/o AUTH");
g_clear_error(&error);
+ sput_fail_unless(net_client_pop_list(pop, NULL, TRUE, NULL) == FALSE, "list w/ empty target list");
+ op_res = net_client_pop_list(pop, &msg_list, TRUE, &error);
+ sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_POP_SERVER_ERR), "LIST not
allowed w/o AUTH");
+ g_clear_error(&error);
g_object_unref(pop);
sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_NONE, TRUE)) != NULL,
"localhost:64110");
g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), NULL);
- sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == TRUE, "connect: success");
- sput_fail_unless(net_client_pop_list(pop, NULL, TRUE, NULL) == FALSE, "list w/ empty target list");
- op_res = net_client_pop_list(pop, &msg_list, TRUE, &error);
- sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_POP_SERVER_ERR), "LIST not
allowed w/ empty AUTH");
- g_clear_error(&error);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == FALSE, "connect: password required");
g_object_unref(pop);
// unencrypted, force USER auth
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]