[balsa] Move POP3 support to the GIO-based networking lib
- From: Peter Bloomfield <peterb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [balsa] Move POP3 support to the GIO-based networking lib
- Date: Fri, 7 Apr 2017 23:57:44 +0000 (UTC)
commit 08fc014bf7fc57a169f93feb6ad884ea5d7dad55
Author: Peter Bloomfield <PeterBloomfield bellsouth net>
Date: Fri Apr 7 19:46:29 2017 -0400
Move POP3 support to the GIO-based networking lib
This part of the patch adds the POP3 implementation and unit tests to libnetclient, and adds a few small
improvements to the existing files.
* libnetclient/net-client-pop.c, libnetclient/net-client-pop.h: implement the NetClientPop class
* libnetclient/Makefile.am: include net-client-pop.[hc] in the build
* libnetclient/README: update documentation
* libnetclient/net-client-smtp.c: as the starttls and authentication capabilities are required in the
connect method only, remove them from the object data; improve error checking
* libnetclient/net-client-smtp.h, libnetclient/net-client.h: documentation fixes
* libnetclient/net-client.c: add more debug messages
* libnetclient/test/Makefile.am, libnetclient/test/inetsim.conf, libnetclient/test/tests.c: include
NetClientPop unit tests
* libnetclient/test/inetsim-1.2.6-POP3.diff: patch for INetSim fixing two bugs in its POP3 simulation,
and adding pipelining support (see libnetclient/README)
* libnetclient/test/start-test-env.sh: generated file, should be removed from the repo
libnetclient/Makefile.am | 2 +
libnetclient/README | 7 +-
libnetclient/net-client-pop.c | 808 +++++++++++++++++++++++++++++
libnetclient/net-client-pop.h | 262 ++++++++++
libnetclient/net-client-smtp.c | 55 +-
libnetclient/net-client-smtp.h | 4 +-
libnetclient/net-client.c | 13 +-
libnetclient/net-client.h | 12 +-
libnetclient/test/Makefile.am | 5 +-
libnetclient/test/inetsim-1.2.6-POP3.diff | 45 ++
libnetclient/test/inetsim.conf | 7 +-
libnetclient/test/start-test-env.sh | 20 -
libnetclient/test/tests.c | 251 +++++++++-
13 files changed, 1413 insertions(+), 78 deletions(-)
---
diff --git a/libnetclient/Makefile.am b/libnetclient/Makefile.am
index 3562e41..89da9de 100644
--- a/libnetclient/Makefile.am
+++ b/libnetclient/Makefile.am
@@ -4,6 +4,8 @@ noinst_LIBRARIES = libnetclient.a
libnetclient_a_SOURCES = \
net-client.c \
net-client.h \
+ net-client-pop.c \
+ net-client-pop.h \
net-client-smtp.c \
net-client-smtp.h \
net-client-utils.c \
diff --git a/libnetclient/README b/libnetclient/README
index f5d4d23..cdebc57 100644
--- a/libnetclient/README
+++ b/libnetclient/README
@@ -19,7 +19,8 @@ Purpose
This library provides an implementation of CRLF-terminated line-based client
protocols built on top of GIO. It provides a base module, containing the
-line-based IO methods, and on top of that a SMTP (RFC5321) client class.
+line-based IO methods, and on top of that POP3 (RFC1939) and SMTP (RFC5321)
+client classes.
Coding Style
@@ -72,6 +73,10 @@ are required for running them:
Note that most of these requirements are typically available pre-packaged
for your favorite distribution.
+Unfortunately, INetSim 1.2.6 has two little bugs in POP3 handling and does
+not offer pipelining. The file test/inetsim-1.2.6-POP3.diff contains a
+patch which fixes these issues.
+
For running the tests, open two terminal windows, and cd to the test folder
of this package.
diff --git a/libnetclient/net-client-pop.c b/libnetclient/net-client-pop.c
new file mode 100644
index 0000000..7145276
--- /dev/null
+++ b/libnetclient/net-client-pop.c
@@ -0,0 +1,808 @@
+/* NetClient - simple line-based network client library
+ *
+ * Copyright (C) Albrecht Dreß <mailto:albrecht dress arcor de> 2017
+ *
+ * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License
+ * as published by the Free Software Foundation; either version 3 of the License, or (at your option) any
later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with this library. If not,
see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <glib/gi18n.h>
+#include "net-client-utils.h"
+#include "net-client-pop.h"
+
+
+struct _NetClientPopPrivate {
+ NetClientCryptMode crypt_mode;
+ gchar *apop_banner;
+ guint auth_allowed[2]; /** 0: encrypted, 1: unencrypted */
+ gboolean can_pipelining;
+ gboolean can_uidl;
+ gboolean use_pipelining;
+};
+
+
+/* Note: the maximum line length of a message body downloaded from the POP3 server may be up to 998 chars,
excluding the terminating
+ * CRLF, see RFC 5322, Sect. 2.1.1. However, it also states that "Receiving implementations would do well
to handle an arbitrarily
+ * large number of characters in a line for robustness sake", so we actually accept lines from POP3 of
unlimited length. */
+#define MAX_POP_LINE_LEN 0U
+#define POP_DATA_BUF_SIZE 4096U
+
+
+/*lint -save -e9026 allow function-like macros, see MISRA C:2012, Directive 4.9 */
+#define IS_ML_TERM(str) ((str[0] == '.') && (str[1] == '\0'))
+/*lint -emacro(9079,POP_MSG_INFO) -emacro(9087,POP_MSG_INFO)
+ * allow conversion of GList data pointer, MISRA C:2012, Rules 11.3, 11.5 */
+#define POP_MSG_INFO(list) ((NetClientPopMessageInfo *) ((list)->data))
+/*lint -restore */
+
+
+G_DEFINE_TYPE(NetClientPop, net_client_pop, NET_CLIENT_TYPE)
+
+
+static void net_client_pop_finalise(GObject *object);
+static void net_client_pop_get_capa(NetClientPop *client, guint *auth_supported);
+static gboolean net_client_pop_read_reply(NetClientPop *client, gchar **reply, GError **error);
+static gboolean net_client_pop_uidl(NetClientPop *client, GList * const *msg_list, GError **error);
+static gboolean net_client_pop_starttls(NetClientPop *client, GError **error);
+static gboolean net_client_pop_execute(NetClientPop *client, const gchar *request_fmt, gchar **last_reply,
GError **error, ...)
+ G_GNUC_PRINTF(2, 5);
+static gboolean net_client_pop_execute_sasl(NetClientPop *client, const gchar *request_fmt, gchar
**challenge, GError **error, ...);
+static gboolean net_client_pop_auth(NetClientPop *client, const gchar *user, const gchar *passwd, guint
auth_supported,
+ GError **error);
+static gboolean net_client_pop_auth_plain(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
+static gboolean net_client_pop_auth_login(NetClientPop *client, const gchar *user, const gchar *passwd,
GError **error);
+static gboolean net_client_pop_auth_user_pass(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
+static gboolean net_client_pop_auth_apop(NetClientPop *client, const gchar* user, const gchar* passwd,
GError** error);
+static gboolean net_client_pop_auth_cram(NetClientPop *client, GChecksumType chksum_type, const gchar *user,
const gchar *passwd,
+ GError **error);
+static gboolean net_client_pop_retr_msg(NetClientPop *client, const NetClientPopMessageInfo *info,
NetClientPopMsgCb callback,
+ gpointer user_data, GError
**error);
+
+
+NetClientPop *
+net_client_pop_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mode, gboolean use_pipelining)
+{
+ NetClientPop *client;
+
+ g_return_val_if_fail((host != NULL) && (crypt_mode >= NET_CLIENT_CRYPT_ENCRYPTED) && (crypt_mode <=
NET_CLIENT_CRYPT_NONE),
+ NULL);
+
+ client = NET_CLIENT_POP(g_object_new(NET_CLIENT_POP_TYPE, NULL));
+ if (client != NULL) {
+ if (!net_client_configure(NET_CLIENT(client), host, port, MAX_POP_LINE_LEN, NULL)) {
+ g_object_unref(G_OBJECT(client));
+ client = NULL;
+ } else {
+ client->priv->crypt_mode = crypt_mode;
+ client->priv->use_pipelining = use_pipelining;
+ }
+ }
+
+ return client;
+}
+
+
+gboolean
+net_client_pop_allow_auth(NetClientPop *client, gboolean encrypted, guint allow_auth)
+{
+ /* paranoia check */
+ g_return_val_if_fail(NET_IS_CLIENT_POP(client), FALSE);
+ if (encrypted) {
+ client->priv->auth_allowed[0] = allow_auth;
+ } else {
+ client->priv->auth_allowed[1] = allow_auth;
+ }
+ return TRUE;
+}
+
+
+gboolean
+net_client_pop_connect(NetClientPop *client, gchar **greeting, GError **error)
+{
+ gchar *server_msg = NULL;
+ guint auth_supported = 0U;
+ gboolean result;
+
+ /* paranoia checks */
+ g_return_val_if_fail(NET_IS_CLIENT_POP(client), FALSE);
+
+ /* establish connection, and immediately switch to TLS if required */
+ result = net_client_connect(NET_CLIENT(client), error);
+ if (result && (client->priv->crypt_mode == NET_CLIENT_CRYPT_ENCRYPTED)) {
+ result = net_client_start_tls(NET_CLIENT(client), error);
+ }
+
+ /* get the greeting */
+ if (result) {
+ result = net_client_pop_read_reply(client, &server_msg, error);
+ }
+
+ /* extract the APOP banner */
+ if (result) {
+ const gchar *ang_open;
+
+ ang_open = strchr(server_msg, '<'); /*lint !e668 !e9034 server_msg
cannot be NULL; accept char literal as int */
+ if (ang_open != NULL) {
+ const gchar *ang_close;
+
+ 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);
+ auth_supported = NET_CLIENT_POP_AUTH_APOP;
+ }
+ }
+ if (greeting != NULL) {
+ *greeting = g_strdup(server_msg);
+ }
+ g_free(server_msg);
+ }
+
+ /* perform STLS if required- note that some servers support STLS, but do not announce it. So just
try... */
+ if (result &&
+ ((client->priv->crypt_mode == NET_CLIENT_CRYPT_STARTTLS) || (client->priv->crypt_mode ==
NET_CLIENT_CRYPT_STARTTLS_OPT))) {
+ result = net_client_pop_starttls(client, error);
+ if (!result) {
+ if (client->priv->crypt_mode == NET_CLIENT_CRYPT_STARTTLS_OPT) {
+ result = TRUE;
+ g_clear_error(error);
+ }
+ }
+ }
+
+ /* read the capabilities (which may be unsupported, so ignore any negative result) */
+ if (result) {
+ net_client_pop_get_capa(client, &auth_supported);
+ }
+
+ /* authenticate if we were successful so far */
+ if (result) {
+ gchar **auth_data;
+
+ auth_data = NULL;
+ 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)) {
+ 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]));
+ }
+ g_strfreev(auth_data);
+ }
+
+ return result;
+}
+
+
+gboolean
+net_client_pop_stat(NetClientPop *client, gsize *msg_count, gsize *mbox_size, GError **error)
+{
+ gboolean result;
+ gchar *stat_buf;
+
+ /* paranoia checks */
+ g_return_val_if_fail(NET_IS_CLIENT_POP(client), FALSE);
+
+ /* run the STAT command */
+ result = net_client_pop_execute(client, "STAT", &stat_buf, error);
+ if (result) {
+ unsigned long count;
+ unsigned long total_size;
+
+ if (sscanf(stat_buf, "%lu %lu", &count, &total_size) == 2) {
+ if (msg_count != NULL) {
+ *msg_count = count;
+ }
+ if (mbox_size != NULL) {
+ *mbox_size = total_size;
+ }
+ } else {
+ result = FALSE;
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_PROTOCOL,
_("bad server reply: %s"),
+ stat_buf);
+ }
+
+ g_free(stat_buf);
+ }
+
+ return result;
+}
+
+
+gboolean
+net_client_pop_list(NetClientPop *client, GList **msg_list, gboolean with_uid, GError **error)
+{
+ gboolean result;
+ gboolean done;
+
+ /* paranoia checks */
+ g_return_val_if_fail(NET_IS_CLIENT_POP(client) && (msg_list != NULL), FALSE);
+
+ *msg_list = NULL;
+
+ /* run the LIST command */
+ result = net_client_pop_execute(client, "LIST", NULL, error);
+ done = FALSE;
+ while (result && !done) {
+ gchar *reply;
+
+ result = net_client_read_line(NET_CLIENT(client), &reply, error);
+ if (result) {
+ if (IS_ML_TERM(reply)) {
+ done = TRUE;
+ } else {
+ NetClientPopMessageInfo *info;
+
+ info = g_new0(NetClientPopMessageInfo, 1U);
+ *msg_list = g_list_prepend(*msg_list, info);
+ if (sscanf(reply, "%u %lu", &info->id, &info->size) != 2) {
+ result = FALSE;
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_POP_PROTOCOL, _("bad server reply"));
+ }
+ }
+ g_free(reply);
+ }
+ }
+
+ /* on success, turn the list into the order reported by the remote server */
+ if (result) {
+ *msg_list = g_list_reverse(*msg_list);
+ }
+
+ /* get all uid's if requested */
+ if (result && with_uid && client->priv->can_uidl && (*msg_list != NULL)) {
+ result = net_client_pop_uidl(client, msg_list, error);
+ }
+
+ if (!result) {
+ g_list_free_full(*msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+
+ return result;
+}
+
+
+gboolean
+net_client_pop_retr(NetClientPop *client, GList *msg_list, NetClientPopMsgCb callback, gpointer user_data,
GError **error)
+{
+ gboolean result;
+ gboolean pipelining;
+ const GList *p;
+
+ /* paranoia checks */
+ g_return_val_if_fail(NET_IS_CLIENT_POP(client) && (msg_list != NULL) && (callback != NULL), FALSE);
+
+ /* pipelining: send all RETR commands */
+ pipelining = client->priv->can_pipelining && client->priv->use_pipelining;
+ if (pipelining) {
+ GString *retr_buf;
+
+ retr_buf = g_string_sized_new(10U * g_list_length(msg_list));
+ for (p = msg_list; p != NULL; p = p->next) {
+ g_string_append_printf(retr_buf, "RETR %u\r\n", POP_MSG_INFO(p)->id);
+ }
+ result = net_client_write_buffer(NET_CLIENT(client), retr_buf->str, retr_buf->len, error);
+ (void) g_string_free(retr_buf, TRUE);
+ } else {
+ result = TRUE;
+ }
+
+ for (p = msg_list; result && (p != NULL); p = p->next) {
+ const NetClientPopMessageInfo *info = POP_MSG_INFO(p);
+
+ if (pipelining) {
+ result = net_client_pop_read_reply(client, NULL, error);
+ } else {
+ result = net_client_pop_execute(client, "RETR %u", NULL, error, info->id);
+ }
+ if (result) {
+ result = net_client_pop_retr_msg(client, info, callback, user_data, error);
+ }
+ }
+
+ return result;
+}
+
+
+gboolean
+net_client_pop_dele(NetClientPop *client, GList *msg_list, GError **error)
+{
+ gboolean result;
+ gboolean pipelining;
+ const GList *p;
+
+ /* paranoia checks */
+ g_return_val_if_fail(NET_IS_CLIENT_POP(client) && (msg_list != NULL), FALSE);
+
+ /* pipelining: send all DELE commands */
+ pipelining = client->priv->can_pipelining && client->priv->use_pipelining;
+ if (pipelining) {
+ GString *dele_buf;
+
+ dele_buf = g_string_sized_new(10U * g_list_length(msg_list));
+ for (p = msg_list; p != NULL; p = p->next) {
+ g_string_append_printf(dele_buf, "DELE %u\r\n", POP_MSG_INFO(p)->id);
+ }
+ result = net_client_write_buffer(NET_CLIENT(client), dele_buf->str, dele_buf->len, error);
+ (void) g_string_free(dele_buf, TRUE);
+ } else {
+ result = TRUE;
+ }
+
+ for (p = msg_list; result && (p != NULL); p = p->next) {
+ const NetClientPopMessageInfo *info = POP_MSG_INFO(p);
+
+ if (pipelining) {
+ result = net_client_pop_read_reply(client, NULL, error);
+ } else {
+ result = net_client_pop_execute(client, "DELE %u", NULL, error, info->id);
+ }
+ }
+
+ return result;
+
+}
+
+
+void
+net_client_pop_msg_info_free(NetClientPopMessageInfo *info)
+{
+ if (info != NULL) {
+ g_free(info->uid);
+ g_free(info);
+ }
+}
+
+
+/* == local functions
=========================================================================================================== */
+
+static void
+net_client_pop_class_init(NetClientPopClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+ gobject_class->finalize = net_client_pop_finalise;
+}
+
+
+static void
+net_client_pop_init(NetClientPop *self)
+{
+ self->priv = g_new0(NetClientPopPrivate, 1U);
+ self->priv->auth_allowed[0] = NET_CLIENT_POP_AUTH_ALL;
+ self->priv->auth_allowed[1] = NET_CLIENT_POP_AUTH_SAFE;
+}
+
+
+static void
+net_client_pop_finalise(GObject *object)
+{
+ const NetClientPop *client = NET_CLIENT_POP(object);
+ const GObjectClass *parent_class = G_OBJECT_CLASS(net_client_pop_parent_class);
+
+ /* send the 'QUIT' command - no need to evaluate the reply or check for errors */
+ (void) net_client_execute(NET_CLIENT(client), NULL, "QUIT", NULL);
+
+ if (client->priv != NULL) {
+ g_free(client->priv->apop_banner);
+ g_free(client->priv);
+ }
+ (*parent_class->finalize)(object);
+}
+
+
+/* Note: if supplied, reply is never NULL on success */
+static gboolean
+net_client_pop_read_reply(NetClientPop *client, gchar **reply, GError **error)
+{
+ gboolean result;
+ gchar *reply_buf;
+
+ result = net_client_read_line(NET_CLIENT(client), &reply_buf, error);
+ if (result) {
+ if (strncmp(reply_buf, "+OK", 3U) == 0) {
+ if ((strlen(reply_buf) > 3U) && (reply != NULL)) {
+ *reply = g_strdup(&reply_buf[4]);
+ }
+ } else if (strncmp(reply_buf, "-ERR", 4U) == 0) {
+ if (strlen(reply_buf) > 4U) {
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_POP_SERVER_ERR, _("error: %s"),
+ &reply_buf[5]);
+ } else {
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_POP_SERVER_ERR, _("error"));
+ }
+ result = FALSE;
+ } else {
+ /* unexpected server reply */
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_PROTOCOL,
_("bad server reply: %s"),
+ reply_buf);
+ result = FALSE;
+ }
+
+ g_free(reply_buf);
+ }
+
+ return result;
+}
+
+
+/* note: if supplied, last_reply is never NULL on success */
+static gboolean
+net_client_pop_execute(NetClientPop *client, const gchar *request_fmt, gchar **last_reply, GError **error,
...)
+{
+ va_list args;
+ gboolean result;
+
+ va_start(args, error); /*lint !e413 a NULL error argument is irrelevant here */
+ result = net_client_vwrite_line(NET_CLIENT(client), request_fmt, args, error);
+ va_end(args);
+
+ if (result) {
+ result = net_client_pop_read_reply(client, last_reply, error);
+ }
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_starttls(NetClientPop *client, GError **error)
+{
+ gboolean result;
+
+ result = net_client_pop_execute(client, "STLS", NULL, error);
+ if (result) {
+ result = net_client_start_tls(NET_CLIENT(client), error);
+ }
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_auth(NetClientPop *client, const gchar *user, const gchar *passwd, guint auth_supported,
GError **error)
+{
+ gboolean result;
+ 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);
+ } else {
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_POP_NO_AUTH,
+ _("no suitable authentication mechanism"));
+ result = FALSE;
+ }
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_auth_plain(NetClientPop *client, const gchar *user, const gchar *passwd, GError **error)
+{
+ gboolean result ;
+ gchar *base64_buf;
+
+ base64_buf = net_client_auth_plain_calc(user, passwd);
+ if (base64_buf != NULL) {
+ result = net_client_pop_execute_sasl(client, "AUTH PLAIN", NULL, error);
+ if (result) {
+ result = net_client_pop_execute(client, "%s", NULL, error, base64_buf);
+ }
+ memset(base64_buf, 0, strlen(base64_buf));
+ g_free(base64_buf);
+ } else {
+ result = FALSE;
+ }
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_auth_login(NetClientPop *client, const gchar *user, const gchar *passwd, GError **error)
+{
+ gboolean result;
+
+ result = net_client_pop_execute_sasl(client, "AUTH LOGIN", NULL, error);
+ if (result) {
+ gchar *base64_buf;
+
+ base64_buf = g_base64_encode((const guchar *) user, strlen(user));
+ result = net_client_pop_execute_sasl(client, "%s", NULL, error, base64_buf);
+ memset(base64_buf, 0, strlen(base64_buf));
+ g_free(base64_buf);
+ if (result) {
+ base64_buf = g_base64_encode((const guchar *) passwd, strlen(passwd));
+ result = net_client_pop_execute(client, "%s", NULL, error, base64_buf);
+ memset(base64_buf, 0, strlen(base64_buf));
+ g_free(base64_buf);
+ }
+ }
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_auth_user_pass(NetClientPop *client, const gchar *user, const gchar *passwd, GError **error)
+{
+ gboolean result;
+
+ result = net_client_pop_execute(client, "USER %s", NULL, error, user);
+ if (result) {
+ result = net_client_pop_execute(client, "PASS %s", NULL, error, passwd);
+ }
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_auth_apop(NetClientPop *client, const gchar *user, const gchar *passwd, GError **error)
+{
+ gboolean result;
+ gchar *auth_buf;
+ gchar *md5_buf;
+
+ auth_buf = g_strconcat(client->priv->apop_banner, passwd, NULL);
+ md5_buf = g_compute_checksum_for_string(G_CHECKSUM_MD5, auth_buf, -1);
+ memset(auth_buf, 0, strlen(auth_buf));
+ g_free(auth_buf);
+ result = net_client_pop_execute(client, "APOP %s %s", NULL, error, user, md5_buf);
+ memset(md5_buf, 0, strlen(md5_buf));
+ g_free(md5_buf);
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_auth_cram(NetClientPop *client, GChecksumType chksum_type, const gchar *user, const gchar
*passwd, GError **error)
+{
+ gboolean result;
+ gchar *challenge = NULL;
+
+ result = net_client_pop_execute_sasl(client, "AUTH CRAM-%s", &challenge, error,
net_client_chksum_to_str(chksum_type));
+ if (result) {
+ gchar *auth_buf;
+ auth_buf = net_client_cram_calc(challenge, chksum_type, user, passwd);
+ if (auth_buf != NULL) {
+ result = net_client_pop_execute(client, "%s", NULL, error, auth_buf);
+ memset(auth_buf, 0, strlen(auth_buf));
+ g_free(auth_buf);
+ } else {
+ result = FALSE;
+ }
+ }
+ g_free(challenge);
+
+ return result;
+}
+
+
+/* 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, ...)
+{
+ va_list args;
+ gboolean result;
+
+ va_start(args, error); /*lint !e413 a NULL error argument is irrelevant here */
+ result = net_client_vwrite_line(NET_CLIENT(client), request_fmt, args, error);
+ va_end(args);
+
+ if (result) {
+ gchar *reply_buf;
+
+ result = net_client_read_line(NET_CLIENT(client), &reply_buf, error);
+ if (result) {
+ if (strncmp(reply_buf, "+ ", 2U) == 0) {
+ if (challenge != NULL) {
+ *challenge = g_strdup(&reply_buf[2]);
+ }
+ } else {
+ result = FALSE;
+ g_set_error(error, NET_CLIENT_POP_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_POP_SERVER_ERR, _("error: %s"), reply_buf);
+ }
+ g_free(reply_buf);
+ }
+ }
+
+ return result;
+}
+
+
+static void
+net_client_pop_get_capa(NetClientPop *client, guint *auth_supported)
+{
+ gboolean result;
+ gboolean done;
+
+ /* clear all capability flags except APOP and send the CAPA command */
+ *auth_supported = *auth_supported & NET_CLIENT_POP_AUTH_APOP;
+ client->priv->can_pipelining = FALSE;
+ result = net_client_pop_execute(client, "CAPA", NULL, NULL);
+
+ /* evaluate the response */
+ done = FALSE;
+ while (result && !done) {
+ gchar *reply;
+
+ result = net_client_read_line(NET_CLIENT(client), &reply, NULL);
+ if (result) {
+ if (IS_ML_TERM(reply)) {
+ done = TRUE;
+ } else if (strcmp(reply, "USER") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_USER_PASS;
+ } else if (strncmp(reply, "SASL ", 5U) == 0) {
+ gchar **auth;
+ guint n;
+
+ auth = g_strsplit(&reply[5], " ", -1);
+ for (n = 0U; auth[n] != NULL; n++) {
+ if (strcmp(auth[n], "PLAIN") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_PLAIN;
+ } else if (strcmp(auth[n], "LOGIN") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_LOGIN;
+ } else if (strcmp(auth[n], "CRAM-MD5") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_CRAM_MD5;
+ } else if (strcmp(auth[n], "CRAM-SHA1") == 0) {
+ *auth_supported |= NET_CLIENT_POP_AUTH_CRAM_SHA1;
+ } else {
+ /* other auth methods are ignored for the time being */
+ }
+ }
+ g_strfreev(auth);
+ } else if (strcmp(reply, "PIPELINING") == 0) {
+ client->priv->can_pipelining = TRUE;
+ } else if (strcmp(reply, "UIDL") == 0) {
+ client->priv->can_uidl = TRUE;
+ } else {
+ /* ignore this capability (see MISRA C:2012, Rule 15.7) */
+ }
+
+ g_free(reply);
+ }
+ }
+
+ /* see RFC 1939, Sect. 4: if no other authentication method is supported explicitly (in particular no
APOP), the server *must*
+ * at least support USER/PASS... */
+ if (*auth_supported == 0U) {
+ *auth_supported = NET_CLIENT_POP_AUTH_USER_PASS;
+ }client->priv->can_pipelining = TRUE;
+}
+
+
+static gboolean
+net_client_pop_uidl(NetClientPop *client, GList * const *msg_list, GError **error)
+{
+ gboolean result;
+ gboolean done;
+ const GList *p;
+
+ result = net_client_pop_execute(client, "UIDL", NULL, error);
+ done = FALSE;
+ p = *msg_list;
+ while (result && !done) {
+ gchar *reply;
+
+ result = net_client_read_line(NET_CLIENT(client), &reply, error);
+ if (result) {
+ if (IS_ML_TERM(reply)) {
+ done = TRUE;
+ } else {
+ guint msg_id;
+ gchar *endptr;
+
+ msg_id = strtoul(reply, &endptr, 10);
+ if (endptr[0] != ' ') {
+ result = FALSE;
+ } else {
+ /* we assume the passed list is already in the proper order, re-scan
it if not */
+ if ((p == NULL) || (POP_MSG_INFO(p)->id != msg_id)) {
+ for (p = *msg_list; (p != NULL) && (POP_MSG_INFO(p)->id !=
msg_id); p = p->next) {
+ /* nothing to do (see MISRA C:2012, Rule 15.7) */
+ }
+ }
+ /* FIXME - error if we get a UID for a message which is not in the
list? */
+ if (p != NULL) {
+ NetClientPopMessageInfo* info = POP_MSG_INFO(p);
+
+ g_free(info->uid);
+ info->uid = g_strdup(&endptr[1]);
+ p = p->next;
+ }
+ }
+ }
+ g_free(reply);
+ }
+ }
+
+ return result;
+}
+
+
+static gboolean
+net_client_pop_retr_msg(NetClientPop *client, const NetClientPopMessageInfo *info, NetClientPopMsgCb
callback, gpointer user_data,
+ GError **error)
+{
+ gboolean result;
+ gboolean done;
+ GString *msg_buf;
+ gsize lines;
+
+ result = TRUE;
+ done = FALSE;
+ msg_buf = g_string_sized_new(POP_DATA_BUF_SIZE);
+ lines = 0U;
+ while (!done && result) {
+ gchar *linebuf;
+
+ result = net_client_read_line(NET_CLIENT(client), &linebuf, error);
+ if (result) {
+ if (IS_ML_TERM(linebuf)) {
+ done = TRUE;
+ } else {
+ if (linebuf[0] == '.') {
+ msg_buf = g_string_append(msg_buf, &linebuf[1]);
+ } else {
+ msg_buf = g_string_append(msg_buf, linebuf);
+ }
+ msg_buf = g_string_append_c(msg_buf, '\n');
+ lines++;
+
+ /* pass an almost full buffer to the callback */
+ if (msg_buf->len > (POP_DATA_BUF_SIZE - 100U)) {
+ result = callback(msg_buf->str, (gssize) msg_buf->len, lines, info,
user_data, error);
+ msg_buf = g_string_truncate(msg_buf, 0U);
+ lines = 0U;
+ }
+ }
+ g_free(linebuf);
+ }
+ }
+
+ if (result) {
+ if (msg_buf->len > 0U) {
+ result = callback(msg_buf->str, (gssize) msg_buf->len, lines, info, user_data, error);
+ }
+ if (result) {
+ result = callback(NULL, 0, 0U, info, user_data, error);
+ }
+ }
+
+ if (!result) {
+ (void) callback(NULL, -1, 0U, info, user_data, NULL);
+ }
+ (void) g_string_free(msg_buf, TRUE);
+
+ return result;
+
+}
diff --git a/libnetclient/net-client-pop.h b/libnetclient/net-client-pop.h
new file mode 100644
index 0000000..63ff340
--- /dev/null
+++ b/libnetclient/net-client-pop.h
@@ -0,0 +1,262 @@
+/* NetClient - simple line-based network client library
+ *
+ * Copyright (C) Albrecht Dreß <mailto:albrecht dress arcor de> 2017
+ *
+ * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License
+ * as published by the Free Software Foundation; either version 3 of the License, or (at your option) any
later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with this library. If not,
see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NET_CLIENT_POP_H_
+#define NET_CLIENT_POP_H_
+
+
+#include "net-client.h"
+
+
+G_BEGIN_DECLS
+
+
+#define NET_CLIENT_POP_TYPE (net_client_pop_get_type())
+#define NET_CLIENT_POP(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),
NET_CLIENT_POP_TYPE, NetClientPop))
+#define NET_IS_CLIENT_POP(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),
NET_CLIENT_POP_TYPE))
+#define NET_CLIENT_POP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),
NET_CLIENT_POP_TYPE, NetClientPopClass))
+#define NET_IS_CLIENT_POP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NET_CLIENT_POP_TYPE))
+#define NET_CLIENT_POP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NET_CLIENT_POP_TYPE,
NetClientPopClass))
+
+#define NET_CLIENT_POP_ERROR_QUARK (g_quark_from_static_string("net-client-pop"))
+
+
+typedef struct _NetClientPop NetClientPop;
+typedef struct _NetClientPopClass NetClientPopClass;
+typedef struct _NetClientPopPrivate NetClientPopPrivate;
+typedef struct _NetClientPopMessage NetClientPopMessage;
+typedef struct _NetClientPopMessageInfo NetClientPopMessageInfo;
+
+
+/** @brief POP-specific error codes */
+enum _NetClientPopError {
+ NET_CLIENT_ERROR_POP_PROTOCOL = 1, /**< A bad server reply has been received. */
+ NET_CLIENT_ERROR_POP_SERVER_ERR, /**< The server replied with an error. */
+ NET_CLIENT_ERROR_POP_NO_AUTH, /**< The server offers no suitable authentication
mechanism. */
+ NET_CLIENT_ERROR_POP_NO_STARTTLS /**< The server does not support STARTTLS. */
+};
+
+
+/** @name POP authentication methods
+ *
+ * Note that the availability of these authentication methods depends upon the result of the CAPABILITY
list. According to RFC
+ * 1939, Section 4, at least either APOP or USER/PASS @em must be supported.
+ * @{
+ */
+/** RFC 1939 "USER" and "PASS" authentication method. */
+#define NET_CLIENT_POP_AUTH_USER_PASS 0x01U
+/** RFC 1939 "APOP" authentication method. */
+#define NET_CLIENT_POP_AUTH_APOP 0x02U
+/** RFC 5034 SASL "LOGIN" authentication method. */
+#define NET_CLIENT_POP_AUTH_LOGIN 0x04U
+/** RFC 5034 SASL "PLAIN" authentication method. */
+#define NET_CLIENT_POP_AUTH_PLAIN 0x08U
+/** RFC 5034 SASL "CRAM-MD5" authentication method. */
+#define NET_CLIENT_POP_AUTH_CRAM_MD5 0x10U
+/** RFC 5034 SASL "CRAM-SHA1" authentication method. */
+#define NET_CLIENT_POP_AUTH_CRAM_SHA1 0x20U
+/** 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)
+/** 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)
+/** @} */
+
+
+struct _NetClientPop {
+ NetClient parent;
+ NetClientPopPrivate *priv;
+};
+
+
+struct _NetClientPopClass {
+ NetClientClass parent;
+};
+
+
+/** @brief Message information
+ *
+ * This structure is returned in a GList by net_client_pop_list() and contains information about on message
in the remote mailbox.
+ */
+struct _NetClientPopMessageInfo {
+ guint id; /**< Message ID in the remote mailbox. */
+ gsize size; /**< Size of the message in bytes. */
+ gchar *uid; /**< Message UID, or NULL if it was not requested or
the remote server does not support the UIDL
+ * command. */
+};
+
+
+/** @brief POP3 Message Read Callback Function
+ *
+ * The user-provided callback function to receive a message from the remote POP3 server:
+ * - @em buffer - the next NUL-terminated chunk of data, always guaranteed to consist of complete, LF
terminated lines, or NULL
+ * when the @em count is <= 0 (see below)
+ * - @em count - indicates the number of bytes in the buffer (> 0), the end of the message (== 0), or that
the download is
+ * terminated due to an error condition (-1)
+ * - @em lines - number of lines in the buffer, valid only if @em count > 0
+ * - @em info - information for the current message
+ * - @em user_data - user data pointer
+ * - @em error - shall be filled with error information if an error occurs in the callback; this location is
actually the @em error
+ * parameter passed to net_client_pop_retr() with the exception of a call when the @em count is -1 when
this parameter is always
+ * NULL
+ * - return value: TRUE if the message download shall proceed, or FALSE to terminate it because an error
occurred in the callback.
+ * In the latter case, the callback function should set @em error appropriately.
+ *
+ * The message retrieved from the remote POP3 server is passed as "raw" data. The line endings are always
LF (i.e. @em not CRLF),
+ * and byte-stuffed termination '.' characters have been unstuffed. If the data passed to the callback
function shall be fed into
+ * <a href="http://spruce.sourceforge.net/gmime/">GMime</a>, it is thus @em not necessary to run it through
a GMimeFilterCRLF
+ * filter.
+ *
+ * The download of every message is terminated by calling the callback with a @em count of 0. If the
callback returns FALSE for a
+ * count >= 0, it is called again for the same message with count == -1 before the download is terminated.
The return value of the
+ * callback called with count == -1 is ignored.
+ */
+typedef gboolean (*NetClientPopMsgCb)(const gchar *buffer, gssize count, gsize lines, const
NetClientPopMessageInfo *info,
+ gpointer user_data, GError **error);
+
+
+GType net_client_pop_get_type(void)
+ G_GNUC_CONST;
+
+
+/** @brief Create a new POP network client
+ *
+ * @param host host name or IP address to connect
+ * @param port port number to connect
+ * @param crypt_mode encryption mode
+ * @param use_pipelining whether POP3 PIPELINING shall be used if supported by the remote server
+ * @return the POP network client object
+ */
+NetClientPop *net_client_pop_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mode, gboolean
use_pipelining);
+
+
+/** @brief Set allowed POP AUTH methods
+ *
+ * @param client POP network client object
+ * @param encrypted set allowed methods for encrypted or unencrypted connections
+ * @param allow_auth mask of allowed authentication methods
+ * @return TRUE on success or FALSE on error
+ *
+ * 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.
+ *
+ * @note Call this function @em before calling net_client_pop_connect().
+ */
+gboolean net_client_pop_allow_auth(NetClientPop *client, gboolean encrypted, guint allow_auth);
+
+
+/** @brief Connect a POP network client
+ *
+ * @param client POP network client object
+ * @param greeting filled with the greeting of the POP server on success, may be NULL to ignore
+ * @param error filled with error information if the connection fails
+ * @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.
+ *
+ * @note The caller must free the returned greeting when it is not needed any more.
+ */
+gboolean net_client_pop_connect(NetClientPop *client, gchar **greeting, GError **error);
+
+
+/** @brief Get the status of a POP3 mailbox
+ *
+ * @param client POP network client object
+ * @param msg_count filled with the number of messages available in the mailbox, may be NULL to ignore the
value
+ * @param mbox_size filled with the total mailbox size in bytes, may be NULL to ignore the value
+ * @param error filled with error information if the connection fails
+ * @return TRUE on success or FALSE if the command failed
+ *
+ * Run the POP3 STAT command to retrieve the mailbox status.
+ */
+gboolean net_client_pop_stat(NetClientPop *client, gsize *msg_count, gsize *mbox_size, GError **error);
+
+
+/** @brief List the messages in the POP3 mailbox
+ *
+ * @param client POP network client object
+ * @param msg_list filled with a list of @ref NetClientPopMessageInfo items
+ * @param with_uid TRUE to include the UID's of the messages in the returned list
+ * @param error filled with error information if the connection fails
+ * @return TRUE on success or FALSE if the command failed
+ *
+ * Run the LIST command and fill the passed list with the message identifier and message size for all
messages available in the
+ * mailbox. If the parameter @em with_uid is TRUE, also run the UIDL command and include the UID's reported
by the remote server in
+ * the returned list.
+ *
+ * The caller shall free the items in the returned list by calling net_client_pop_msg_info_free() on them.
+ *
+ * @note The UID's can be added only if the remote server reports in its @em CAPABILITY list that the @em
UIDL command is supported.
+ */
+gboolean net_client_pop_list(NetClientPop *client, GList **msg_list, gboolean with_uid, GError **error);
+
+
+/** @brief Load messages from the POP3 mailbox
+ *
+ * @param client POP network client object
+ * @param msg_list list of @ref NetClientPopMessageInfo items which shall be read from the server
+ * @param callback callback function which shall be called to process the downloaded message data
+ * @param user_data user data pointer passed to the callback function
+ * @param error filled with error information if the connection fails
+ * @return TRUE on success or FALSE if the command failed
+ *
+ * Load all messages in the passed list from the remote server, passing them through the specified callback
function. The function
+ * takes advantage of the RFC 2449 @em PIPELINING capability if supported by the remote server.
+ */
+gboolean net_client_pop_retr(NetClientPop *client, GList *msg_list, NetClientPopMsgCb callback, gpointer
user_data, GError **error);
+
+
+/** @brief Delete messages from the POP3 mailbox
+ *
+ * @param client POP network client object
+ * @param msg_list list of @ref NetClientPopMessageInfo items which shall be deleted from the server
+ * @param error filled with error information if the connection fails
+ * @return TRUE on success or FALSE if the command failed
+ *
+ * Delete all messages in the passed list from the remote server. The function takes advantage of the RFC
2449 @em PIPELINING
+ * capability if supported by the remote server.
+ */
+gboolean net_client_pop_dele(NetClientPop *client, GList *msg_list, GError **error);
+
+
+/** @brief Free POP3 message item information
+ *
+ * @param info POP3 message item information as returned by net_client_pop_list()
+ *
+ * Free the data of a POP3 message item information.
+ */
+void net_client_pop_msg_info_free(NetClientPopMessageInfo *info);
+
+
+/** @file
+ *
+ * This module implements a POP3 client class conforming with <a
href="https://tools.ietf.org/html/rfc1939">RFC 1939</a>.
+ *
+ * The following features are supported:
+ * - the <i>STAT</i>, <i>LIST</i>, <i>RETR</i> and <i>DELE</i> commands as defined in RFC 1939;
+ * - support for <i>PIPELINING</i> and <i>UIDL</i> as defined by <a
href="https://tools.ietf.org/html/rfc2449">RFC 2449</a>;
+ * - <i>STLS</i> encryption as defined by <a href="https://tools.ietf.org/html/rfc2595">RFC 2595</a>;
+ * - authentication using <i>APOP</i>, <i>USER/PASS</i> (both RFC 1939) or the SASL methods <i>PLAIN</i>,
<i>LOGIN</i>,
+ * <i>CRAM-MD5</i> 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.
+ */
+
+
+G_END_DECLS
+
+
+#endif /* NET_CLIENT_POP_H_ */
diff --git a/libnetclient/net-client-smtp.c b/libnetclient/net-client-smtp.c
index 74c7858..b55710c 100644
--- a/libnetclient/net-client-smtp.c
+++ b/libnetclient/net-client-smtp.c
@@ -21,9 +21,7 @@
struct _NetClientSmtpPrivate {
NetClientCryptMode crypt_mode;
guint auth_allowed[2]; /** 0: encrypted, 1: unencrypted */
- guint auth_supported;
gboolean can_dsn;
- gboolean can_starttls;
};
@@ -52,11 +50,12 @@ G_DEFINE_TYPE(NetClientSmtp, net_client_smtp, NET_CLIENT_TYPE)
static void net_client_smtp_finalise(GObject *object);
-static gboolean net_client_smtp_ehlo(NetClientSmtp *client, GError **error);
+static gboolean net_client_smtp_ehlo(NetClientSmtp *client, guint *auth_supported, gboolean *can_starttls,
GError **error);
static gboolean net_client_smtp_starttls(NetClientSmtp *client, GError **error);
static gboolean net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gchar **last_reply,
GError **error, ...)
G_GNUC_PRINTF(2, 5);
-static gboolean net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *passwd, GError
**error);
+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_cram(NetClientSmtp *client, GChecksumType chksum_type, const gchar
*user, const gchar *passwd,
@@ -72,7 +71,8 @@ net_client_smtp_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mo
{
NetClientSmtp *client;
- g_return_val_if_fail(host != NULL, NULL);
+ g_return_val_if_fail((host != NULL) && (crypt_mode >= NET_CLIENT_CRYPT_ENCRYPTED) && (crypt_mode <=
NET_CLIENT_CRYPT_NONE),
+ NULL);
client = NET_CLIENT_SMTP(g_object_new(NET_CLIENT_SMTP_TYPE, NULL));
if (client != NULL) {
@@ -106,6 +106,8 @@ gboolean
net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
{
gboolean result;
+ gboolean can_starttls = FALSE;
+ guint auth_supported = 0U;
/* paranoia checks */
g_return_val_if_fail(NET_IS_CLIENT_SMTP(client), FALSE);
@@ -123,13 +125,13 @@ net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
/* send EHLO and read the capabilities of the server */
if (result) {
- result = net_client_smtp_ehlo(client, error);
+ result = net_client_smtp_ehlo(client, &auth_supported, &can_starttls, error);
}
/* perform STARTTLS if required, and send EHLO again */
if (result &&
((client->priv->crypt_mode == NET_CLIENT_CRYPT_STARTTLS) || (client->priv->crypt_mode ==
NET_CLIENT_CRYPT_STARTTLS_OPT))) {
- if (!client->priv->can_starttls) {
+ if (!can_starttls) {
if (client->priv->crypt_mode == NET_CLIENT_CRYPT_STARTTLS) {
g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_SMTP_NO_STARTTLS,
_("remote server does not support STARTTLS"));
@@ -138,7 +140,7 @@ net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
} else {
result = net_client_smtp_starttls(client, error);
if (result) {
- result = net_client_smtp_ehlo(client, error);
+ result = net_client_smtp_ehlo(client, &auth_supported, &can_starttls, error);
}
}
}
@@ -151,7 +153,7 @@ net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
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)) {
- result = net_client_smtp_auth(client, auth_data[0], auth_data[1], error);
+ 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]));
}
@@ -350,8 +352,6 @@ net_client_smtp_starttls(NetClientSmtp *client, GError **error)
{
gboolean result;
- g_return_val_if_fail(NET_IS_CLIENT_SMTP(client), FALSE);
-
result = net_client_smtp_execute(client, "STARTTLS", NULL, error);
if (result) {
result = net_client_start_tls(NET_CLIENT(client), error);
@@ -362,17 +362,17 @@ net_client_smtp_starttls(NetClientSmtp *client, GError **error)
static gboolean
-net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *passwd, GError **error)
+net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *passwd, guint auth_supported,
GError **error)
{
gboolean result;
guint auth_mask;
- g_return_val_if_fail(NET_IS_CLIENT_SMTP(client), FALSE);
+ g_return_val_if_fail(NET_IS_CLIENT_SMTP(client) && (user != NULL) && (passwd != NULL), FALSE);
if (net_client_is_encrypted(NET_CLIENT(client))) {
- auth_mask = client->priv->auth_allowed[0] & client->priv->auth_supported;
+ auth_mask = client->priv->auth_allowed[0] & auth_supported;
} else {
- auth_mask = client->priv->auth_allowed[1] & client->priv->auth_supported;
+ auth_mask = client->priv->auth_allowed[1] & auth_supported;
}
if ((auth_mask & NET_CLIENT_SMTP_AUTH_CRAM_SHA1) != 0U) {
@@ -418,8 +418,6 @@ net_client_smtp_auth_login(NetClientSmtp *client, const gchar *user, const gchar
gboolean result;
gchar *base64_buf;
- g_return_val_if_fail((user != NULL) && (passwd != NULL), FALSE);
-
base64_buf = g_base64_encode((const guchar *) user, strlen(user));
result = net_client_smtp_execute(client, "AUTH LOGIN %s", NULL, error, base64_buf);
memset(base64_buf, 0, strlen(base64_buf));
@@ -441,8 +439,6 @@ net_client_smtp_auth_cram(NetClientSmtp *client, GChecksumType chksum_type, cons
gboolean result;
gchar *challenge = NULL;
- g_return_val_if_fail((user != NULL) && (passwd != NULL), FALSE);
-
result = net_client_smtp_execute(client, "AUTH CRAM-%s", &challenge, error,
net_client_chksum_to_str(chksum_type));
if (result) {
gchar *auth_buf;
@@ -462,6 +458,7 @@ net_client_smtp_auth_cram(NetClientSmtp *client, GChecksumType chksum_type, cons
}
+/* 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,
...)
{
@@ -481,7 +478,7 @@ net_client_smtp_execute(NetClientSmtp *client, const gchar *request_fmt, gchar *
static gboolean
-net_client_smtp_ehlo(NetClientSmtp *client, GError **error)
+net_client_smtp_ehlo(NetClientSmtp *client, guint *auth_supported, gboolean *can_starttls, GError **error)
{
gboolean result;
gboolean done;
@@ -489,9 +486,9 @@ net_client_smtp_ehlo(NetClientSmtp *client, GError **error)
result = net_client_write_line(NET_CLIENT(client), "EHLO %s", error, g_get_host_name());
/* clear all capability flags */
- client->priv->auth_supported = 0U;
+ *auth_supported = 0U;
client->priv->can_dsn = FALSE;
- client->priv->can_starttls = FALSE;
+ *can_starttls = FALSE;
/* evaluate the response */
done = FALSE;
@@ -512,7 +509,7 @@ net_client_smtp_ehlo(NetClientSmtp *client, GError **error)
if (strcmp(&endptr[1], "DSN") == 0) {
client->priv->can_dsn = TRUE;
} else if (strcmp(&endptr[1], "STARTTLS") == 0) {
- client->priv->can_starttls = TRUE;
+ *can_starttls = TRUE;
} else if ((strncmp(&endptr[1], "AUTH ", 5U) == 0) || (strncmp(&endptr[1],
"AUTH=", 5U) == 0)) {
gchar **auth;
guint n;
@@ -520,20 +517,20 @@ net_client_smtp_ehlo(NetClientSmtp *client, GError **error)
auth = g_strsplit(&endptr[6], " ", -1);
for (n = 0U; auth[n] != NULL; n++) {
if (strcmp(auth[n], "PLAIN") == 0) {
- client->priv->auth_supported |=
NET_CLIENT_SMTP_AUTH_PLAIN;
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_PLAIN;
} else if (strcmp(auth[n], "LOGIN") == 0) {
- client->priv->auth_supported |=
NET_CLIENT_SMTP_AUTH_LOGIN;
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_LOGIN;
} else if (strcmp(auth[n], "CRAM-MD5") == 0) {
- client->priv->auth_supported |=
NET_CLIENT_SMTP_AUTH_CRAM_MD5;
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_CRAM_MD5;
} else if (strcmp(auth[n], "CRAM-SHA1") == 0) {
- client->priv->auth_supported |=
NET_CLIENT_SMTP_AUTH_CRAM_SHA1;
+ *auth_supported |= NET_CLIENT_SMTP_AUTH_CRAM_SHA1;
} else {
/* other auth methods are ignored for the time being
*/
}
}
g_strfreev(auth);
} else {
- /* ignored */
+ /* ignored (see MISRA C:2012, Rule 15.7) */
}
if (*endptr == ' ') {
@@ -549,7 +546,7 @@ net_client_smtp_ehlo(NetClientSmtp *client, GError **error)
}
-/* Note: according to RFC 5321, sect. 4.2, \em any reply may be multiline. */
+/* Note: according to RFC 5321, sect. 4.2, \em any reply may be multiline. If supplied, last_reply is never
NULL on success */
static gboolean
net_client_smtp_read_reply(NetClientSmtp *client, gint expect_code, gchar **last_reply, GError **error)
{
diff --git a/libnetclient/net-client-smtp.h b/libnetclient/net-client-smtp.h
index b098d83..1eb35fc 100644
--- a/libnetclient/net-client-smtp.h
+++ b/libnetclient/net-client-smtp.h
@@ -44,7 +44,7 @@ enum _NetClientSmtpError {
NET_CLIENT_ERROR_SMTP_PROTOCOL = 1, /**< A bad server reply has been received. */
NET_CLIENT_ERROR_SMTP_TRANSIENT, /**< The server replied with a transient error code
(code 4yz). */
NET_CLIENT_ERROR_SMTP_PERMANENT, /**< The server replied with a permanent error code
(code 5yz). */
- NET_CLIENT_ERROR_SMTP_NO_AUTH, /**< The server offers no implemented authentication
mechanism. */
+ NET_CLIENT_ERROR_SMTP_NO_AUTH, /**< The server offers no suitable authentication mechanism.
*/
NET_CLIENT_ERROR_SMTP_NO_STARTTLS /**< The server does not support STARTTLS. */
};
@@ -118,7 +118,7 @@ GType net_client_smtp_get_type(void)
* @param host host name or IP address to connect
* @param port port number to connect
* @param crypt_mode encryption mode
- * @return the net SMTP network client object
+ * @return the SMTP network client object
*/
NetClientSmtp *net_client_smtp_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mode);
diff --git a/libnetclient/net-client.c b/libnetclient/net-client.c
index 1250769..c7b73f7 100644
--- a/libnetclient/net-client.c
+++ b/libnetclient/net-client.c
@@ -51,7 +51,7 @@ net_client_new(const gchar *host_and_port, guint16 default_port, gsize max_line_
{
NetClient *client;
- g_return_val_if_fail((host_and_port != NULL) && (max_line_len > 0U), NULL);
+ g_return_val_if_fail(host_and_port != NULL, NULL);
client = NET_CLIENT(g_object_new(NET_CLIENT_TYPE, NULL));
@@ -73,7 +73,7 @@ net_client_configure(NetClient *client, const gchar *host_and_port, guint16 defa
NetClientPrivate *priv;
gboolean result;
- g_return_val_if_fail(NET_IS_CLIENT(client) && (host_and_port != NULL) && (max_line_len > 0U), FALSE);
+ g_return_val_if_fail(NET_IS_CLIENT(client) && (host_and_port != NULL), FALSE);
priv = client->priv;
if (priv->plain_conn != NULL) {
@@ -118,6 +118,7 @@ net_client_connect(NetClient *client, GError **error)
} else {
priv->plain_conn = g_socket_client_connect_to_host(priv->sock, priv->host_and_port,
priv->default_port, NULL, error);
if (priv->plain_conn != NULL) {
+ g_debug("connected to %s", priv->host_and_port);
priv->istream =
g_data_input_stream_new(g_io_stream_get_input_stream(G_IO_STREAM(priv->plain_conn)));
g_data_input_stream_set_newline_type(priv->istream, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
priv->ostream = g_io_stream_get_output_stream(G_IO_STREAM(priv->plain_conn));
@@ -139,6 +140,7 @@ net_client_is_encrypted(NetClient *client)
} else {
result = FALSE;
}
+
return result;
}
@@ -160,7 +162,7 @@ net_client_read_line(NetClient *client, gchar **recv_line, GError **error)
line_buf = g_data_input_stream_read_line(client->priv->istream, &length, NULL, &read_err);
if (line_buf != NULL) {
/* check that the protocol-specific maximum line length is not exceeded */
- if (length > client->priv->max_line_len) {
+ if ((client->priv->max_line_len > 0U) && (length > client->priv->max_line_len)) {
g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint)
NET_CLIENT_ERROR_LINE_TOO_LONG,
_("reply length %lu exceeds the maximum allowed length %lu"), length,
client->priv->max_line_len);
g_free(line_buf);
@@ -224,7 +226,7 @@ net_client_vwrite_line(NetClient *client, const gchar *format, va_list args, GEr
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) || ((gsize) buf_len > (client->priv->max_line_len - 2U))) {
+ if ((buf_len < 0) || ((client->priv->max_line_len > 0U) && ((gsize) buf_len >
(client->priv->max_line_len - 2U)))) {
g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_LINE_TOO_LONG, _("line too
long"));
result = FALSE;
} else {
@@ -299,7 +301,7 @@ net_client_set_cert_from_pem(NetClient *client, const gchar *pem_data, GError **
} else {
gnutls_datum_t data;
- /*lint -e9005 cast'ing away the const is safe as gnutls treas data as const */
+ /*lint -e9005 cast'ing away the const is safe as gnutls treats data as const */
data.data = (unsigned char *) pem_data;
data.size = strlen(pem_data);
res = gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM);
@@ -501,6 +503,7 @@ net_client_finalise(GObject *object)
g_object_unref(G_OBJECT(client->priv->certificate));
client->priv->certificate = NULL;
}
+ g_debug("finalised connection to %s", client->priv->host_and_port);
g_free(client->priv->host_and_port);
(*parent_class->finalize)(object);
}
diff --git a/libnetclient/net-client.h b/libnetclient/net-client.h
index 41db07d..be89df1 100644
--- a/libnetclient/net-client.h
+++ b/libnetclient/net-client.h
@@ -80,7 +80,7 @@ GType net_client_get_type(void)
*
* @param host_and_port remote host and port or service, separated by a colon, which shall be connected
* @param default_port default remote port if host_and_port does not contain a port
- * @param max_line_len maximum line length supported by the underlying protocol
+ * @param max_line_len maximum line length supported by the underlying protocol, 0 for no limit
* @return the net network client object
*
* Create a new network client object with the passed parameters. Call <tt>g_object_unref()</tt> on it to
shut down the connection
@@ -94,7 +94,7 @@ NetClient *net_client_new(const gchar *host_and_port, guint16 default_port, gsiz
* @param client network client
* @param host_and_port remote host and port or service, separated by a colon, which shall be connected
* @param default_port default remote port if host_and_port does not contain a port
- * @param max_line_len maximum line length supported by the underlying protocol
+ * @param max_line_len maximum line length supported by the underlying protocol, 0 for no limit
* @param error filled with error information on error
* @return TRUE is the connection was successful, FALSE on error
*
@@ -184,7 +184,8 @@ gboolean net_client_start_tls(NetClient *client, GError **error);
*
* Read a CRLF-terminated line from the remote server and return it in the passed buffer. The terminating
CRLF is always stripped.
*
- * @note The caller must free the returned buffer when it is not needed any more.
+ * @note If supplied, the response buffer is never NULL on success. The caller must free the returned
buffer when it is not needed
+ * any more.
*/
gboolean net_client_read_line(NetClient *client, gchar **recv_line, GError **error);
@@ -263,8 +264,9 @@ gboolean net_client_set_timeout(NetClient *client, guint timeout_secs);
* @mainpage
*
* This library provides an implementation of CRLF-terminated line-based client protocols built on top of
GIO. It provides a base
- * module (see file net-client.h), containing the line-based IO methods, and on top of that a SMTP (RFC5321)
client class (see
- * file net-client-smtp.h). The file net-client-utils.h contains some helper functions for authentication.
+ * module (see file net-client.h), containing the line-based IO methods, and on top of that SMTP (RFC 5321)
and POP3 (RFC 1939)
+ * client classes (see files net-client-smtp.h and net-client-pop.h, respectively). The file
net-client-utils.h contains some
+ * helper functions for authentication.
*
* \author Written by Albrecht Dreß mailto:albrecht dress arcor de
* \copyright Copyright © Albrecht Dreß 2017<br/>
diff --git a/libnetclient/test/Makefile.am b/libnetclient/test/Makefile.am
index be9caf7..d735dc5 100644
--- a/libnetclient/test/Makefile.am
+++ b/libnetclient/test/Makefile.am
@@ -2,7 +2,7 @@
# Note: the following hack is needed so lcov recognises the paths of the sources...
libsrcdir = $(shell echo $(abs_srcdir) | sed -e 's;/test$$;;')
-test_src = $(libsrcdir)/net-client.c $(libsrcdir)/net-client-smtp.c $(libsrcdir)/net-client-utils.c
+test_src = $(libsrcdir)/net-client.c $(libsrcdir)/net-client-pop.c $(libsrcdir)/net-client-smtp.c
$(libsrcdir)/net-client-utils.c
EXTRA_DIST = \
tests.c \
@@ -16,7 +16,8 @@ TESTFLAGS = -DNCAT="\"@NCAT@\"" -DSED="\"@SED@\"" -fprofile-arcs -ftest-coverage
LCOVFLGS = --rc lcov_branch_coverage=1
GENHTMLFLGS = --function-coverage --branch-coverage --num-spaces 4
-VALGRFLAGS = --tool=memcheck --log-file=$@.vg --suppressions=valgrind.supp --leak-check=full
--child-silent-after-fork=yes
+VALGRFLAGS = --tool=memcheck --log-file=$@.vg --suppressions=valgrind.supp --leak-check=full
--track-fds=yes \
+ --child-silent-after-fork=yes
CLEANFILES = *.gcda *.gcno *.covi *.vg tests
diff --git a/libnetclient/test/inetsim-1.2.6-POP3.diff b/libnetclient/test/inetsim-1.2.6-POP3.diff
new file mode 100644
index 0000000..6271880
--- /dev/null
+++ b/libnetclient/test/inetsim-1.2.6-POP3.diff
@@ -0,0 +1,45 @@
+--- inetsim-1.2.6-orig/lib/INetSim/POP3.pm 2016-08-29 09:43:28.000000000 +0200
++++ inetsim-1.2.6/lib/INetSim/POP3.pm 2017-03-20 18:47:24.082678213 +0100
+@@ -32,7 +32,7 @@
+ "SASL" => 2, # RFC 2449, 1734, 5034, 2195 ...
(http://www.iana.org/assignments/sasl-mechanisms)
+ "RESP-CODES" => 1, # RFC 2449
+ "LOGIN-DELAY" => 2, # RFC 2449
+- "PIPELINING" => 0, # RFC 2449
++ "PIPELINING" => 1, # RFC 2449
+ "EXPIRE" => 2, # RFC 2449
+ "UIDL" => 1, # RFC 1939, 2449
+ "IMPLEMENTATION" => 2, # RFC 2449
+@@ -292,6 +292,11 @@
+ $line =~ s/[\r\n\s\t]+$//g;
+ alarm($self->{timeout});
+ $self->slog_("recv: $line");
++ ### flush input buffer if pipelining is disabled
++ if (!defined $POP3_CAPA{PIPELINING}) {
++
++ ### FIXME - flush any pending input here
++ }
+ ### Auth via USER/PASS
+ if ($line =~ /^USER(|([\s]+)(.*))$/i && defined $POP3_CAPA{USER}) {
+ $self->USER($3);
+@@ -1038,9 +1043,11 @@
+ my ($flag, $hash, $uid, $size, $header, $body) = $self->read_mail($args);
+ if (defined $flag && $flag) {
+ $self->send_("+OK", "Message follows ($size octets)");
++ # quote termination octet (RFC 1939, Sect. 3)
++ $body =~ s/\r\n\./\r\n../g;
+ print $client "$header\r\n$body";
+ $self->slog_("send: <(MESSAGE)>");
+- print $client "\r\n.\r\n";
++ print $client ".\r\n";
+ $self->slog_("send: .");
+ $status{retrieved}++;
+ }
+@@ -1300,8 +1307,6 @@
+ # convert LF to CR/LF
+ $msg =~ s/\r\n/\n/g;
+ $msg =~ s/\n/\r\n/g;
+- # quote 'CR+LF+.+CR+LF'
+- $msg =~ s/\r\n\.\r\n/\r\n\.\.\r\n/g;
+ # split header & body
+ $msg =~ s/(\r\n){2,}/\|/;
+ ($header, $body) = split(/\|/, $msg, 2);
diff --git a/libnetclient/test/inetsim.conf b/libnetclient/test/inetsim.conf
index d3ab91b..75e5fa0 100644
--- a/libnetclient/test/inetsim.conf
+++ b/libnetclient/test/inetsim.conf
@@ -888,7 +888,7 @@ smtps_auth_required yes
#
# Default: 110
#
-#pop3_bind_port 110
+pop3_bind_port 64110
#########################################
@@ -1021,6 +1021,7 @@ pop3_capability SASL PLAIN LOGIN ANONYMOUS CRAM-MD5 CRAM-SHA1
pop3_capability UIDL
pop3_capability IMPLEMENTATION "INetSim POP3 server"
pop3_capability STLS
+pop3_capability PIPELINING
#
@@ -1090,7 +1091,7 @@ pop3_capability STLS
#
# Default: 995
#
-#pop3s_bind_port 995
+pop3s_bind_port 64995
#########################################
@@ -1165,7 +1166,7 @@ pop3_capability STLS
#
# Default: yes
#
-#pop3s_enable_apop no
+pop3s_enable_apop no
#########################################
diff --git a/libnetclient/test/tests.c b/libnetclient/test/tests.c
index d636a0b..053d48c 100644
--- a/libnetclient/test/tests.c
+++ b/libnetclient/test/tests.c
@@ -11,12 +11,14 @@
#include <sput.h>
#include "net-client.h"
#include "net-client-smtp.h"
+#include "net-client-pop.h"
#include "net-client-utils.h"
static void test_basic(void);
static void test_basic_crypt(void);
static void test_smtp(void);
+static void test_pop3(void);
static void test_utils(void);
@@ -34,8 +36,8 @@ main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
sput_enter_suite("test SMTP");
sput_run_test(test_smtp);
- //sput_enter_suite("test POP3");
- //sput_run_test(test_pop3);
+ sput_enter_suite("test POP3");
+ sput_run_test(test_pop3);
sput_enter_suite("test utility functions");
sput_run_test(test_utils);
@@ -57,7 +59,6 @@ test_basic(void)
gchar *read_res;
sput_fail_unless(net_client_new(NULL, 65000, 42) == NULL, "missing host");
- sput_fail_unless(net_client_new("localhost", 65000, 0) == NULL, "zero max line length");
sput_fail_unless((basic = net_client_new("localhost", 65000, 42)) != NULL, "localhost; port 65000");
sput_fail_unless(net_client_get_host(NULL) == NULL, "get host w/o client");
@@ -68,7 +69,6 @@ test_basic(void)
sput_fail_unless((basic = net_client_new("www.google.com", 80, 1)) != NULL, "www.google.com:80; port
0");
sput_fail_unless(net_client_configure(NULL, "localhost", 65000, 42, NULL) == FALSE, "configure w/o
client");
sput_fail_unless(net_client_configure(basic, NULL, 65000, 42, NULL) == FALSE, "configure w/o host");
- sput_fail_unless(net_client_configure(basic, "localhost", 65000, 0, NULL) == FALSE, "configure w/
zero max line length");
sput_fail_unless(net_client_configure(basic, "localhost", 65000, 42, NULL) == TRUE, "configure
localhost:65000 ok");
sput_fail_unless(net_client_set_timeout(NULL, 3) == FALSE, "set timeout w/o client");
@@ -78,7 +78,7 @@ test_basic(void)
sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_NOT_CONNECTED), "write w/o
connection");
g_clear_error(&error);
op_res = net_client_read_line(basic, NULL, &error);
- sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_NOT_CONNECTED), "read w/o
connection");
+ sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_NOT_CONNECTED), "read line w/o
connection");
g_clear_error(&error);
op_res =
@@ -108,11 +108,10 @@ test_basic(void)
g_clear_error(&error);
sput_fail_unless(net_client_write_line(basic, "%s", NULL, "x") == TRUE, "write ok");
- sput_fail_unless(net_client_read_line(NULL, NULL, NULL) == FALSE, "read w/o client");
- sput_fail_unless(net_client_read_line(basic, NULL, NULL) == TRUE, "read, data discarded");
+ sput_fail_unless(net_client_read_line(NULL, NULL, NULL) == FALSE, "read line w/o client");
+ sput_fail_unless(net_client_read_line(basic, NULL, NULL) == TRUE, "read line, data discarded");
op_res = net_client_read_line(basic, NULL, &error);
- sput_fail_unless((op_res == FALSE) && (error->code == G_IO_ERROR_TIMED_OUT), "read timeout");
- g_message("%s %d %s", g_quark_to_string(error->domain), error->code, error->message);
+ sput_fail_unless((op_res == FALSE) && (error->code == G_IO_ERROR_TIMED_OUT), "read line timeout");
g_clear_error(&error);
sput_fail_unless(net_client_execute(NULL, NULL, "Hi There", NULL) == FALSE, "execute w/o client");
@@ -285,7 +284,9 @@ get_auth(NetClient *client, gpointer user_data)
g_message("%s(%p, %p)", __func__, client, user_data);
result = g_new0(gchar *, 3U);
result[0] = g_strdup("john.doe");
- result[1] = g_strdup("@ C0mplex P@sswd");
+ if (user_data != NULL) {
+ result[1] = g_strdup("@ C0mplex P@sswd");
+ }
return result;
}
@@ -318,7 +319,9 @@ test_smtp(void)
// smtp stuff - test various failures
- sput_fail_unless(net_client_smtp_new(NULL, 0, NET_CLIENT_CRYPT_NONE) == NULL, "missing host");
+ sput_fail_unless(net_client_smtp_new(NULL, 0, NET_CLIENT_CRYPT_NONE) == NULL, "new, missing host");
+ sput_fail_unless(net_client_smtp_new("localhost", 0, 0) == NULL, "new, bad crypt mode");
+ sput_fail_unless(net_client_smtp_new("localhost", 0, 42) == NULL, "new, bad crypt mode");
sput_fail_unless((smtp = net_client_smtp_new("localhost", 65000, NET_CLIENT_CRYPT_NONE)) != NULL,
"localhost; port 65000");
sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL) == FALSE, "no server");
@@ -364,6 +367,12 @@ test_smtp(void)
g_clear_error(&error);
g_object_unref(smtp);
+ // 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)");
+ g_object_unref(smtp);
+
// unencrypted, PLAIN auth
sput_fail_unless((smtp = net_client_smtp_new("localhost", 65025, NET_CLIENT_CRYPT_NONE)) != NULL,
"localhost:65025");
sput_fail_unless(net_client_smtp_allow_auth(NULL, FALSE, NET_CLIENT_SMTP_AUTH_PLAIN) == FALSE, "set
auth meths, no client");
@@ -426,6 +435,226 @@ test_smtp(void)
net_client_smtp_msg_free(msg);
}
+static gboolean
+msg_cb(const gchar *buffer, gssize count, gsize lines, const NetClientPopMessageInfo *info, gpointer
user_data, GError **error)
+{
+ g_message("%s(%p, %ld, %lu, %p, %p, %p)", __func__, buffer, count, lines, info, user_data, error);
+ if (((GPOINTER_TO_INT(user_data) == 1) && (count > 0)) ||
+ ((GPOINTER_TO_INT(user_data) == 2) && (count == 0))) {
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+static void
+test_pop3(void)
+{
+ NetClientPop *pop;
+ GError *error = NULL;
+ gboolean op_res;
+ gchar *read_res;
+ gsize msg_count;
+ gsize mbox_size;
+ GList *msg_list;
+
+ // some error cases
+ sput_fail_unless(net_client_pop_new(NULL, 0, NET_CLIENT_CRYPT_NONE, TRUE) == NULL, "new, missing
host");
+ sput_fail_unless(net_client_pop_new("localhost", 0, 0, TRUE) == NULL, "new, bad crypt mode");
+ sput_fail_unless(net_client_pop_new("localhost", 0, 42, TRUE) == NULL, "new, bad crypt mode");
+ sput_fail_unless(net_client_pop_allow_auth(NULL, TRUE, 0U) == FALSE, "allow auth, no client");
+ sput_fail_unless(net_client_pop_connect(NULL, NULL, NULL) == FALSE, "connect, no client");
+ sput_fail_unless(net_client_pop_stat(NULL, NULL, NULL, NULL) == FALSE, "stat, no client");
+ sput_fail_unless(net_client_pop_list(NULL, NULL, FALSE, NULL) == FALSE, "list, no client");
+ sput_fail_unless(net_client_pop_retr(NULL, NULL, NULL, NULL, NULL) == FALSE, "retr, no client");
+ sput_fail_unless(net_client_pop_dele(NULL, NULL, NULL) == FALSE, "dele, no client");
+ net_client_pop_msg_info_free(NULL); // just for checking
+
+ // some basic stuff
+ sput_fail_unless((pop = net_client_pop_new("localhost", 65000, NET_CLIENT_CRYPT_NONE, TRUE)) != NULL,
"localhost; port 65000");
+ sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == FALSE, "no server");
+ g_object_unref(pop);
+
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_NONE, TRUE)) != NULL,
"localhost:64110");
+ op_res = net_client_pop_connect(pop, &read_res, NULL);
+ sput_fail_unless((op_res == TRUE) && (strncmp(read_res, "INetSim POP3 Server ready <", 27U) == 0),
+ "connect: success");
+ g_free(read_res);
+ sput_fail_unless(net_client_is_encrypted(NET_CLIENT(pop)) == FALSE, "not encrypted");
+ op_res = net_client_pop_connect(pop, NULL, &error);
+ sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_CONNECTED), "cannot
reconnect");
+ g_clear_error(&error);
+
+ // not allowed if unauthenticated
+ 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);
+ 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);
+ g_object_unref(pop);
+
+ // unencrypted, force USER auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_NONE, TRUE)) != NULL,
"localhost:64110");
+ sput_fail_unless(net_client_pop_allow_auth(pop, FALSE, 0U), "no AUTH mechanism");
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ op_res = net_client_pop_connect(pop, NULL, &error);
+ sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_POP_NO_AUTH), "no suitable
AUTH mechanism");
+ g_clear_error(&error);
+ g_object_unref(pop);
+
+ // STARTTLS
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_STARTTLS, TRUE)) !=
NULL,
+ "localhost:64110, starttls");
+ op_res = net_client_pop_connect(pop, NULL, &error);
+ sput_fail_unless((op_res == FALSE) && (error != NULL), "connect: fails (untrusted cert)");
+ g_clear_error(&error);
+ g_object_unref(pop);
+
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_STARTTLS_OPT, TRUE))
!= NULL,
+ "localhost:64110, starttls (opt)");
+ op_res = net_client_pop_connect(pop, NULL, &error);
+ sput_fail_unless((op_res == TRUE) && (error == NULL), "connect: ok, but...");
+ sput_fail_unless(net_client_is_encrypted(NET_CLIENT(pop)) == FALSE, "...not encrypted");
+ g_object_unref(pop);
+
+ // STARTTLS required, USER/PASS auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_STARTTLS, FALSE)) !=
NULL,
+ "localhost:64110, starttls, no pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_USER_PASS) == TRUE, "force
auth meth USER/PASS");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_stat(pop, &msg_count, NULL, NULL) == TRUE, "STAT: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, FALSE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, NULL, msg_cb, NULL, NULL) == FALSE, "retr w/o
message list");
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, NULL, NULL, NULL) == FALSE, "retr w/o
callback");
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, GINT_TO_POINTER(1), NULL) ==
FALSE, "retr error");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ g_object_unref(pop);
+
+ // STARTTLS optional, APOP auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_STARTTLS_OPT, TRUE))
!= NULL,
+ "localhost:64110, starttls opt, pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_APOP) == TRUE, "force auth
meth APOP");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_stat(pop, NULL, &mbox_size, NULL) == TRUE, "STAT: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, FALSE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, GINT_TO_POINTER(2), NULL) ==
FALSE, "retr error");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ g_object_unref(pop);
+
+ // STARTTLS required, PLAIN auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_STARTTLS, TRUE)) !=
NULL,
+ "localhost:64110, starttls, pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_PLAIN) == TRUE, "force auth
meth PLAIN");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, FALSE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, NULL, NULL) == TRUE, "retr ok");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ g_object_unref(pop);
+
+ // STARTTLS optional, PLAIN auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64110, NET_CLIENT_CRYPT_STARTTLS_OPT, FALSE))
!= NULL,
+ "localhost:64110, starttls opt, no pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_PLAIN) == TRUE, "force auth
meth PLAIN");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, FALSE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, NULL, NULL) == TRUE, "retr ok");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ g_object_unref(pop);
+
+ // SSL, LOGIN auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64995, NET_CLIENT_CRYPT_ENCRYPTED, FALSE)) !=
NULL,
+ "localhost:64995, pop3s, no pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_LOGIN) == TRUE, "force auth
meth LOGIN");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, &error) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_stat(pop, &msg_count, &mbox_size, NULL) == TRUE, "STAT: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, FALSE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, GINT_TO_POINTER(1), NULL) ==
FALSE, "retr error");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ g_object_unref(pop);
+
+ // SSL, CRAM-MD5 auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64995, NET_CLIENT_CRYPT_ENCRYPTED, TRUE)) !=
NULL,
+ "localhost:64995, pop3s, pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_CRAM_MD5) == TRUE, "force
auth meth CRAM-MD5");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, NULL) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, TRUE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, GINT_TO_POINTER(2), NULL) ==
FALSE, "retr error");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ g_object_unref(pop);
+
+ // SSL, CRAM-SHA1 auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64995, NET_CLIENT_CRYPT_ENCRYPTED, FALSE)) !=
NULL,
+ "localhost:64995, pop3s, no pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_CRAM_SHA1) == TRUE, "force
auth meth CRAM-SHA1");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, &error) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, TRUE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, NULL, NULL) == TRUE, "retr ok");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ g_object_unref(pop);
+
+ // SSL, CRAM-SHA1 auth
+ sput_fail_unless((pop = net_client_pop_new("localhost", 64995, NET_CLIENT_CRYPT_ENCRYPTED, TRUE)) !=
NULL,
+ "localhost:64995, pop3s, pipelining");
+ sput_fail_unless(net_client_pop_allow_auth(pop, TRUE, NET_CLIENT_POP_AUTH_CRAM_SHA1) == TRUE, "force
auth meth CRAM-SHA1");
+ g_signal_connect(G_OBJECT(pop), "cert-check", G_CALLBACK(check_cert), NULL);
+ g_signal_connect(G_OBJECT(pop), "auth", G_CALLBACK(get_auth), pop);
+ sput_fail_unless(net_client_pop_connect(pop, NULL, &error) == TRUE, "connect: success");
+ sput_fail_unless(net_client_pop_list(pop, &msg_list, TRUE, NULL) == TRUE, "LIST: success");
+ g_message("message count: %u", g_list_length(msg_list));
+ if (msg_list != NULL) {
+ sput_fail_unless(net_client_pop_retr(pop, msg_list, msg_cb, NULL, NULL) == TRUE, "retr ok");
+ sput_fail_unless(net_client_pop_dele(pop, NULL,NULL) == FALSE, "dele w/o message list");
+ sput_fail_unless(net_client_pop_dele(pop, msg_list, NULL) == TRUE, "dele ok");
+ g_list_free_full(msg_list, (GDestroyNotify) net_client_pop_msg_info_free);
+ }
+ op_res = net_client_pop_list(pop, &msg_list, TRUE, NULL);
+ sput_fail_unless((op_res == TRUE) && (msg_list == NULL), "LIST: success, empty");
+ g_object_unref(pop);
+
+}
+
static void
test_utils(void)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]