[balsa] Implement GSSAPI single sign-on for SMTP, POP3



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]