[balsa] Initial commit of libclient



commit c69dd2a5f8463de0597972a398e307ad2b0ac781
Author: Albrecht Dreß <albrecht dress arcor de>
Date:   Tue Feb 7 22:17:50 2017 -0500

    Initial commit of libclient

 libnetclient/Makefile.am               |   19 +
 libnetclient/README                    |  103 ++
 libnetclient/libnetclient.dox          |  265 +++++
 libnetclient/net-client-smtp.c         |  668 +++++++++++
 libnetclient/net-client-smtp.h         |  260 +++++
 libnetclient/net-client-utils.c        |   87 ++
 libnetclient/net-client-utils.h        |   75 ++
 libnetclient/net-client.c              |  518 +++++++++
 libnetclient/net-client.h              |  305 +++++
 libnetclient/test/Makefile.am          |   31 +
 libnetclient/test/ca_cert.pem          |   46 +
 libnetclient/test/cert.pem             |   53 +
 libnetclient/test/cert_u.pem           |   51 +
 libnetclient/test/inetsim.conf         | 1932 ++++++++++++++++++++++++++++++++
 libnetclient/test/start-test-env.sh    |   20 +
 libnetclient/test/start-test-env.sh.in |   20 +
 libnetclient/test/tests.c              |  470 ++++++++
 libnetclient/test/valgrind.supp        |   17 +
 18 files changed, 4940 insertions(+), 0 deletions(-)
---
diff --git a/libnetclient/Makefile.am b/libnetclient/Makefile.am
new file mode 100644
index 0000000..3562e41
--- /dev/null
+++ b/libnetclient/Makefile.am
@@ -0,0 +1,19 @@
+# $Id$
+
+noinst_LIBRARIES = libnetclient.a
+libnetclient_a_SOURCES =       \
+       net-client.c                    \
+       net-client.h                    \
+       net-client-smtp.c               \
+       net-client-smtp.h               \
+       net-client-utils.c              \
+       net-client-utils.h
+CLEANFILES = doxygen.log
+SUBDIRS = test
+AM_CFLAGS = $(LIBNETCLIENT_CFLAGS)
+
+clean-local:
+       -rm -rf html
+
+doc:
+       @DOXYGEN@ libnetclient.dox
\ No newline at end of file
diff --git a/libnetclient/README b/libnetclient/README
new file mode 100644
index 0000000..f5d4d23
--- /dev/null
+++ b/libnetclient/README
@@ -0,0 +1,103 @@
+Written by Albrecht Dreß <albrecht dress arcor de>
+Copyright (C) Albrecht Dreß 2017
+
+This library is free software: you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published bythe 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this library.  If not, see <http://www.gnu.org/licenses/>.
+
+
+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.
+
+
+Coding Style
+============
+
+The implementation tries to follow the MISRA C:2012 (MISRA C3) and SEI CERT
+Secure Coding standards for safer code.  For further details about these
+standards, see
+<https://www.misra.org.uk/Publications/tabid/57/Default.aspx> and
+<https://www.securecoding.cert.org/confluence/display/c/SEI+CERT+C+Coding+Standard>.
+
+
+Requirements
+============
+
+Apart from GLib/GIO (at least version 2.40.0) it requires GnuTLS.
+
+
+API Documentation
+=================
+
+Doxygen is required to create a HTML API documentation.  Install doxygen from
+<http://www.stack.nl/~dimitri/doxygen/download.html>.  Then run in this folder
+
+       make doc
+
+and open html/index.html.
+
+
+Testing
+=======
+
+In the folder test, a test application for running unit tests, a check for
+memory leaks and a coverage analysis is available.  The following packages
+are required for running them:
+
+- the Sput Unit Testing Framework for C/C++, available from
+  <http://www.use-strict.de/sput-unit-testing/>
+- ncat, available from <https://nmap.org/ncat/>
+- screen, available from <https://www.gnu.org/software/screen/>
+- sudo, available from <https://www.sudo.ws/>
+- Valgrind, avilable from <http://valgrind.org/>
+- LCOV and Genhtml, available from
+  <http://ltp.sourceforge.net/coverage/lcov.php>
+- gnutls-serv, which is a part of the GnuTLS package, available from
+  <https://www.gnutls.org/>
+- INetSim Internet Services Simulation Suite, available from
+  <http://www.inetsim.org/>
+
+Note that most of these requirements are typically available pre-packaged
+for your favorite distribution.
+
+For running the tests, open two terminal windows, and cd to the test folder
+of this package.
+
+In the first terminal, call
+
+       ./start-test-env.sh
+
+which will launch several dummy servers required for testing.  Note that
+INetSim requires root permissions and is thus called via sudo.
+
+Then, in the second terminal, call
+
+       make tests
+
+to build the test application, and run it in Valgrind.  Note that the test
+application will report many CRITICAL glib errors.  This is normal, as the
+behaviour of the library's parameter checks is verified.
+
+The test produces the following output:
+- stdout/stderr: unit test results, which shall report zero failed
+- tests.vg: the output of the Valgrind memory check
+- gcov/index.html: results of the coverage analysis
+
+Finally, just terminate the test servers in the first window.  Note that
+INetSim will dump further information in its output files (typically in
+/var/log/inetsim).  You should verify that INetSim recorded the requested
+operations properly.
+
+                                                               -oOo-
diff --git a/libnetclient/libnetclient.dox b/libnetclient/libnetclient.dox
new file mode 100644
index 0000000..699e013
--- /dev/null
+++ b/libnetclient/libnetclient.dox
@@ -0,0 +1,265 @@
+DOXYFILE_ENCODING = UTF-8
+PROJECT_NAME = "libnetclient"
+PROJECT_NUMBER = "0.1.0"
+PROJECT_BRIEF = 
+PROJECT_LOGO = 
+OUTPUT_DIRECTORY = .
+CREATE_SUBDIRS = NO
+ALLOW_UNICODE_NAMES = NO
+OUTPUT_LANGUAGE = English
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ABBREVIATE_BRIEF = "The $name class" "The $name widget" "The $name file" is provides specifies contains 
represents a an the
+ALWAYS_DETAILED_SEC = NO
+INLINE_INHERITED_MEMB = NO
+FULL_PATH_NAMES = YES
+STRIP_FROM_PATH = 
+STRIP_FROM_INC_PATH = 
+SHORT_NAMES = NO
+JAVADOC_AUTOBRIEF = NO
+QT_AUTOBRIEF = NO
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS = YES
+SEPARATE_MEMBER_PAGES = NO
+TAB_SIZE = 4
+TCL_SUBST = 
+OPTIMIZE_OUTPUT_FOR_C = YES
+OPTIMIZE_OUTPUT_JAVA = NO
+OPTIMIZE_FOR_FORTRAN = NO
+OPTIMIZE_OUTPUT_VHDL = NO
+EXTENSION_MAPPING = 
+MARKDOWN_SUPPORT = YES
+AUTOLINK_SUPPORT = YES
+BUILTIN_STL_SUPPORT = NO
+CPP_CLI_SUPPORT = NO
+SIP_SUPPORT = NO
+IDL_PROPERTY_SUPPORT = YES
+DISTRIBUTE_GROUP_DOC = NO
+GROUP_NESTED_COMPOUNDS = NO
+SUBGROUPING = YES
+INLINE_GROUPED_CLASSES = NO
+INLINE_SIMPLE_STRUCTS = NO
+TYPEDEF_HIDES_STRUCT = NO
+LOOKUP_CACHE_SIZE = 0
+EXTRACT_ALL = YES
+EXTRACT_PRIVATE = YES
+EXTRACT_PACKAGE = NO
+EXTRACT_STATIC = YES
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_LOCAL_METHODS = NO
+EXTRACT_ANON_NSPACES = NO
+HIDE_UNDOC_MEMBERS = NO
+HIDE_UNDOC_CLASSES = NO
+HIDE_FRIEND_COMPOUNDS = NO
+HIDE_IN_BODY_DOCS = NO
+INTERNAL_DOCS = NO
+CASE_SENSE_NAMES = NO
+HIDE_SCOPE_NAMES = NO
+HIDE_COMPOUND_REFERENCE = NO
+SHOW_INCLUDE_FILES = YES
+SHOW_GROUPED_MEMB_INC = NO
+FORCE_LOCAL_INCLUDES = NO
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+SORT_BRIEF_DOCS = NO
+SORT_MEMBERS_CTORS_1ST = NO
+SORT_GROUP_NAMES = NO
+SORT_BY_SCOPE_NAME = NO
+STRICT_PROTO_MATCHING = NO
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST = YES
+ENABLED_SECTIONS = 
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = YES
+SHOW_FILES = YES
+SHOW_NAMESPACES = YES
+FILE_VERSION_FILTER = 
+LAYOUT_FILE = 
+CITE_BIB_FILES = 
+QUIET = NO
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = YES
+WARN_IF_DOC_ERROR = YES
+WARN_NO_PARAMDOC = NO
+WARN_AS_ERROR = NO
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE = doxygen.log
+INPUT = ./
+INPUT_ENCODING = UTF-8
+FILE_PATTERNS = *.h
+RECURSIVE = NO
+EXCLUDE = 
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS = 
+EXCLUDE_SYMBOLS = 
+EXAMPLE_PATH = 
+EXAMPLE_PATTERNS = *
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH = 
+INPUT_FILTER = 
+FILTER_PATTERNS = 
+FILTER_SOURCE_FILES = NO
+FILTER_SOURCE_PATTERNS = 
+USE_MDFILE_AS_MAINPAGE = 
+SOURCE_BROWSER = NO
+INLINE_SOURCES = NO
+STRIP_CODE_COMMENTS = YES
+REFERENCED_BY_RELATION = NO
+REFERENCES_RELATION = NO
+REFERENCES_LINK_SOURCE = YES
+SOURCE_TOOLTIPS = YES
+USE_HTAGS = NO
+VERBATIM_HEADERS = YES
+CLANG_ASSISTED_PARSING = NO
+CLANG_OPTIONS = 
+ALPHABETICAL_INDEX = YES
+COLS_IN_ALPHA_INDEX = 5
+IGNORE_PREFIX = 
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER = 
+HTML_FOOTER = 
+HTML_STYLESHEET = 
+HTML_EXTRA_STYLESHEET = 
+HTML_EXTRA_FILES = 
+HTML_COLORSTYLE_HUE = 220
+HTML_COLORSTYLE_SAT = 100
+HTML_COLORSTYLE_GAMMA = 80
+HTML_TIMESTAMP = NO
+HTML_DYNAMIC_SECTIONS = NO
+HTML_INDEX_NUM_ENTRIES = 100
+GENERATE_DOCSET = NO
+DOCSET_FEEDNAME = "Doxygen generated docs"
+DOCSET_BUNDLE_ID = org.doxygen.Project
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+DOCSET_PUBLISHER_NAME = Publisher
+GENERATE_HTMLHELP = NO
+CHM_FILE = 
+HHC_LOCATION = 
+GENERATE_CHI = NO
+CHM_INDEX_ENCODING = 
+BINARY_TOC = NO
+TOC_EXPAND = NO
+GENERATE_QHP = NO
+QCH_FILE = 
+QHP_NAMESPACE = org.doxygen.Project
+QHP_VIRTUAL_FOLDER = doc
+QHP_CUST_FILTER_NAME = 
+QHP_CUST_FILTER_ATTRS = 
+QHP_SECT_FILTER_ATTRS = 
+QHG_LOCATION = 
+GENERATE_ECLIPSEHELP = NO
+ECLIPSE_DOC_ID = org.doxygen.Project
+DISABLE_INDEX = NO
+GENERATE_TREEVIEW = YES
+ENUM_VALUES_PER_LINE = 4
+TREEVIEW_WIDTH = 250
+EXT_LINKS_IN_WINDOW = NO
+FORMULA_FONTSIZE = 10
+FORMULA_TRANSPARENT = YES
+USE_MATHJAX = NO
+MATHJAX_FORMAT = HTML-CSS
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+MATHJAX_EXTENSIONS = 
+MATHJAX_CODEFILE = 
+SEARCHENGINE = YES
+SERVER_BASED_SEARCH = NO
+EXTERNAL_SEARCH = NO
+SEARCHENGINE_URL = 
+SEARCHDATA_FILE = searchdata.xml
+EXTERNAL_SEARCH_ID = 
+EXTRA_SEARCH_MAPPINGS = 
+GENERATE_LATEX = NO
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4
+EXTRA_PACKAGES = 
+LATEX_HEADER = 
+LATEX_FOOTER = 
+LATEX_EXTRA_STYLESHEET = 
+LATEX_EXTRA_FILES = 
+PDF_HYPERLINKS = YES
+USE_PDFLATEX = YES
+LATEX_BATCHMODE = NO
+LATEX_HIDE_INDICES = NO
+LATEX_SOURCE_CODE = NO
+LATEX_BIB_STYLE = plain
+LATEX_TIMESTAMP = NO
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE = 
+RTF_EXTENSIONS_FILE = 
+RTF_SOURCE_CODE = NO
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_SUBDIR = 
+MAN_LINKS = NO
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_PROGRAMLISTING = YES
+GENERATE_DOCBOOK = NO
+DOCBOOK_OUTPUT = docbook
+DOCBOOK_PROGRAMLISTING = NO
+GENERATE_AUTOGEN_DEF = NO
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX = 
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = NO
+SEARCH_INCLUDES = YES
+INCLUDE_PATH = 
+INCLUDE_FILE_PATTERNS = 
+PREDEFINED = G_GNUC_MALLOC= G_GNUC_PRINTF(x,y)=
+EXPAND_AS_DEFINED = 
+SKIP_FUNCTION_MACROS = NO
+TAGFILES = 
+GENERATE_TAGFILE = 
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = YES
+EXTERNAL_PAGES = YES
+PERL_PATH = /usr/bin/perl
+CLASS_DIAGRAMS = NO
+MSCGEN_PATH = 
+DIA_PATH = 
+HIDE_UNDOC_RELATIONS = NO
+HAVE_DOT = YES
+DOT_NUM_THREADS = 0
+DOT_FONTNAME = Helvetica
+DOT_FONTSIZE = 10
+DOT_FONTPATH = 
+CLASS_GRAPH = YES
+COLLABORATION_GRAPH = YES
+GROUP_GRAPHS = YES
+UML_LOOK = YES
+UML_LIMIT_NUM_FIELDS = 10
+TEMPLATE_RELATIONS = NO
+INCLUDE_GRAPH = YES
+INCLUDED_BY_GRAPH = YES
+CALL_GRAPH = YES
+CALLER_GRAPH = YES
+GRAPHICAL_HIERARCHY = YES
+DIRECTORY_GRAPH = YES
+DOT_IMAGE_FORMAT = png
+INTERACTIVE_SVG = NO
+DOT_PATH = 
+DOTFILE_DIRS = 
+MSCFILE_DIRS = 
+DIAFILE_DIRS = 
+PLANTUML_JAR_PATH = 
+PLANTUML_INCLUDE_PATH = 
+DOT_GRAPH_MAX_NODES = 50
+MAX_DOT_GRAPH_DEPTH = 0
+DOT_TRANSPARENT = NO
+DOT_MULTI_TARGETS = NO
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
diff --git a/libnetclient/net-client-smtp.c b/libnetclient/net-client-smtp.c
new file mode 100644
index 0000000..74c7858
--- /dev/null
+++ b/libnetclient/net-client-smtp.c
@@ -0,0 +1,668 @@
+/* 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 <glib/gi18n.h>
+#include "net-client-utils.h"
+#include "net-client-smtp.h"
+
+
+struct _NetClientSmtpPrivate {
+       NetClientCryptMode crypt_mode;
+       guint auth_allowed[2];                  /** 0: encrypted, 1: unencrypted */
+       guint auth_supported;
+       gboolean can_dsn;
+       gboolean can_starttls;
+};
+
+
+struct _NetClientSmtpMessage {
+       gchar *sender;
+       GList *recipients;
+       gchar *dsn_envid;
+       gboolean dsn_ret_full;
+       gboolean have_dsn_rcpt;
+       NetClientSmtpSendCb data_callback;
+       gpointer user_data;
+};
+
+
+typedef struct {
+       gchar *rfc5321_addr;
+       NetClientSmtpDsnMode dsn_mode;
+} smtp_rcpt_t;
+
+
+#define MAX_SMTP_LINE_LEN                      512U
+#define SMTP_DATA_BUF_SIZE                     8192U
+
+
+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_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_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_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);
+static void smtp_rcpt_free(smtp_rcpt_t *rcpt);
+
+
+NetClientSmtp *
+net_client_smtp_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mode)
+{
+       NetClientSmtp *client;
+
+       g_return_val_if_fail(host != NULL, NULL);
+
+       client = NET_CLIENT_SMTP(g_object_new(NET_CLIENT_SMTP_TYPE, NULL));
+       if (client != NULL) {
+               if (!net_client_configure(NET_CLIENT(client), host, port, MAX_SMTP_LINE_LEN, NULL)) {
+                       g_object_unref(G_OBJECT(client));
+                       client = NULL;
+               } else {
+                       client->priv->crypt_mode = crypt_mode;
+               }
+       }
+
+       return client;
+}
+
+
+gboolean
+net_client_smtp_allow_auth(NetClientSmtp *client, gboolean encrypted, guint allow_auth)
+{
+       /* paranoia check */
+       g_return_val_if_fail(NET_IS_CLIENT_SMTP(client), FALSE);
+       if (encrypted) {
+               client->priv->auth_allowed[0] = allow_auth;
+       } else {
+               client->priv->auth_allowed[1] = allow_auth;
+       }
+       return TRUE;
+}
+
+
+gboolean
+net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error)
+{
+       gboolean result;
+
+       /* paranoia checks */
+       g_return_val_if_fail(NET_IS_CLIENT_SMTP(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_smtp_read_reply(client, 220, greeting, error);
+       }
+
+       /* send EHLO and read the capabilities of the server */
+       if (result) {
+               result = net_client_smtp_ehlo(client, 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 (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"));
+                               result = FALSE;
+                       }
+               } else {
+                       result = net_client_smtp_starttls(client, error);
+                       if (result) {
+                               result = net_client_smtp_ehlo(client, error);
+                       }
+               }
+       }
+
+       /* 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_smtp_auth(client, auth_data[0], auth_data[1], 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_smtp_can_dsn(NetClientSmtp *client)
+{
+       return NET_IS_CLIENT_SMTP(client) ? client->priv->can_dsn : FALSE;
+}
+
+
+gboolean
+net_client_smtp_send_msg(NetClientSmtp *client, const NetClientSmtpMessage *message, GError **error)
+{
+       gboolean result;
+       const GList *rcpt;
+
+       /* paranoia checks */
+       g_return_val_if_fail(NET_IS_CLIENT_SMTP(client) && (message != NULL) && (message->sender != NULL) &&
+               (message->recipients != NULL) && (message->data_callback != NULL), FALSE);
+
+       /* set the RFC 5321 sender and recipient(s) */
+       if (message->have_dsn_rcpt) {
+               if (message->dsn_envid != NULL) {
+                       result = net_client_smtp_execute(client, "MAIL FROM:<%s> RET=%s ENVID=%s", NULL, 
error, message->sender,
+                                                                                        
(message->dsn_ret_full) ? "FULL" : "HDRS", message->dsn_envid);
+               } else {
+                       result = net_client_smtp_execute(client, "MAIL FROM:<%s> RET=%s", NULL, error, 
message->sender,
+                                                                                        
(message->dsn_ret_full) ? "FULL" : "HDRS");
+               }
+       } else {
+               result = net_client_smtp_execute(client, "MAIL FROM:<%s>", NULL, error, message->sender);
+       }
+       rcpt = message->recipients;
+       while (result && (rcpt != NULL)) {
+               const smtp_rcpt_t *this_rcpt = (const smtp_rcpt_t *) rcpt->data;        /*lint !e9079 !e9087 
(MISRA C:2012 Rules 11.3, 11.5) */
+               gchar *dsn_opts;
+
+               /* create the RFC 3461 DSN string */
+               dsn_opts = net_client_smtp_dsn_to_string(client, this_rcpt->dsn_mode);
+               result = net_client_smtp_execute(client, "RCPT TO:<%s>%s", NULL, error, 
this_rcpt->rfc5321_addr, dsn_opts);
+               g_free(dsn_opts);
+               rcpt = rcpt->next;
+       }
+
+       /* initialise sending the message data */
+       if (result) {
+               result = net_client_smtp_execute(client, "DATA", NULL, error);
+       }
+
+       /* call the data callback until all data has been transmitted or an error occurs */
+       if (result) {
+               gchar buffer[SMTP_DATA_BUF_SIZE];
+               gssize count;
+               gchar last_char = '\0';
+
+               do {
+                       count = message->data_callback(buffer, SMTP_DATA_BUF_SIZE, message->user_data, error);
+                       if (count < 0) {
+                               result = FALSE;
+                       } else if (count > 0) {
+                               result = net_client_write_buffer(NET_CLIENT(client), buffer, (gsize) count, 
error);
+                               last_char = buffer[count - 1];
+                       } else {
+                               /* write termination */
+                               if (last_char == '\n') {
+                                       result = net_client_write_buffer(NET_CLIENT(client), ".\r\n", 3U, 
error);
+                               } else {
+                                       result = net_client_write_buffer(NET_CLIENT(client), "\r\n.\r\n", 5U, 
error);
+                               }
+                       }
+               } while (result && (count > 0));
+       }
+
+       if (result) {
+               result = net_client_smtp_read_reply(client, -1, NULL, error);
+       }
+
+       return result;
+}
+
+
+NetClientSmtpMessage *
+net_client_smtp_msg_new(NetClientSmtpSendCb data_callback, gpointer user_data)
+{
+       NetClientSmtpMessage *smtp_msg;
+
+       g_return_val_if_fail(data_callback != NULL, NULL);
+
+       smtp_msg = g_new0(NetClientSmtpMessage, 1U);
+       smtp_msg->data_callback = data_callback;
+       smtp_msg->user_data = user_data;
+       return smtp_msg;
+}
+
+
+gboolean
+net_client_smtp_msg_set_dsn_opts(NetClientSmtpMessage *smtp_msg, const gchar *envid, gboolean ret_full)
+{
+       g_return_val_if_fail(smtp_msg != NULL, FALSE);
+
+       g_free(smtp_msg->dsn_envid);
+       if (envid != NULL) {
+               smtp_msg->dsn_envid = g_strdup(envid);
+       } else {
+               smtp_msg->dsn_envid = NULL;
+       }
+       smtp_msg->dsn_ret_full = ret_full;
+       return TRUE;
+}
+
+
+gboolean
+net_client_smtp_msg_set_sender(NetClientSmtpMessage *smtp_msg, const gchar *rfc5321_sender)
+{
+       g_return_val_if_fail((smtp_msg != NULL) && (rfc5321_sender != NULL), FALSE);
+
+       g_free(smtp_msg->sender);
+       smtp_msg->sender = g_strdup(rfc5321_sender);
+       return TRUE;
+}
+
+
+gboolean
+net_client_smtp_msg_add_recipient(NetClientSmtpMessage *smtp_msg, const gchar *rfc5321_rcpt, 
NetClientSmtpDsnMode dsn_mode)
+{
+       smtp_rcpt_t *new_rcpt;
+
+       g_return_val_if_fail((smtp_msg != NULL) && (rfc5321_rcpt != NULL), FALSE);
+       new_rcpt = g_new0(smtp_rcpt_t, 1U);
+       new_rcpt->rfc5321_addr = g_strdup(rfc5321_rcpt);
+       new_rcpt->dsn_mode = dsn_mode;
+       smtp_msg->recipients = g_list_append(smtp_msg->recipients, new_rcpt);
+       if (dsn_mode != NET_CLIENT_SMTP_DSN_NEVER) {
+               smtp_msg->have_dsn_rcpt = TRUE;
+       }
+       return TRUE;
+}
+
+
+void
+net_client_smtp_msg_free(NetClientSmtpMessage *smtp_msg)
+{
+       if (smtp_msg != NULL) {
+               g_free(smtp_msg->sender);
+               g_free(smtp_msg->dsn_envid);
+               /*lint -e{9074} -e{9087}        accept safe (and required) pointer conversion */
+               g_list_free_full(smtp_msg->recipients, (GDestroyNotify) smtp_rcpt_free);
+               g_free(smtp_msg);
+       }
+}
+
+
+/* == local functions 
=========================================================================================================== */
+
+static void
+net_client_smtp_class_init(NetClientSmtpClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+       gobject_class->finalize = net_client_smtp_finalise;
+}
+
+
+static void
+net_client_smtp_init(NetClientSmtp *self)
+{
+       self->priv = g_new0(NetClientSmtpPrivate, 1U);
+       self->priv->auth_allowed[0] = NET_CLIENT_SMTP_AUTH_ALL;
+       self->priv->auth_allowed[1] = NET_CLIENT_SMTP_AUTH_SAFE;
+}
+
+
+static void
+net_client_smtp_finalise(GObject *object)
+{
+       const NetClientSmtp *client = NET_CLIENT_SMTP(object);
+       const GObjectClass *parent_class = G_OBJECT_CLASS(net_client_smtp_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);
+
+       g_free(client->priv);
+       (*parent_class->finalize)(object);
+}
+
+
+static gboolean
+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);
+       }
+
+       return result;
+}
+
+
+static gboolean
+net_client_smtp_auth(NetClientSmtp *client, const gchar *user, const gchar *passwd, GError **error)
+{
+       gboolean result;
+       guint auth_mask;
+
+       g_return_val_if_fail(NET_IS_CLIENT_SMTP(client), FALSE);
+
+       if (net_client_is_encrypted(NET_CLIENT(client))) {
+               auth_mask = client->priv->auth_allowed[0] & client->priv->auth_supported;
+       } else {
+               auth_mask = client->priv->auth_allowed[1] & client->priv->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);
+       } else {
+               g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_NO_AUTH,
+                       _("no suitable authentication mechanism"));
+               result = FALSE;
+       }
+
+       return result;
+}
+
+
+static gboolean
+net_client_smtp_auth_plain(NetClientSmtp *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_smtp_execute(client, "AUTH PLAIN %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_smtp_auth_login(NetClientSmtp *client, const gchar *user, const gchar *passwd, GError **error)
+{
+       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));
+       g_free(base64_buf);
+       if (result) {
+               base64_buf = g_base64_encode((const guchar *) passwd, strlen(passwd));
+               result = net_client_smtp_execute(client, "%s", NULL, error, base64_buf);
+               memset(base64_buf, 0, strlen(base64_buf));
+               g_free(base64_buf);
+       }
+
+       return result;
+}
+
+
+static gboolean
+net_client_smtp_auth_cram(NetClientSmtp *client, GChecksumType chksum_type, const gchar *user, const gchar 
*passwd, GError **error)
+{
+       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;
+
+               auth_buf = net_client_cram_calc(challenge, chksum_type, user, passwd);
+               if (auth_buf != NULL) {
+                       result = net_client_smtp_execute(client, "%s", NULL, error, auth_buf);
+                       memset(auth_buf, 0, strlen(auth_buf));
+                       g_free(auth_buf);
+               } else {
+                       result = FALSE;
+               }
+       }
+       g_free(challenge);
+
+       return result;
+}
+
+
+static gboolean
+net_client_smtp_execute(NetClientSmtp *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_smtp_read_reply(client, -1, last_reply, error);
+       }
+
+       return result;
+}
+
+
+static gboolean
+net_client_smtp_ehlo(NetClientSmtp *client, GError **error)
+{
+       gboolean result;
+       gboolean done;
+
+       result = net_client_write_line(NET_CLIENT(client), "EHLO %s", error, g_get_host_name());
+
+       /* clear all capability flags */
+       client->priv->auth_supported = 0U;
+       client->priv->can_dsn = FALSE;
+       client->priv->can_starttls = FALSE;
+
+       /* evaluate the response */
+       done = FALSE;
+       while (result && !done) {
+               gchar *reply;
+
+               result = net_client_read_line(NET_CLIENT(client), &reply, error);
+               if (result) {
+                       gint reply_code;
+                       gchar *endptr;
+
+                       reply_code = strtol(reply, &endptr, 10);
+                       if (reply_code != 250) {
+                               g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) 
NET_CLIENT_ERROR_SMTP_PROTOCOL,
+                                       _("bad server reply: %s"), reply);
+                               result = FALSE;
+                       } else {
+                               if (strcmp(&endptr[1], "DSN") == 0) {
+                                       client->priv->can_dsn = TRUE;
+                               } else if (strcmp(&endptr[1], "STARTTLS") == 0) {
+                                       client->priv->can_starttls = TRUE;
+                               } else if ((strncmp(&endptr[1], "AUTH ", 5U) == 0) || (strncmp(&endptr[1], 
"AUTH=", 5U) == 0)) {
+                                       gchar **auth;
+                                       guint n;
+
+                                       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;
+                                               } else if (strcmp(auth[n], "LOGIN") == 0) {
+                                                       client->priv->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;
+                                               } else if (strcmp(auth[n], "CRAM-SHA1") == 0) {
+                                                       client->priv->auth_supported |= 
NET_CLIENT_SMTP_AUTH_CRAM_SHA1;
+                                               } else {
+                                                       /* other auth methods are ignored for the time being 
*/
+                                               }
+                                       }
+                                       g_strfreev(auth);
+                               } else {
+                                       /* ignored */
+                               }
+
+                               if (*endptr == ' ') {
+                                       done = TRUE;
+                               }
+                       }
+
+                       g_free(reply);
+               }
+       }
+
+       return result;
+}
+
+
+/* Note: according to RFC 5321, sect. 4.2, \em any reply may be multiline. */
+static gboolean
+net_client_smtp_read_reply(NetClientSmtp *client, gint expect_code, gchar **last_reply, GError **error)
+{
+       gint rescode;
+       gboolean done;
+       gboolean result;
+
+       done = FALSE;
+       rescode = expect_code;
+       do {
+               gchar *reply;
+
+               result = net_client_read_line(NET_CLIENT(client), &reply, error);
+               if (result) {
+                       gint this_rescode;
+                       gchar *endptr;
+
+                       this_rescode = strtol(reply, &endptr, 10);
+                       if (rescode == -1) {
+                               rescode = this_rescode;
+                               result = net_client_smtp_eval_rescode(rescode, reply, error);
+                       } else if (rescode != this_rescode) {
+                               g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) 
NET_CLIENT_ERROR_SMTP_PROTOCOL,
+                                       _("bad server reply: %s"), reply);
+                               result = FALSE;
+                       } else {
+                               /* nothing to do (see MISRA C:2012, Rule 15.7) */
+                       }
+                       if (reply[3] == ' ') {
+                               done = TRUE;
+                               if (last_reply != NULL) {
+                                       *last_reply = g_strdup(&reply[4]);
+                               }
+                       }
+
+                       g_free(reply);
+               }
+       } while (result && !done);
+
+       return result;
+}
+
+
+static gboolean
+net_client_smtp_eval_rescode(gint res_code, const gchar *reply, GError **error)
+{
+       gboolean result;
+
+       switch (res_code / 100) {
+       case 2:
+       case 3:
+               result = TRUE;
+               break;
+       case 4:
+               g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_TRANSIENT,
+                       _("transient error %d: %s"), res_code, reply);
+               result = FALSE;
+               break;
+       case 5:
+               g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_PERMANENT,
+                       _("permanent error %d: %s"), res_code, reply);
+               result = FALSE;
+               break;
+       default:
+               g_set_error(error, NET_CLIENT_SMTP_ERROR_QUARK, (gint) NET_CLIENT_ERROR_SMTP_PROTOCOL,
+                       _("bad server reply: %s"), reply);
+               result = FALSE;
+               break;
+       }
+
+       return result;
+}
+
+
+static gchar *
+net_client_smtp_dsn_to_string(const NetClientSmtp *client, NetClientSmtpDsnMode dsn_mode)
+{
+       gchar *result;
+
+       /* create the RFC 3461 DSN string */
+       if (client->priv->can_dsn && (dsn_mode != NET_CLIENT_SMTP_DSN_NEVER)) {
+               GString *dsn_buf;
+               gsize start_len;
+
+               dsn_buf = g_string_new(" NOTIFY=");
+               start_len = dsn_buf->len;
+               if ((dsn_mode & NET_CLIENT_SMTP_DSN_DELAY) == NET_CLIENT_SMTP_DSN_DELAY) {
+                       dsn_buf = g_string_append(dsn_buf, "DELAY");
+               }
+               if ((dsn_mode & NET_CLIENT_SMTP_DSN_FAILURE) == NET_CLIENT_SMTP_DSN_FAILURE) {
+                       if (start_len != dsn_buf->len) {
+                               dsn_buf = g_string_append_c(dsn_buf, ',');
+                       }
+                       dsn_buf = g_string_append(dsn_buf, "FAILURE");
+               }
+               if ((dsn_mode & NET_CLIENT_SMTP_DSN_SUCCESS) == NET_CLIENT_SMTP_DSN_SUCCESS) {
+                       if (start_len != dsn_buf->len) {
+                               dsn_buf = g_string_append_c(dsn_buf, ',');
+                       }
+                       dsn_buf = g_string_append(dsn_buf, "SUCCESS");
+               }
+               result = g_string_free(dsn_buf, FALSE);
+       } else {
+               result = g_strdup("");
+       }
+
+       return result;
+}
+
+
+static void
+smtp_rcpt_free(smtp_rcpt_t *rcpt)
+{
+       g_free(rcpt->rfc5321_addr);
+       g_free(rcpt);
+}
diff --git a/libnetclient/net-client-smtp.h b/libnetclient/net-client-smtp.h
new file mode 100644
index 0000000..b098d83
--- /dev/null
+++ b/libnetclient/net-client-smtp.h
@@ -0,0 +1,260 @@
+/* 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_SMTP_H_
+#define NET_CLIENT_SMTP_H_
+
+
+#include "net-client.h"
+
+
+G_BEGIN_DECLS
+
+
+#define NET_CLIENT_SMTP_TYPE                           (net_client_smtp_get_type())
+#define NET_CLIENT_SMTP(obj)                           (G_TYPE_CHECK_INSTANCE_CAST((obj), 
NET_CLIENT_SMTP_TYPE, NetClientSmtp))
+#define NET_IS_CLIENT_SMTP(obj)                                (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
NET_CLIENT_SMTP_TYPE))
+#define NET_CLIENT_SMTP_CLASS(klass)           (G_TYPE_CHECK_CLASS_CAST((klass), NET_CLIENT_SMTP_TYPE, 
NetClientSmtpClass))
+#define NET_IS_CLIENT_SMTP_CLASS(klass)                (G_TYPE_CHECK_CLASS_TYPE((klass), 
NET_CLIENT_SMTP_TYPE))
+#define NET_CLIENT_SMTP_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS((obj), NET_CLIENT_SMTP_TYPE, 
NetClientSmtpClass))
+
+#define NET_CLIENT_SMTP_ERROR_QUARK                    (g_quark_from_static_string("net-client-smtp"))
+
+
+typedef struct _NetClientSmtp NetClientSmtp;
+typedef struct _NetClientSmtpClass NetClientSmtpClass;
+typedef struct _NetClientSmtpPrivate NetClientSmtpPrivate;
+typedef struct _NetClientSmtpMessage NetClientSmtpMessage;
+typedef enum _NetClientSmtpDsnMode NetClientSmtpDsnMode;
+
+
+/** @brief SMTP-specific error codes */
+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_STARTTLS               /**< The server does not support STARTTLS. */
+};
+
+
+/** @name SMTP authentication methods
+ * @{
+ */
+/** RFC 4616 "PLAIN" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_PLAIN                     0x01U
+/** "LOGIN" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_LOGIN                     0x02U
+/** RFC 2195 "CRAM-MD5" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_CRAM_MD5          0x04U
+/** RFC xxxx "CRAM-SHA1" authentication method. */
+#define NET_CLIENT_SMTP_AUTH_CRAM_SHA1         0x08U
+/** 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)
+/** 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)
+/** @} */
+
+
+struct _NetClientSmtp {
+    NetClient parent;
+    NetClientSmtpPrivate *priv;
+};
+
+
+struct _NetClientSmtpClass {
+       NetClientClass parent;
+};
+
+
+/** @brief Delivery Status Notification mode
+ *
+ * See <a href="https://tools.ietf.org/html/rfc3461";>RFC 3461</a> for a description of Delivery Status 
Notifications (DSNs).  The
+ * DSN mode is the logical OR of these options.
+ */
+enum _NetClientSmtpDsnMode {
+       NET_CLIENT_SMTP_DSN_NEVER = 0,                  /**< Never request a DSN (do not combine with other 
options). */
+       NET_CLIENT_SMTP_DSN_SUCCESS = 1,                /**< Request a DSN on successful delivery. */
+       NET_CLIENT_SMTP_DSN_FAILURE = 2,                /**< Request a DSN on delivery failure. */
+       NET_CLIENT_SMTP_DSN_DELAY = 4                   /**< Request a DSN if delivery of a message has been 
delayed. */
+};
+
+
+/** @brief SMTP Message Transmission Callback Function
+ *
+ * The user-provided callback function to send a message to the remote SMTP server:
+ * - @em buffer - shall be filled with the next chunk of data
+ * - @em count - maximum number of bytes which may be written to @em buffer
+ * - @em user_data - user data pointer (_NetClientSmtpMessage::user_data)
+ * - @em error - shall be filled with error information if an error occurs in the callback
+ * - return value: a value > 0 indicating the number of bytes written to @em buffer, or 0 to indicate that 
all data has been
+ *   transferred, or a value < 0 to indicate an error in the callback function.
+ *
+ * @note The callback function is responsible for properly formatting the message body according to
+ *       <a href="https://tools.ietf.org/html/rfc5321";>RFC 5321</a>, <a 
href="https://tools.ietf.org/html/rfc5322";>RFC 5322</a> and
+ *       further relevant standards, e.g. by using <a href="http://spruce.sourceforge.net/gmime/";>GMime</a>.
+ */
+typedef gssize (*NetClientSmtpSendCb)(gchar *buffer, gsize count, gpointer user_data, GError **error);
+
+
+GType net_client_smtp_get_type(void)
+       G_GNUC_CONST;
+
+
+/** @brief Create a new SMTP network client
+ *
+ * @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
+ */
+NetClientSmtp *net_client_smtp_new(const gchar *host, guint16 port, NetClientCryptMode crypt_mode);
+
+
+/** @brief Set allowed SMTP AUTH methods
+ *
+ * @param client SMTP network client object
+ * @param encrypted set allowed methods for encrypted or unencrypted connections
+ * @param allow_auth mask of allowed authentication methods
+ * @return TRUE on success or FALSE on error
+ *
+ * Set the allowed authentication methods for the passed connection.  The default is @ref 
NET_CLIENT_SMTP_AUTH_ALL for encrypted and
+ * @ref NET_CLIENT_SMTP_AUTH_SAFE for unencrypted connections, respectively.
+ *
+ * @note Call this function @em before calling net_client_smtp_connect().
+ */
+gboolean net_client_smtp_allow_auth(NetClientSmtp *client, gboolean encrypted, guint allow_auth);
+
+
+/** @brief Connect a SMTP network client
+ *
+ * @param client SMTP network client object
+ * @param greeting filled with the greeting of the SMTP 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 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.
+ *
+ * @note The caller must free the returned greeting when it is not needed any more.
+ */
+gboolean net_client_smtp_connect(NetClientSmtp *client, gchar **greeting, GError **error);
+
+
+/** @brief Check if the SMTP network client supports Delivery Status Notifications
+ *
+ * @param client connected SMTP network client object
+ * @return TRUE is DSN's are supported, FALSE if not
+ *
+ * Return if the connected SMTP server announced support for Delivery Status Notifications (DSNs) according 
to
+ * <a href="https://tools.ietf.org/html/rfc3461";>RFC 3461</a>.
+ */
+gboolean net_client_smtp_can_dsn(NetClientSmtp *client);
+
+
+/** @brief Send a message to a SMTP network client
+ *
+ * @param client connected SMTP network client object
+ * @param message message data
+ * @param error filled with error information if the connection fails
+ * @return TRUE on success or FALSE if sending the message failed
+ *
+ * Send the passed SMTP message to the connected SMTP server.
+ */
+gboolean net_client_smtp_send_msg(NetClientSmtp *client, const NetClientSmtpMessage *message, GError 
**error);
+
+
+/** @brief Create a SMTP message
+ *
+ * @param data_callback callback function called to send the message data
+ * @param user_data additional user data passed to the callback function
+ * @return a newly create SMTP message
+ *
+ * Create a message suitable for transmission by calling net_client_smtp_send_msg().  At least one sender 
and at least one recipient
+ * must be added by calling net_client_smtp_msg_set_sender() and net_client_smtp_msg_add_recipient(), 
respectively.  When the SMTP
+ * message is not needed any more, call net_client_smtp_msg_free() to free it.
+ */
+NetClientSmtpMessage *net_client_smtp_msg_new(NetClientSmtpSendCb data_callback, gpointer user_data)
+       G_GNUC_MALLOC;
+
+
+/** @brief Set the sender of a SMTP message
+ *
+ * @param smtp_msg SMTP message returned by net_client_smtp_msg_new()
+ * @param rfc5321_sender RFC 5321-compliant sender address
+ * @return TRUE on success or FALSE on error
+ *
+ * Set the sender address ("MAIL FROM" reverse-path, see <a href="https://tools.ietf.org/html/rfc5321";>RFC 
5321</a>) of the SMTP
+ * message.
+ */
+gboolean net_client_smtp_msg_set_sender(NetClientSmtpMessage *smtp_msg, const gchar *rfc5321_sender);
+
+
+/** @brief Set options for Delivery Status Notifications
+ *
+ * @param smtp_msg SMTP message returned by net_client_smtp_msg_new()
+ * @param envid ENVID parameter to the ESMTP MAIL command, may be NULL
+ * @param ret_full return the full message on failure instead of the headers only
+ * @return TRUE on success or FALSE on error
+ *
+ * Set the @em ENVID and @em RET parameters for Delivery Status Notifications (DSN's).  The default is to 
omit the ENVID and to
+ * request headers only.
+ */
+gboolean net_client_smtp_msg_set_dsn_opts(NetClientSmtpMessage *smtp_msg, const gchar *envid, gboolean 
ret_full);
+
+
+/** @brief Add a recipient to a SMTP message
+ *
+ * @param smtp_msg SMTP message returned by net_client_smtp_msg_new()
+ * @param rfc5321_rcpt RFC 5321-compliant recipient address
+ * @param dsn_mode Delivery Status Notification mode for the recipient
+ * @return TRUE on success or FALSE on error
+ *
+ * Add a recipient address ("RCPT TO" forward-path, see <a href="https://tools.ietf.org/html/rfc5321";>RFC 
5321</a>) to the SMTP
+ * message.
+ */
+gboolean net_client_smtp_msg_add_recipient(NetClientSmtpMessage *smtp_msg, const gchar *rfc5321_rcpt,
+                                                                                  NetClientSmtpDsnMode 
dsn_mode);
+
+
+/** @brief Free a SMTP message
+ *
+ * @param smtp_msg SMTP message returned by net_client_smtp_msg_new()
+ *
+ * Free all resources of the passed SMTP message.
+ */
+void net_client_smtp_msg_free(NetClientSmtpMessage *smtp_msg);
+
+
+/** @file
+ *
+ * This module implements a SMTP client class conforming with <a 
href="https://tools.ietf.org/html/rfc5321";>RFC 5321</a>.
+ *
+ * The following additional features are supported:
+ * - Authentication according to <a href="https://tools.ietf.org/html/rfc4954";>RFC 4954</a>, using the 
methods
+ *   - CRAM-MD5 according to <a href="https://tools.ietf.org/html/rfc2195";>RFC 2195</a>
+ *   - 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
+ * - 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>
+ */
+
+
+G_END_DECLS
+
+
+#endif /* NET_CLIENT_SMTP_H_ */
diff --git a/libnetclient/net-client-utils.c b/libnetclient/net-client-utils.c
new file mode 100644
index 0000000..79d1677
--- /dev/null
+++ b/libnetclient/net-client-utils.c
@@ -0,0 +1,87 @@
+/* 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 <string.h>
+#include <stdio.h>
+#include "net-client-utils.h"
+
+gchar *
+net_client_cram_calc(const gchar *base64_challenge, GChecksumType chksum_type, const gchar *user, const 
gchar *passwd)
+{
+       guchar *chal_plain;
+       gsize plain_len;
+       gchar *digest;
+       gchar *auth_buf;
+       gchar *base64_buf;
+
+       g_return_val_if_fail((base64_challenge != NULL) && (user != NULL) && (passwd != NULL), NULL);
+
+       chal_plain = g_base64_decode(base64_challenge, &plain_len);
+       digest = g_compute_hmac_for_data(chksum_type, (const guchar *) passwd, strlen(passwd), chal_plain, 
plain_len);
+       memset(chal_plain, 0, plain_len);
+       g_free(chal_plain);
+
+       auth_buf = g_strdup_printf("%s %s", user, digest);
+       memset(digest, 0, strlen(digest));
+       g_free(digest);
+
+       base64_buf = g_base64_encode((const guchar *) auth_buf, strlen(auth_buf));
+       memset(auth_buf, 0, strlen(auth_buf));
+       g_free(auth_buf);
+
+       return base64_buf;
+}
+
+
+const gchar *
+net_client_chksum_to_str(GChecksumType chksum_type)
+{
+       /*lint -e{904} -e{9077} -e{9090}        (MISRA C:2012 Rules 15.5, 16.1, 16.3) */
+       switch (chksum_type) {
+       case G_CHECKSUM_MD5:
+               return "MD5";
+       case G_CHECKSUM_SHA1:
+               return "SHA1";
+       case G_CHECKSUM_SHA256:
+               return "SHA256";
+       case G_CHECKSUM_SHA512:
+               return "SHA512";
+       default:
+               return "_UNKNOWN_";
+       }
+}
+
+
+gchar *
+net_client_auth_plain_calc(const gchar *user, const gchar *passwd)
+{
+       gchar *base64_buf;
+       gchar *plain_buf;
+       size_t user_len;
+       size_t passwd_len;
+
+       g_return_val_if_fail((user != NULL) && (passwd != NULL), NULL);
+
+       user_len = strlen(user);
+       passwd_len = strlen(passwd);
+       plain_buf = g_malloc0((2U * user_len) + passwd_len + 3U);               /*lint !e9079 (MISRA C:2012 
Rule 11.5) */
+       strcpy(plain_buf, user);
+       strcpy(&plain_buf[user_len + 1U], user);
+       strcpy(&plain_buf[(2U * user_len) + 2U], passwd);
+       base64_buf = g_base64_encode((const guchar *) plain_buf, (2U * user_len) + passwd_len + 2U);
+       memset(plain_buf, 0, (2U * user_len) + passwd_len + 2U);
+       g_free(plain_buf);
+
+       return base64_buf;
+}
diff --git a/libnetclient/net-client-utils.h b/libnetclient/net-client-utils.h
new file mode 100644
index 0000000..c3065bb
--- /dev/null
+++ b/libnetclient/net-client-utils.h
@@ -0,0 +1,75 @@
+/* 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_UTILS_H_
+#define NET_CLIENT_UTILS_H_
+
+
+#include <gio/gio.h>
+
+
+G_BEGIN_DECLS
+
+
+/** @brief Calculate a CRAM authentication string
+ *
+ * @param base64_challenge base64-encoded challenge sent by the server
+ * @param chksum_type checksum type
+ * @param user user name
+ * @param passwd password
+ * @return a newly allocated string containing the base64-encoded authentication
+ *
+ * This helper function calculates the the base64-encoded authentication string from the server challenge, 
the user name and the
+ * password according to the standards <a href="https://tools.ietf.org/html/rfc2104";>RFC 2104 (MD5, 
SHA1)</a> and
+ * <a href="https://tools.ietf.org/html/rfc4868";>RFC 4868 (SHA256, SHA512)</a>.  The caller shall free the 
returned string when it
+ * is not needed any more.
+ *
+ * \sa <a href="https://tools.ietf.org/html/rfc2195";>RFC 2195</a>.
+ */
+gchar *net_client_cram_calc(const gchar *base64_challenge, GChecksumType chksum_type, const gchar *user, 
const gchar *passwd)
+       G_GNUC_MALLOC;
+
+
+/** @brief Get the checksum type as string
+ *
+ * @param chksum_type checksum type
+ * @return a string representation of the checksum type
+ */
+const gchar *net_client_chksum_to_str(GChecksumType chksum_type);
+
+
+/** @brief Calculate a SASL AUTH PLAIN authentication string
+ *
+ * @param user user name
+ * @param passwd password
+ * @return a newly allocated string containing the base64-encoded authentication
+ *
+ * This helper function calculates the the base64-encoded SASL AUTH PLAIN authentication string from the 
user name and the password
+ * 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.
+ */
+gchar *net_client_auth_plain_calc(const gchar *user, const gchar *passwd)
+       G_GNUC_MALLOC;
+
+
+/** @file
+ *
+ * This module implements authentication-related helper functions for the network client library.
+ */
+
+
+G_END_DECLS
+
+
+#endif /* NET_CLIENT_UTILS_H_ */
diff --git a/libnetclient/net-client.c b/libnetclient/net-client.c
new file mode 100644
index 0000000..1250769
--- /dev/null
+++ b/libnetclient/net-client.c
@@ -0,0 +1,518 @@
+/* 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 <string.h>
+#include <stdio.h>
+#include <glib/gi18n.h>
+#include <gnutls/x509.h>
+#include "net-client.h"
+
+
+#define LINE_BUF_LEN                                   1024U
+
+
+struct _NetClientPrivate {
+       gchar *host_and_port;
+       guint16 default_port;
+       gsize max_line_len;
+
+       GSocketClient *sock;
+       GSocketConnection *plain_conn;
+       GIOStream *tls_conn;
+       GDataInputStream *istream;
+       GOutputStream *ostream;
+       GTlsCertificate *certificate;
+};
+
+
+static guint signals[3];
+
+
+G_DEFINE_TYPE(NetClient, net_client, G_TYPE_OBJECT)
+
+
+static void net_client_finalise(GObject *object);
+static gboolean cert_accept_cb(GTlsConnection *conn, GTlsCertificate *peer_cert, GTlsCertificateFlags 
errors, gpointer user_data);
+
+
+NetClient *
+net_client_new(const gchar *host_and_port, guint16 default_port, gsize max_line_len)
+{
+       NetClient *client;
+
+       g_return_val_if_fail((host_and_port != NULL) && (max_line_len > 0U), NULL);
+
+       client = NET_CLIENT(g_object_new(NET_CLIENT_TYPE, NULL));
+
+       if (client->priv->sock == NULL) {
+               g_object_unref(G_OBJECT(client));
+       } else {
+               client->priv->host_and_port = g_strdup(host_and_port);
+               client->priv->default_port = default_port;
+               client->priv->max_line_len = max_line_len;
+       }
+
+       return client;
+}
+
+
+gboolean
+net_client_configure(NetClient *client, const gchar *host_and_port, guint16 default_port, gsize 
max_line_len, GError **error)
+{
+       NetClientPrivate *priv;
+       gboolean result;
+
+       g_return_val_if_fail(NET_IS_CLIENT(client) && (host_and_port != NULL) && (max_line_len > 0U), FALSE);
+
+       priv = client->priv;
+       if (priv->plain_conn != NULL) {
+               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_CONNECTED, _("network 
client is already connected"));
+               result = FALSE;
+       } else {
+               g_free(priv->host_and_port);
+               priv->host_and_port = g_strdup(host_and_port);
+               priv->default_port = default_port;
+               priv->max_line_len = max_line_len;
+               result = TRUE;
+       }
+       return result;
+}
+
+
+const gchar *
+net_client_get_host(NetClient *client)
+{
+       const gchar *result;
+
+       if (NET_IS_CLIENT(client)) {
+               result = client->priv->host_and_port;
+       } else {
+               result = NULL;
+       }
+       return result;
+}
+
+
+gboolean
+net_client_connect(NetClient *client, GError **error)
+{
+       gboolean result = FALSE;
+       NetClientPrivate *priv;
+
+       g_return_val_if_fail(NET_IS_CLIENT(client), FALSE);
+
+       priv = client->priv;
+       if (priv->plain_conn != NULL) {
+               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_CONNECTED, _("network 
client is already connected"));
+       } 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) {
+                       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));
+                       result = TRUE;
+               }
+       }
+
+       return result;
+}
+
+
+gboolean
+net_client_is_encrypted(NetClient *client)
+{
+       gboolean result;
+
+       if (NET_IS_CLIENT(client) && (client->priv->plain_conn != NULL) && (client->priv->tls_conn != NULL)) {
+               result = TRUE;
+       } else {
+               result = FALSE;
+       }
+       return result;
+}
+
+
+gboolean
+net_client_read_line(NetClient *client, gchar **recv_line, GError **error)
+{
+       gboolean result = FALSE;
+
+       g_return_val_if_fail(NET_IS_CLIENT(client), FALSE);
+
+       if (client->priv->istream == NULL) {
+               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_NOT_CONNECTED, _("network 
client is not connected"));
+       } else {
+               gchar *line_buf;
+               gsize length;
+               GError *read_err = NULL;
+
+               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) {
+                               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);
+                       } else {
+                               g_debug("R '%s'", line_buf);
+                               result = TRUE;
+                               if (recv_line != NULL) {
+                                       *recv_line = line_buf;
+                               } else {
+                                       g_free(line_buf);
+                               }
+                       }
+               } else {
+                       if (read_err != NULL) {
+                               g_propagate_error(error, read_err);
+                       } else {
+                               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) 
NET_CLIENT_ERROR_CONNECTION_LOST, _("connection lost"));
+                       }
+               }
+       }
+
+       return result;
+}
+
+
+gboolean
+net_client_write_buffer(NetClient *client, const gchar *buffer, gsize count, GError **error)
+{
+       gboolean result;
+
+       g_return_val_if_fail(NET_IS_CLIENT(client) && (buffer != NULL) && (count > 0UL), FALSE);
+
+       if (client->priv->ostream == NULL) {
+               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_NOT_CONNECTED, _("network 
client is not connected"));
+               result = FALSE;
+       } else {
+               gsize bytes_written;
+
+               if ((count > 2U) && (buffer[count - 1U] == '\n')) {
+                       g_debug("W '%.*s'", (int) count - 2, buffer);
+               } else {
+                       g_debug("W '%.*s'", (int) count, buffer);
+               }
+               result = g_output_stream_write_all(client->priv->ostream, buffer, count, &bytes_written, 
NULL, error);
+               if (result) {
+                       result = g_output_stream_flush(client->priv->ostream, NULL, error);
+               }
+       }
+
+       return result;
+}
+
+
+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;
+
+       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))) {
+               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);
+       }
+
+       return result;
+}
+
+
+gboolean
+net_client_write_line(NetClient *client, const gchar *format, GError **error, ...)
+{
+       va_list args;
+       gboolean result;
+
+       va_start(args, error);
+       result = net_client_vwrite_line(client, format, args, error);
+       va_end(args);
+
+       return result;
+}
+
+
+gboolean
+net_client_execute(NetClient *client, gchar **response, const gchar *request_fmt, GError **error, ...)
+{
+       va_list args;
+       gboolean result;
+
+       va_start(args, error);
+       result = net_client_vwrite_line(client, request_fmt, args, error);
+       va_end(args);
+       if (result) {
+               result = net_client_read_line(client, response, error);
+       }
+
+       return result;
+}
+
+
+/* Note: I have no idea how I can load a PEM with an encrypted key into a GTlsCertificate using 
g_tls_certificate_new_from_file()
+ * or g_tls_certificate_new_from_pem(), as this will always fail with a GnuTLS error "ASN1 parser: Error in 
DER parsing".  There
+ * /might/ be a solution with GTlsInteraction and GTlsPassword, though.  Please enlighten me if you know 
more...
+ *
+ * This function uses "raw" GnuTLS calls and will emit the cert-pass signal if necessary.
+ *
+ * See also <https://mail.gnome.org/archives/gtk-list/2016-May/msg00027.html>.
+ */
+gboolean
+net_client_set_cert_from_pem(NetClient *client, const gchar *pem_data, GError **error)
+{
+       gboolean result = FALSE;
+       gnutls_x509_crt_t cert;
+       int res;
+
+       g_return_val_if_fail(NET_IS_CLIENT(client) && (pem_data != NULL), FALSE);
+
+       /* always free any existing certificate */
+       if (client->priv->certificate != NULL) {
+               g_object_unref(G_OBJECT(client->priv->certificate));
+               client->priv->certificate = NULL;
+       }
+
+       /* load the certificate */
+       res = gnutls_x509_crt_init(&cert);
+       if (res != GNUTLS_E_SUCCESS) {
+               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GNUTLS, _("error 
initializing certificate: %s"),
+                       gnutls_strerror(res));
+       } else {
+               gnutls_datum_t data;
+
+               /*lint -e9005   cast'ing away the const is safe as gnutls treas 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);
+               if (res != GNUTLS_E_SUCCESS) {
+                       g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GNUTLS, _("error 
loading certificate: %s"),
+                               gnutls_strerror(res));
+               } else {
+                       gnutls_x509_privkey_t key;
+
+                       /* try to load the key, emit cert-pass signal if gnutls says the key is encrypted */
+                       res = gnutls_x509_privkey_init(&key);
+                       if (res != GNUTLS_E_SUCCESS) {
+                               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_GNUTLS, 
_("error initializing key: %s"),
+                                       gnutls_strerror(res));
+                       } else {
+                               res = gnutls_x509_privkey_import2(key, &data, GNUTLS_X509_FMT_PEM, NULL, 0);
+                               if (res == GNUTLS_E_DECRYPTION_FAILED) {
+                                       size_t der_size;
+                                       guint8 *der_data;
+
+                                       /* determine cert buffer size requirements */
+                                       der_size = 0U;
+                                       (void) gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, NULL, 
&der_size);
+                                       der_data = g_malloc(der_size);          /*lint !e9079 (MISRA C:2012 
Rule 11.5) */
+
+                                       res = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, der_data, 
&der_size);
+                                       if (res == GNUTLS_E_SUCCESS) {
+                                               GByteArray *cert_der;
+                                               gchar *key_pass = NULL;
+
+                                               cert_der = g_byte_array_new_take(der_data, der_size);
+                                               g_debug("emit 'cert-pass' signal for client %p", client);
+                                               g_signal_emit(client, signals[2], 0, cert_der, &key_pass);
+                                               g_byte_array_unref(cert_der);
+                                               if (key_pass != NULL) {
+                                                       res = gnutls_x509_privkey_import2(key, &data, 
GNUTLS_X509_FMT_PEM, key_pass, 0);
+                                                       memset(key_pass, 0, strlen(key_pass));
+                                                       g_free(key_pass);
+                                               }
+                                       }
+                               }
+
+                               /* on success, set the certificate using the unencrypted key */
+                               if (res == GNUTLS_E_SUCCESS) {
+                                       gchar *pem_buf;
+                                       size_t key_buf_size = 0;
+                                       size_t crt_buf_size = 0;
+
+                                       /* determine buffer size requirements */
+                                       (void) gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, NULL, 
&key_buf_size);
+                                       (void) gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM, NULL, 
&crt_buf_size);
+                                       pem_buf = g_malloc(key_buf_size + crt_buf_size);                
/*lint !e9079 (MISRA C:2012 Rule 11.5) */
+                                       res = gnutls_x509_privkey_export(key, GNUTLS_X509_FMT_PEM, pem_buf, 
&key_buf_size);
+                                       if (res == GNUTLS_E_SUCCESS) {
+                                               res = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_PEM, 
&pem_buf[key_buf_size], &crt_buf_size);
+                                       }
+
+                                       if (res == GNUTLS_E_SUCCESS) {
+                                               client->priv->certificate = 
g_tls_certificate_new_from_pem(pem_buf, -1, error);
+                                               if (client->priv->certificate != NULL) {
+                                                       result = TRUE;
+                                               }
+                                       }
+                                       g_free(pem_buf);
+                               }
+                               gnutls_x509_privkey_deinit(key);
+
+                               if (res != GNUTLS_E_SUCCESS) {
+                                       g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) 
NET_CLIENT_ERROR_GNUTLS, _("error loading key: %s"),
+                                               gnutls_strerror(res));
+                               }
+                       }
+               }
+               gnutls_x509_crt_deinit(cert);
+       }
+
+       return result;
+}
+
+
+gboolean
+net_client_set_cert_from_file(NetClient *client, const gchar *pem_path, GError **error)
+{
+       gboolean result;
+       GFile *pem_file;
+       gchar *pem_buf;
+
+       g_return_val_if_fail(pem_path != NULL, FALSE);
+
+       pem_file = g_file_new_for_path(pem_path);
+       result = g_file_load_contents(pem_file, NULL, &pem_buf, NULL, NULL, error);
+       if (result) {
+               result = net_client_set_cert_from_pem(client, pem_buf, error);
+               g_free(pem_buf);
+       }
+       g_object_unref(G_OBJECT(pem_file));
+       return result;
+}
+
+
+gboolean
+net_client_start_tls(NetClient *client, GError **error)
+{
+       gboolean result = FALSE;
+
+       g_return_val_if_fail(NET_IS_CLIENT(client), FALSE);
+
+       if (client->priv->plain_conn == NULL) {
+               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_NOT_CONNECTED, _("not 
connected"));
+       } else if (client->priv->tls_conn != NULL) {
+               g_set_error(error, NET_CLIENT_ERROR_QUARK, (gint) NET_CLIENT_ERROR_TLS_ACTIVE, _("connection 
is already encrypted"));
+       } else {
+               client->priv->tls_conn = g_tls_client_connection_new(G_IO_STREAM(client->priv->plain_conn), 
NULL, error);
+               if (client->priv->tls_conn != NULL) {
+                       if (client->priv->certificate != NULL) {
+                               g_tls_connection_set_certificate(G_TLS_CONNECTION(client->priv->tls_conn), 
client->priv->certificate);
+                       }
+                       (void) g_signal_connect(G_OBJECT(client->priv->tls_conn), "accept-certificate", 
G_CALLBACK(cert_accept_cb), client);
+                       result = g_tls_connection_handshake(G_TLS_CONNECTION(client->priv->tls_conn), NULL, 
error);
+                       if (result) {
+                               
g_filter_input_stream_set_close_base_stream(G_FILTER_INPUT_STREAM(client->priv->istream), FALSE);
+                               g_object_unref(G_OBJECT(client->priv->istream));                /* unref the 
plain connection's stream */
+                               client->priv->istream = 
g_data_input_stream_new(g_io_stream_get_input_stream(G_IO_STREAM(client->priv->tls_conn)));
+                               g_data_input_stream_set_newline_type(client->priv->istream, 
G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+                               client->priv->ostream = 
g_io_stream_get_output_stream(G_IO_STREAM(client->priv->tls_conn));
+                       } else {
+                               g_object_unref(G_OBJECT(client->priv->tls_conn));
+                               client->priv->tls_conn = NULL;
+                       }
+               }
+       }
+
+       return result;
+}
+
+
+gboolean
+net_client_set_timeout(NetClient *client, guint timeout_secs)
+{
+       g_return_val_if_fail(NET_IS_CLIENT(client), FALSE);
+
+       g_socket_client_set_timeout(client->priv->sock, timeout_secs);
+       return TRUE;
+}
+
+
+/* == local functions 
=========================================================================================================== */
+
+static void
+net_client_class_init(NetClientClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+       g_type_class_add_private(klass, sizeof(NetClientPrivate));
+       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[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);
+}
+
+
+static void
+net_client_init(NetClient *self)
+{
+       self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NET_CLIENT_TYPE, NetClientPrivate);
+       self->priv->sock = g_socket_client_new();
+       if (self->priv->sock != NULL) {
+               g_socket_client_set_timeout(self->priv->sock, 180U);
+       }
+}
+
+
+static void
+net_client_finalise(GObject *object)
+{
+       const NetClient *client = NET_CLIENT(object);
+       const GObjectClass *parent_class = G_OBJECT_CLASS(net_client_parent_class);
+
+       /* note: we must unref the GDataInputStream, but *not* the GOutputStream! */
+       if (client->priv->istream != NULL) {
+               g_object_unref(G_OBJECT(client->priv->istream));
+               client->priv->istream = NULL;
+       }
+       if (client->priv->tls_conn != NULL) {
+               g_object_unref(G_OBJECT(client->priv->tls_conn));
+               client->priv->tls_conn = NULL;
+       }
+       if (client->priv->plain_conn != NULL) {
+               g_object_unref(G_OBJECT(client->priv->plain_conn));
+               client->priv->plain_conn = NULL;
+       }
+       if (client->priv->sock != NULL) {
+               g_object_unref(G_OBJECT(client->priv->sock));
+               client->priv->sock = NULL;
+       }
+       if (client->priv->certificate != NULL) {
+               g_object_unref(G_OBJECT(client->priv->certificate));
+               client->priv->certificate = NULL;
+       }
+       g_free(client->priv->host_and_port);
+       (*parent_class->finalize)(object);
+}
+
+
+static gboolean
+cert_accept_cb(G_GNUC_UNUSED GTlsConnection *conn, GTlsCertificate *peer_cert, GTlsCertificateFlags errors, 
gpointer user_data)
+{
+       NetClient *client = NET_CLIENT(user_data);
+       gboolean result;
+
+       g_debug("emit 'cert-check' signal for client %p", client);
+       g_signal_emit(client, signals[0], 0, peer_cert, errors, &result);
+       return result;
+}
diff --git a/libnetclient/net-client.h b/libnetclient/net-client.h
new file mode 100644
index 0000000..41db07d
--- /dev/null
+++ b/libnetclient/net-client.h
@@ -0,0 +1,305 @@
+/* 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_H_
+#define NET_CLIENT_H_
+
+
+#include <stdarg.h>
+#include <glib-object.h>
+#include <gio/gio.h>
+
+
+G_BEGIN_DECLS
+
+
+#define NET_CLIENT_TYPE                                                (net_client_get_type())
+#define NET_CLIENT(obj)                                                (G_TYPE_CHECK_INSTANCE_CAST((obj), 
NET_CLIENT_TYPE, NetClient))
+#define NET_IS_CLIENT(obj)                                     (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
NET_CLIENT_TYPE))
+#define NET_CLIENT_CLASS(klass)                                (G_TYPE_CHECK_CLASS_CAST((klass), 
NET_CLIENT_TYPE, NetClientClass))
+#define NET_IS_CLIENT_CLASS(klass)                     (G_TYPE_CHECK_CLASS_TYPE((klass), NET_CLIENT_TYPE))
+#define NET_CLIENT_GET_CLASS(obj)                      (G_TYPE_INSTANCE_GET_CLASS((obj), NET_CLIENT_TYPE, 
NetClientClass))
+
+#define NET_CLIENT_ERROR_QUARK                         (g_quark_from_static_string("net-client"))
+
+
+typedef struct _NetClient NetClient;
+typedef struct _NetClientClass NetClientClass;
+typedef struct _NetClientPrivate NetClientPrivate;
+typedef enum _NetClientError NetClientError;
+typedef enum _NetClientCryptMode NetClientCryptMode;
+
+
+struct _NetClient {
+    GObject parent;
+    NetClientPrivate *priv;
+};
+
+
+struct _NetClientClass {
+    GObjectClass parent;
+};
+
+
+/** @brief Encryption mode */
+enum _NetClientCryptMode {
+       NET_CLIENT_CRYPT_ENCRYPTED = 1,                 /**< TLS encryption @em before starting the protocol 
required (e.g. SMTPS). */
+       NET_CLIENT_CRYPT_STARTTLS,                              /**< Protocol-specific STARTTLS encryption 
required. */
+       NET_CLIENT_CRYPT_STARTTLS_OPT,                  /**< Optional protocol-specific STARTTLS encryption, 
proceed unencrypted on fail. */
+       NET_CLIENT_CRYPT_NONE                                   /**< Unencrypted connection. */
+};
+
+
+/** @brief Error codes */
+enum _NetClientError {
+       NET_CLIENT_ERROR_CONNECTED = 1,                 /**< The client is already connected. */
+       NET_CLIENT_ERROR_NOT_CONNECTED,                 /**< The client is not connected. */
+       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. */
+};
+
+
+GType net_client_get_type(void)
+       G_GNUC_CONST;
+
+
+/** @brief Create a new 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
+ * @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
+ * and to free all resources of it.
+ */
+NetClient *net_client_new(const gchar *host_and_port, guint16 default_port, gsize max_line_len);
+
+
+/** @brief Configure a network client
+ *
+ * @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 error filled with error information on error
+ * @return TRUE is the connection was successful, FALSE on error
+ *
+ * Set the remote host and port and the maximum line length to the passed parameters, replacing the previous 
values set by calling
+ * net_client_new().
+ */
+gboolean net_client_configure(NetClient *client, const gchar *host_and_port, guint16 default_port, gsize 
max_line_len,
+                                                         GError **error);
+
+
+/** @brief Get the target host of a network client
+ *
+ * @param client network client
+ * @return the currently set host on success, or NULL on error
+ *
+ * @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);
+
+
+/** @brief Connect a network client
+ *
+ * @param client network client
+ * @param error filled with error information on error
+ * @return TRUE is the connection was successful, FALSE on error
+ *
+ * Try to connect the remote host and TCP port.
+ */
+gboolean net_client_connect(NetClient *client, GError **error);
+
+
+/** @brief Check if a network client is connected
+ *
+ * @param client network client
+ * @return TRUE if the passed network client is connected, FALSE if not
+ */
+gboolean net_client_is_encrypted(NetClient *client);
+
+
+/** @brief Load a certificate and private key from PEM data
+ *
+ * @param client network client
+ * @param pem_data NUL-terminated PEM data buffer
+ * @param error filled with error information on error
+ * @return TRUE is the certificate and private key were loaded, FALSE on error
+ *
+ * Load a client certificate and private key form the passed PEM data.  If the private key is encrypted, the 
signal @ref cert-pass
+ * is emitted.
+ *
+ * Use this function (or net_client_set_cert_from_file()) if the remote server requires a client certificate.
+ */
+gboolean net_client_set_cert_from_pem(NetClient *client, const gchar *pem_data, GError **error);
+
+
+/** @brief Load a certificate and private key from a PEM file
+ *
+ * @param client network client
+ * @param pem_path path name of the file containing the PEM certificate and private key
+ * @param error filled with error information on error
+ * @return TRUE is the certificate and private key were loaded, FALSE on error
+ *
+ * Load a client certificate and private key form the passed PEM file.  If the private key is encrypted, the 
signal @ref cert-pass
+ * is emitted.
+ *
+ * Use this function (or net_client_set_cert_from_pem()) if the remote server requires a client certificate.
+ */
+gboolean net_client_set_cert_from_file(NetClient *client, const gchar *pem_path, GError **error);
+
+
+/** @brief Start encryption
+ *
+ * @param client network client
+ * @param error filled with error information on error
+ * @return TRUE if the connection is now TLS encrypted, FALSE on error
+ *
+ * Try to negotiate TLS encryption.  If the remote server presents an untrusted certificate, the signal @ref 
cert-check is emitted.
+ */
+gboolean net_client_start_tls(NetClient *client, GError **error);
+
+
+/** @brief Read a CRLF-terminated line from a network client
+ *
+ * @param client network client
+ * @param recv_line filled with the response buffer on success, may be NULL to discard the line read
+ * @param error filled with error information on error
+ * @return TRUE is the read operation was successful, FALSE on 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.
+ */
+gboolean net_client_read_line(NetClient *client, gchar **recv_line, GError **error);
+
+
+/** @brief Write data to a network client
+ *
+ * @param client network client
+ * @param buffer data buffer
+ * @param count number of bytes in the data buffer
+ * @param error filled with error information on error
+ * @return TRUE is the send operation was successful, FALSE on error
+ *
+ * Send the complete data buffer to the remote server.  The caller must ensure that the allowed line length 
is not exceeded, and
+ * that the lines are CRLF-terminated.
+ */
+gboolean net_client_write_buffer(NetClient *client, const gchar *buffer, gsize count, GError **error);
+
+
+/** @brief Write a line to a network client
+ *
+ * @param client network client
+ * @param format printf-like format string
+ * @param args argument list for the format
+ * @param error filled with error information on error
+ * @return TRUE is the send operation was successful, FALSE on error
+ *
+ * Format a line according to the passed arguments, append CRLF, and send it to the remote server.
+ */
+gboolean net_client_vwrite_line(NetClient *client, const gchar *format, va_list args, GError **error);
+
+
+/** @brief Write a line with a variable argument list to a network client
+ *
+ * @param client network client
+ * @param format printf-like format string
+ * @param error filled with error information on error
+ * @param ... additional arguments according to the format string
+ * @return TRUE is the send operation was successful, FALSE on error
+ *
+ * Format a line according to the passed arguments, append CRLF, and send it to the remote server.
+ */
+gboolean net_client_write_line(NetClient *client, const gchar *format, GError **error, ...)
+       G_GNUC_PRINTF(2, 4);
+
+
+/** @brief Execute a command-response sequence with a network client
+ *
+ * @param client network client
+ * @param response filled with the response buffer on success, may be NULL to discard the response
+ * @param request_fmt printf-like request format string
+ * @param error filled with error information on error
+ * @param ... additional arguments according to the format string
+ * @return TRUE is the operation was successful, FALSE on error
+ *
+ * This convenience function calls net_client_vwrite_line() to write a one-line command to the remote 
server, and then
+ * net_client_read_line() to read the response.  The terminating CRLF of the response is always stripped.
+ *
+ * @note The caller must free the returned buffer when it is not needed any more.
+ */
+gboolean net_client_execute(NetClient *client, gchar **response, const gchar *request_fmt, GError **error, 
...)
+       G_GNUC_PRINTF(3, 5);
+
+
+/** @brief Set the connection time-out
+ *
+ * @param client network client
+ * @param timeout_secs time-out in seconds
+ * @return TRUE on success, FALSE on error
+ *
+ * @note The default timeout is 180 seconds (3 minutes).
+ */
+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.
+ *
+ * \author Written by Albrecht Dreß mailto:albrecht dress arcor de
+ * \copyright Copyright &copy; Albrecht Dreß 2017<br/>
+ * This library is free software: you can redistribute it and/or modify it under the terms of the GNU 
General Public License as
+ * published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later 
version.<br/>
+ * 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 General Public License for more 
details.<br/>
+ * You should have received a copy of the GNU General Public License along with this library.  If not, see
+ * http://www.gnu.org/licenses/.
+ *
+ * @file
+ *
+ * This module implements the base class for a simple network client communication on the basis of 
CRLF-terminated lines.
+ *
+ * @section signals Signals
+ *
+ * The following signals are implemented:
+ *
+ * - @anchor cert-pass cert-pass
+ *   @code gchar *cert_pass(NetClient *client, GByteArray *peer_cert_der, gpointer user_data) @endcode The 
client certificate used
+ *   for the connection has a password-protected key.  The certificate in DER format is passed to the signal 
handler, and shall
+ *   return a newly allocated string containing the password.  The string is wiped and freed when it is not 
needed any more.
+ * - @anchor cert-check cert-check
+ *   @code gboolean check_cert(NetClient *client, GTlsCertificate *peer_cert, GTlsCertificateFlags errors, 
gpointer user_data)
+ *   @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.
+ */
+
+
+G_END_DECLS
+
+
+#endif /* NET_CLIENT_H_ */
diff --git a/libnetclient/test/Makefile.am b/libnetclient/test/Makefile.am
new file mode 100644
index 0000000..be9caf7
--- /dev/null
+++ b/libnetclient/test/Makefile.am
@@ -0,0 +1,31 @@
+# $Id$
+
+# 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
+
+EXTRA_DIST =           \
+       tests.c                 \
+       inetsim.conf    \
+       ca_cert.pem             \
+       cert.pem                \
+       cert_u.pem              \
+       valgrind.supp
+
+TESTFLAGS = -DNCAT="\"@NCAT@\"" -DSED="\"@SED@\"" -fprofile-arcs -ftest-coverage -g -Wno-error
+
+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
+
+CLEANFILES = *.gcda *.gcno *.covi *.vg tests
+
+clean-local:
+       -rm -rf gcov
+
+tests: tests.c
+       $(CC) $(LIBNETCLIENT_CFLAGS) $(CPPFLAGS) $(TESTFLAGS) -I. -I.. $< $(test_src) -o $@ 
$(LIBNETCLIENT_LIBS)
+       $(VALGRIND) $(VALGRFLAGS) ./$@
+       $(LCOV) $(LCOVFLGS) -c -b $(libsrcdir) -d $(abs_srcdir) --no-external -o $@.covi
+       $(LCOV) $(LCOVFLGS) -r $@.covi $< -o $@.covi
+       $(GENHTML) $(GENHTMLFLGS) -o gcov $@.covi
diff --git a/libnetclient/test/ca_cert.pem b/libnetclient/test/ca_cert.pem
new file mode 100644
index 0000000..bf8945b
--- /dev/null
+++ b/libnetclient/test/ca_cert.pem
@@ -0,0 +1,46 @@
+-----BEGIN CERTIFICATE-----
+MIIDxjCCAq6gAwIBAgIBATANBgkqhkiG9w0BAQUFADB0MRMwEQYKCZImiZPyLGQB
+GRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYDVQQKDApTaW1wbGUg
+SW5jMRcwFQYDVQQLDA5TaW1wbGUgUm9vdCBDQTEXMBUGA1UEAwwOU2ltcGxlIFJv
+b3QgQ0EwHhcNMTYwNTI0MTYxNjU4WhcNMjYwNTI0MTYxNjU4WjB0MRMwEQYKCZIm
+iZPyLGQBGRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYDVQQKDApT
+aW1wbGUgSW5jMRcwFQYDVQQLDA5TaW1wbGUgUm9vdCBDQTEXMBUGA1UEAwwOU2lt
+cGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPn/T4
+w9Ggb9aXhZ5stchfb+gZAnLkYKSZZC/+SryGwToK2HouWCwMUx8Jgg9DeuIjjVEx
+gnuTveVWWWL7QWdIWOrcDYN8XDjS/Rx0brhmDY5q19/CuNPDWMPb5/8SboOmEM4b
+D/S0gsBUI8N7yJ6qYzvtAB5GDBy8LcK+RBp197R5aFlqIUDy5neH2NoA6XU5NcfS
+cHpqs/oJkg0zVksrZ279mGEHJTxtpPVlm7/z153DgxB1XDWUGIbkmzzl8FPrLGWo
+o1gUFs3TfxnYuFl4r2NhwYeVEUcuBYFhJGVbeisV47ynFUV8gHqxaH03DfghaR+6
+WGe4MBmYpM3NTi6jAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBQ+k/H1yqCqwMnESs4nzdqhHI2IkDAfBgNVHSMEGDAW
+gBQ+k/H1yqCqwMnESs4nzdqhHI2IkDANBgkqhkiG9w0BAQUFAAOCAQEAswaE/9XS
+uVbWyY6CsMry1dfTCzEaTe3odrVe2GM3nU4nowAUqJfqR4JAYjqJim37997n6jbZ
+lK168uFwI/mj4QCY72KixbaSpl7I54MUNXtBtb1/kTwXDeCMFO0Iw1f60Kr8QOeM
+d+aktreCqE+Gss3nmJJ6TT7b/X1UHywlBcO0ZX4y0aDlVWgMqOHxAYF4+Ayxq4El
+MH3+aDItPqnYp1WXfIn/8cYKhdbKjPgZWXVtIzmPHPrAbLQT3DxvPEv4MIwmWx24
+/c4mAvM8TStB+m2/tYZfRflPZ133Nw5zbKHaP1OJgqErIAZCUCHU8tVVBnc/uFHm
+sS+pZL8j9zrzhg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDzzCCAregAwIBAgIBAjANBgkqhkiG9w0BAQUFADB0MRMwEQYKCZImiZPyLGQB
+GRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYDVQQKDApTaW1wbGUg
+SW5jMRcwFQYDVQQLDA5TaW1wbGUgUm9vdCBDQTEXMBUGA1UEAwwOU2ltcGxlIFJv
+b3QgQ0EwHhcNMTYwNTI0MTYxOTAyWhcNMjYwNTI0MTYxOTAyWjB6MRMwEQYKCZIm
+iZPyLGQBGRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYDVQQKDApT
+aW1wbGUgSW5jMRowGAYDVQQLDBFTaW1wbGUgU2lnbmluZyBDQTEaMBgGA1UEAwwR
+U2ltcGxlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQDQXD9AyJJwmx7Ng/Jo6dJy8xLJnWMWi8MmHOhqig5GPuOPff/iNRnhdU6xdr2j
+v5BCdmHbosnOxPq3RJ9r9wyQv6PukCkPvD/2bePQV+ObqegTR5VlNVBhPoWLda5Y
+hMnLTX/mVjC0duz2x+UcS/hd3Yd/QdVdFMJ0LJvJ96SplyfOAowTmw4jii/dtkl+
+hTGp8xl9+wyi1c/ZFKhq3efgC5ozh5oyMMJ6iziaxecq/Rehk4KWDxlPQ5usFU6n
+ls1lBuBszJfNR1XGTCzlvBR/TWNu1WiZmpXgizIoSehOgc/27G4EvkEa4PlEp6I6
+r5CQrDebSlzJSkyhFnkMZQT/AgMBAAGjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNV
+HRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTuAfbK98FLKXj/yBvsD5owPDo5ijAf
+BgNVHSMEGDAWgBQ+k/H1yqCqwMnESs4nzdqhHI2IkDANBgkqhkiG9w0BAQUFAAOC
+AQEAZYWaIHK+09tQN7IHJZatoeE/06QM/pNZBe5/er3C95zOgis7jCOC+Au4srLf
++oi8y+i4WPqRQAJKOOIf7nLMQfGqEV1UVfEOiP40I5WmNQDk3E5jFqTRZC42mf1v
+u2XL3FkjSj+LQyLCz80JdYFUn+I6lyvNSkAC2f5IA+aleX628LtyiuLF0T3Vtjul
+95vgaGd8m3Zr3/rGUeTMhMHPEGvTjTcXyOC1Sc85PhChZtGDehhafosJU8vXmVeH
+dCk3xxx2sjBvWxwTM1pdCJ0HzP7vveLhB22b2aPu8dVAiGJVKtWrnVj1whQeC3qr
+IjagpJLKKVbRnz1plUtXsO6YdA==
+-----END CERTIFICATE-----
diff --git a/libnetclient/test/cert.pem b/libnetclient/test/cert.pem
new file mode 100644
index 0000000..a3b1ee6
--- /dev/null
+++ b/libnetclient/test/cert.pem
@@ -0,0 +1,53 @@
+-----BEGIN CERTIFICATE-----
+MIID6TCCAtGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB6MRMwEQYKCZImiZPyLGQB
+GRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYDVQQKDApTaW1wbGUg
+SW5jMRowGAYDVQQLDBFTaW1wbGUgU2lnbmluZyBDQTEaMBgGA1UEAwwRU2ltcGxl
+IFNpZ25pbmcgQ0EwHhcNMTYwNTI0MTYyOTE0WhcNMTgwNTI0MTYyOTE0WjBbMRMw
+EQYKCZImiZPyLGQBGRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYD
+VQQKDApTaW1wbGUgSW5jMRcwFQYDVQQDDA53d3cuc2ltcGxlLm9yZzCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALrxG+kSRCK8qlBNYvEQIEKGe75SC4GB
+s46Sr3lUgpX6S8ynAHGh6Zs4lTVQIul3bFbJVD0pmDD2aDncaUhvdXjGk59To1Ol
+rg1Tx8ZudV8EzE4oU5W/mx4Xw0/F6PAcXvD5cFeA2nEMfY8iryyl+1ZwY8yc/OzV
+cN+LovnUWX2UTGAbQ0+F9u/fPfxl+WmdcwsbR8UHAXmYuyoOz8gy65ffKC4kmJvm
+hgER0TMZd0dKvLQaK5aAdYF9w2ViU0tbd56MjZq18Bq80QZZYMaS/7FAg1LcYEoV
+g3cCD4HoLmkRY3wgWJYPxHUN9NOGhrM3bfXHUwaGAWD68DvZAzJL+OcCAwEAAaOB
+mDCBlTAOBgNVHQ8BAf8EBAMCBaAwCQYDVR0TBAIwADAdBgNVHSUEFjAUBggrBgEF
+BQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFF/0rJNIaKTJMgC0gqAM2M7S0EYMMB8G
+A1UdIwQYMBaAFO4B9sr3wUspeP/IG+wPmjA8OjmKMBkGA1UdEQQSMBCCDnd3dy5z
+aW1wbGUub3JnMA0GCSqGSIb3DQEBBQUAA4IBAQCoUYUfMa312MR6CDpoXAdmijXj
+gJhSQyrtJwN0BuyrTIE3wFvFGCRwrGhFZGfzgo+7J1uFZ2Np2sBFl+hiEfzlS2GO
+Ft4s52hNt8WAF6roWGMatLwQXkZtbkpZ2uz8SeUHYskKCv32Qwxtodm6tC1ihyKo
+i6RLVpIML/AvYYe6RrEQ8xSg0GtC28FXQ7dTd41AQ8AVw+/udTYBSa170Pmd5Cr4
+IZIOa/k00VHgcl4TvPuIwJVyNBX5IR4WyRhKHJV1BKXa3jTDJVokvpefIopcLArj
+UPfERJi6R14hKxkfcT7+4tYqt8LWpk0E1ebS7CvEQe3zwVeR10fCKfC4KRNm
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,AAB517FD5312490C
+
+Z4IvcUkeV2vcvF8AR/jt6EDi4MiMKQXNAswQV1BqJMfotGJxQR/XSnrnh0EYadkz
+wRmOhASu6gnVHv0diC37kE1F9DShrqMeia8pXP52hYje/368qTz73/Y70lVIKujz
+G4Ns/1kB2n9CDAi7Kk/hOSnAzpMNirtkabAnpR2VxgyH44DzwMgxavWCGibvIq8Q
+eyqd7sB6lOsP4/gaGILRCGjsYIgtC1xq6rcRRl7Mk07hxjroTy1rtDjOMzJ/RtYX
+FNDqlk0KJeuH/wJCaJ1ys2lA4QSKLs0P/kAh/nmwuMnOFyz46wMEIOE0JEAlJfB/
+yNv93QAOZiyZ71hWtq2hkI6c8znS49rG+ULiYOEDuhU5s54SEHeA1Oz2vtmu5Wz0
+DBzIlCGKNy+v9BOWkbnpAgA/S1cLNGX2Cf5f1ReRYiaHCz6LCaYXzDbV3x8FvU/s
+lKAuuq2nfq2jDHrbSw4Nni8SVsN6vjUzQieQwux0J3XPYMsxwQwL8Z01bZaeom0L
+odQwE64vGtQWsvmhA20dBYthurVzNK9FKZY1oKd7y2vdplKRUg/24J2/HwlZB/ru
+CN4Yrsky5b+q37OafP7ycSCesR+bQWrfLMyUMyUU/9JqH7eIoEh5+LfSZMqSEDAt
+ERi2UxHepSN/oZK8ECAzbUmcSwKJ/oHI5fGnbaN4hsxbcgwoW33+y4ZGFpmaLXj1
+5P2jY1GEfU6ql9oWrRgSAwizfI1riyK+N3x4ecCRf2dO6rMKrJk7AbA3KnQbT76r
+jZOmbGPpWA+7d8RAclr8cAe5LegETzuzA42VanvFhzMoo5opYcaYsPqlXtrj5fbX
+f02l7raps/r1eoJD7esT6leBlQZjAsd2YiGqio1rQmMK5T7kLK2CjLZTGAeQRDJF
+ZiOlZmqC70n5rBFzCUGKw2Hofns7qKpj5+Je9SBbuNkpyahiRRRUlTrQi9sXuEjL
+cd6XX9jURumyv/3XITeauDh4jUtboi4BrBHbztfJIVrXLJ24bsj0OLgnwGHe8vQR
+MUWFRyYGDNzh16hdDPSni1Z6aqwuxiX7FYXs9ZYuUXIl7Klq9wVDLPOHao9xWAWk
+yeMeNETj8bqQmnfO3XgOdH7uOZdZAMFtySKF0Quh/77SvCsqiXFAapwEdZFvnycO
+VmLiQ7eXG4CZZE9QaDfbpY4JcYA3Fyk9OhACErEAyFyEPUTD4B9wXJ1mQNfaWJQD
+Nv6WpCQawCZLPy4Hohe+fCG4ZLZOrTJjXSSNhWPXLgymNbdsWLdcS/tLKfx4nTAY
+XU9ShGTiDE6J0DY2vNmsNK9Gl1b69oiWUc1DkaDc5VYjRpkqhqd2jzeJ3rS0SHLE
+ccejoQA7DPZZ7LqKs6v79STay05CSGpIsC0sa+8JemmaOSRgC8a73dNu6eZ8gywL
+uUvmSEmYXILJhYoObvlxv2mnjwXUpU+svFskj2QxbjVPOC23YNsbZKNWtCRx+cbJ
+RpGye05q7XmjmIeFO3SdnvQmaKpetIEEULihD9hy25OVOH3VbfgBoHdphhCr5Kom
+LXGbxmYKL/Br4bTVkqczLXTBrEAllhEgCkrFfOJX73SUItW53I/p/Jdu7Xm6+f6m
+-----END RSA PRIVATE KEY-----
diff --git a/libnetclient/test/cert_u.pem b/libnetclient/test/cert_u.pem
new file mode 100644
index 0000000..f5ffd65
--- /dev/null
+++ b/libnetclient/test/cert_u.pem
@@ -0,0 +1,51 @@
+-----BEGIN CERTIFICATE-----
+MIID6TCCAtGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB6MRMwEQYKCZImiZPyLGQB
+GRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYDVQQKDApTaW1wbGUg
+SW5jMRowGAYDVQQLDBFTaW1wbGUgU2lnbmluZyBDQTEaMBgGA1UEAwwRU2ltcGxl
+IFNpZ25pbmcgQ0EwHhcNMTYwNTI0MTYyOTE0WhcNMTgwNTI0MTYyOTE0WjBbMRMw
+EQYKCZImiZPyLGQBGRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGc2ltcGxlMRMwEQYD
+VQQKDApTaW1wbGUgSW5jMRcwFQYDVQQDDA53d3cuc2ltcGxlLm9yZzCCASIwDQYJ
+KoZIhvcNAQEBBQADggEPADCCAQoCggEBALrxG+kSRCK8qlBNYvEQIEKGe75SC4GB
+s46Sr3lUgpX6S8ynAHGh6Zs4lTVQIul3bFbJVD0pmDD2aDncaUhvdXjGk59To1Ol
+rg1Tx8ZudV8EzE4oU5W/mx4Xw0/F6PAcXvD5cFeA2nEMfY8iryyl+1ZwY8yc/OzV
+cN+LovnUWX2UTGAbQ0+F9u/fPfxl+WmdcwsbR8UHAXmYuyoOz8gy65ffKC4kmJvm
+hgER0TMZd0dKvLQaK5aAdYF9w2ViU0tbd56MjZq18Bq80QZZYMaS/7FAg1LcYEoV
+g3cCD4HoLmkRY3wgWJYPxHUN9NOGhrM3bfXHUwaGAWD68DvZAzJL+OcCAwEAAaOB
+mDCBlTAOBgNVHQ8BAf8EBAMCBaAwCQYDVR0TBAIwADAdBgNVHSUEFjAUBggrBgEF
+BQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFF/0rJNIaKTJMgC0gqAM2M7S0EYMMB8G
+A1UdIwQYMBaAFO4B9sr3wUspeP/IG+wPmjA8OjmKMBkGA1UdEQQSMBCCDnd3dy5z
+aW1wbGUub3JnMA0GCSqGSIb3DQEBBQUAA4IBAQCoUYUfMa312MR6CDpoXAdmijXj
+gJhSQyrtJwN0BuyrTIE3wFvFGCRwrGhFZGfzgo+7J1uFZ2Np2sBFl+hiEfzlS2GO
+Ft4s52hNt8WAF6roWGMatLwQXkZtbkpZ2uz8SeUHYskKCv32Qwxtodm6tC1ihyKo
+i6RLVpIML/AvYYe6RrEQ8xSg0GtC28FXQ7dTd41AQ8AVw+/udTYBSa170Pmd5Cr4
+IZIOa/k00VHgcl4TvPuIwJVyNBX5IR4WyRhKHJV1BKXa3jTDJVokvpefIopcLArj
+UPfERJi6R14hKxkfcT7+4tYqt8LWpk0E1ebS7CvEQe3zwVeR10fCKfC4KRNm
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC68RvpEkQivKpQ
+TWLxECBChnu+UguBgbOOkq95VIKV+kvMpwBxoembOJU1UCLpd2xWyVQ9KZgw9mg5
+3GlIb3V4xpOfU6NTpa4NU8fGbnVfBMxOKFOVv5seF8NPxejwHF7w+XBXgNpxDH2P
+Iq8spftWcGPMnPzs1XDfi6L51Fl9lExgG0NPhfbv3z38ZflpnXMLG0fFBwF5mLsq
+Ds/IMuuX3yguJJib5oYBEdEzGXdHSry0GiuWgHWBfcNlYlNLW3eejI2atfAavNEG
+WWDGkv+xQINS3GBKFYN3Ag+B6C5pEWN8IFiWD8R1DfTThoazN231x1MGhgFg+vA7
+2QMyS/jnAgMBAAECggEBAKDmGwas4RYg2loJcUpYfdukUmdJyHtr/faNjBhaw5aA
+erMnXftvx1gBCHN9iL73ObksyaNm5IXn32dFrRxaSnpsN+zfGOlK5Za08HJ66eyh
+fZMbpG10H/dzrng/uDDJynGPz8CQt/KrNHp4+Nrt9Xi9fThEOnFBeEf/sINk4K1y
++M6lBgMVc7hjI9P6NH92yj8is5X2cBTDx521jnAOwdiNqVeu/ndddYisM2NBhfRK
+oV/lnSZABVLykJM+eKCkcviCjcnL9AkcbXMPKPjh5LbB0NOSi/uKtQfvBEcFDsfx
+Q3H0OWXJwMUMrqWuzsAt63gAKExi3O26T3v43gqPbFECgYEA4fiGrgTM6CR+8BbM
+7SFmhBURVCC54iQ6LGhBzC2BEpQazQ1T2jJyuXAm881Lmc2MkR7mzeiQlDrqjrQv
+1Ao5EEedfiR4IF3oUyDb0UPkT8GF7kholvjCXQgWMADrSln3hnKndlsAI7/y4LYR
+ZKxSSAoS8bv0MyrOU6PuRzDqXSkCgYEA08jTj2ewjbscbSS9tj+RSmbpA1GxQP6D
+ZPukfDAc7seZO24c1tYRFE3+qB5qMzB/sOWveoIc8Ii5LTa0VofMMEiuRKoaedVq
+z8NixkjmRqQ/YQKmJaEUjaXw0tPr/sENnxmRKINTdPGyTQewaX+Xi//wTeaM89ZR
+E76UDfoRV48CgYEAzi0IFTbkDttdxonHKIVVGCZwzSWe9KrBOUtW3YiXP/BKE9WZ
+MHjfHDssDz69P/O/0Zk0VaNgZx7qbJITIZDCSAxPsoxr3nxQQai1Z9ZeNjcIkEUY
+yMbn8CI0vE/aXth/c/iguuiE1BmK7WSxsf1YhcpLRqyLPzRLVR05h1MmQyECgYAz
++P3KHIUcUwXH1xNjbTgnjRezw9F/BLaUCpk0DM6c3ojBJ+WV51SrqfRLp6MiSs2b
+SNKGevXFJZhj+x/IeGzokH2Lxj7XFUOwFt/fjzZLsbUIBpFlfKFBna563pz0mMXR
+/IE98vSBJ2s9Mhzd1v0G01lSlLiUgkzFTqigwXSOCwKBgQCGSDpRZxuL7xfO0++S
+jJxBdGCX/20vVJ3jdvzO2BtmMr87gIgHNA38TG1u5TWcYgvtqzS8kZcKsKJiLkLR
+8qlPYpyCjD+4sOm2Yg7LgH/O0MbEm1RMTpZQ/XVk98afFoXqM9O17EyYL4GIDbVO
+4+Pa9RSnfdHcOTPl4DjTkePpZQ==
+-----END PRIVATE KEY-----
diff --git a/libnetclient/test/inetsim.conf b/libnetclient/test/inetsim.conf
new file mode 100644
index 0000000..d3ab91b
--- /dev/null
+++ b/libnetclient/test/inetsim.conf
@@ -0,0 +1,1932 @@
+#############################################################
+#
+# INetSim configuration file
+#
+#############################################################
+
+
+#############################################################
+# Main configuration
+#############################################################
+
+#########################################
+# start_service
+#
+# The services to start
+#
+# Syntax: start_service <service name>
+#
+# Default: none
+#
+# Available service names are:
+# dns, http, smtp, pop3, tftp, ftp, ntp, time_tcp,
+# time_udp, daytime_tcp, daytime_udp, echo_tcp,
+# echo_udp, discard_tcp, discard_udp, quotd_tcp,
+# quotd_udp, chargen_tcp, chargen_udp, finger,
+# ident, syslog, dummy_tcp, dummy_udp, smtps, pop3s,
+# ftps, irc, https
+#
+# start_service dns
+# start_service http
+# start_service https
+start_service smtp
+start_service smtps
+start_service pop3
+start_service pop3s
+# start_service ftp
+# start_service ftps
+# start_service tftp
+# start_service irc
+# start_service ntp
+# start_service finger
+# start_service ident
+# start_service syslog
+# start_service time_tcp
+# start_service time_udp
+# start_service daytime_tcp
+# start_service daytime_udp
+# start_service echo_tcp
+# start_service echo_udp
+# start_service discard_tcp
+# start_service discard_udp
+# start_service quotd_tcp
+# start_service quotd_udp
+# start_service chargen_tcp
+# start_service chargen_udp
+# start_service dummy_tcp
+# start_service dummy_udp
+
+
+#########################################
+# service_bind_address
+#
+# IP address to bind services to
+#
+# Syntax: service_bind_address <IP address>
+#
+# Default: 127.0.0.1
+#
+#service_bind_address  10.10.10.1
+
+
+#########################################
+# service_run_as_user
+#
+# User to run services
+#
+# Syntax: service_run_as_user <username>
+#
+# Default: inetsim
+#
+#service_run_as_user   nobody
+
+
+#########################################
+# service_max_childs
+#
+# Maximum number of child processes (parallel connections)
+# for each service
+#
+# Syntax: service_max_childs [1..30]
+#
+# Default: 10
+#
+#service_max_childs    15
+
+
+#########################################
+# service_timeout
+#
+# If a client does not send any data for the number of seconds
+# given here, the corresponding connection will be closed.
+#
+# Syntax: service_timeout [1..600]
+#
+# Default: 120
+#
+#service_timeout       60
+
+
+#########################################
+# create_reports
+#
+# Create report with a summary of connections
+# for the session on shutdown
+#
+# Syntax: create_reports [yes|no]
+#
+# Default: yes
+#
+#create_reports                no
+
+
+#########################################
+# report_language
+#
+# Set language for reports
+# Note: Currently only languages 'en' and 'de' are supported
+#
+# Syntax: report_language <language>
+#
+# Default: en
+#
+#report_language       de
+
+
+#############################################################
+# Faketime
+#############################################################
+
+#########################################
+# faketime_init_delta
+#
+# Initial number of seconds (positive or negative)
+# relative to current date/time for fake time used by all services 
+#
+# Syntax: faketime_init_delta <number of seconds>
+#
+# Default: 0  (use current date/time) 
+#
+#faketime_init_delta   1000
+
+
+#########################################
+# faketime_auto_delay
+#
+# Number of seconds to wait before incrementing fake time
+# by value specified with 'faketime_auto_increment'.
+# Setting to '0' disables this option.
+#
+# Syntax: faketime_auto_delay [0..86400]
+#
+# Default: 0  (disabled)
+#
+#faketime_auto_delay   1000
+
+
+#########################################
+# faketime_auto_increment
+#
+# Number of seconds by which fake time is incremented at
+# regular intervals specified by 'faketime_auto_delay'.
+# This option only takes effect if 'faketime_auto_delay'
+# is enabled (not set to '0').
+#
+# Syntax: faketime_auto_increment [-31536000..31536000]
+#
+# Default: 3600
+#
+#faketime_auto_increment       86400
+
+
+#############################################################
+# Service DNS
+#############################################################
+
+#########################################
+# dns_bind_port
+#
+# Port number to bind DNS service to
+#
+# Syntax: dns_bind_port <port number>
+#
+# Default: 53
+#
+#dns_bind_port         53
+
+
+#########################################
+# dns_default_ip
+#
+# Default IP address to return with DNS replies
+#
+# Syntax: dns_default_ip <IP address>
+#
+# Default: 127.0.0.1
+#
+#dns_default_ip                10.10.10.1
+
+
+#########################################
+# dns_default_hostname
+#
+# Default hostname to return with DNS replies
+#
+# Syntax: dns_default_hostname <hostname>
+#
+# Default: www
+#
+#dns_default_hostname          somehost
+
+
+#########################################
+# dns_default_domainname
+#
+# Default domain name to return with DNS replies
+#
+# Syntax: dns_default_domainname <domain name>
+#
+# Default: inetsim.org
+#
+#dns_default_domainname                some.domain
+
+
+#########################################
+# dns_static
+#
+# Static mappings for DNS
+#
+# Syntax: dns_static <fqdn hostname> <IP address>
+#
+# Default: none
+#
+#dns_static    www.foo.com     10.10.10.10
+#dns_static    ns1.foo.com     10.70.50.30
+#dns_static    ftp.bar.net     10.10.20.30
+
+
+#########################################
+# dns_version
+#
+# DNS version
+#
+# Syntax: dns_version <version>
+#
+# Default: "INetSim DNS Server"
+#
+#dns_version "9.2.4"
+
+
+#############################################################
+# Service HTTP
+#############################################################
+
+#########################################
+# http_bind_port
+#
+# Port number to bind HTTP service to
+#
+# Syntax: http_bind_port <port number>
+#
+# Default: 80
+#
+#http_bind_port                80
+
+
+#########################################
+# http_version
+#
+# Version string to return in HTTP replies
+#
+# Syntax: http_version <string>
+#
+# Default: "INetSim HTTP server"
+#
+#http_version          "Microsoft-IIS/4.0"
+
+
+#########################################
+# http_fakemode
+#
+# Turn HTTP fake mode on or off
+#
+# Syntax: http_fakemode [yes|no]
+#
+# Default: yes
+#
+#http_fakemode         no
+
+
+#########################################
+# http_fakefile
+#
+# Fake files returned in fake mode based on the file extension
+# in the HTTP request.
+# The fake files must be placed in <data-dir>/http/fakefiles
+#
+# Syntax: http_fakefile <extension> <filename> <mime-type>
+#
+# Default: none
+#
+http_fakefile          txt     sample.txt      text/plain
+http_fakefile          htm     sample.html     text/html
+http_fakefile          html    sample.html     text/html
+http_fakefile          php     sample.html     text/html
+http_fakefile          gif     sample.gif      image/gif
+http_fakefile          jpg     sample.jpg      image/jpeg
+http_fakefile          jpeg    sample.jpg      image/jpeg
+http_fakefile          png     sample.png      image/png
+http_fakefile          bmp     sample.bmp      image/x-ms-bmp
+http_fakefile          ico     favicon.ico     image/x-icon
+http_fakefile          exe     sample_gui.exe  x-msdos-program
+http_fakefile          com     sample_gui.exe  x-msdos-program
+
+
+#########################################
+# http_default_fakefile
+#
+# The default fake file returned in fake mode if the file extension
+# in the HTTP request does not match any of the extensions
+# defined above.
+#
+# The default fake file must be placed in <data-dir>/http/fakefiles
+#
+# Syntax: http_default_fakefile <filename> <mime-type>
+#
+# Default: none
+#
+http_default_fakefile  sample.html     text/html
+
+
+#########################################
+# http_static_fakefile
+#
+# Fake files returned in fake mode based on static path.
+# The fake files must be placed in <data-dir>/http/fakefiles
+#
+# Syntax: http_static_fakefile <path> <filename> <mime-type>
+#
+# Default: none
+#
+#http_static_fakefile  /path/                  sample_gui.exe  x-msdos-program
+#http_static_fakefile  /path/to/file.exe       sample_gui.exe  x-msdos-program
+
+
+#############################################################
+# Service HTTPS
+#############################################################
+
+#########################################
+# https_bind_port
+#
+# Port number to bind HTTPS service to
+#
+# Syntax: https_bind_port <port number>
+#
+# Default: 443
+#
+#https_bind_port               443
+
+
+#########################################
+# https_version
+#
+# Version string to return in HTTPS replies
+#
+# Syntax: https_version <string>
+#
+# Default: "INetSim HTTPs server"
+#
+#https_version         "Microsoft-IIS/4.0"
+
+
+#########################################
+# https_fakemode
+#
+# Turn HTTPS fake mode on or off
+#
+# Syntax: https_fakemode [yes|no]
+#
+# Default: yes
+#
+#https_fakemode                no
+
+
+#########################################
+# https_fakefile
+#
+# Fake files returned in fake mode based on the file extension
+# in the HTTPS request.
+# The fake files must be placed in <data-dir>/http/fakefiles
+#
+# Syntax: https_fakefile <extension> <filename> <mime-type>
+#
+# Default: none
+#
+https_fakefile         txt     sample.txt      text/plain
+https_fakefile         htm     sample.html     text/html
+https_fakefile         html    sample.html     text/html
+https_fakefile         php     sample.html     text/html
+https_fakefile         gif     sample.gif      image/gif
+https_fakefile         jpg     sample.jpg      image/jpeg
+https_fakefile         jpeg    sample.jpg      image/jpeg
+https_fakefile         png     sample.png      image/png
+https_fakefile         bmp     sample.bmp      image/x-ms-bmp
+https_fakefile         ico     favicon.ico     image/x-icon
+https_fakefile         exe     sample_gui.exe  x-msdos-program
+https_fakefile         com     sample_gui.exe  x-msdos-program
+
+
+#########################################
+# https_default_fakefile
+#
+# The default fake file returned in fake mode if the file extension
+# in the HTTPS request does not match any of the extensions
+# defined above.
+#
+# The default fake file must be placed in <data-dir>/http/fakefiles
+#
+# Syntax: https_default_fakefile <filename> <mime-type>
+#
+# Default: none
+#
+https_default_fakefile sample.html     text/html
+
+
+#########################################
+# https_static_fakefile
+#
+# Fake files returned in fake mode based on static path.
+# The fake files must be placed in <data-dir>/http/fakefiles
+#
+# Syntax: https_static_fakefile <path> <filename> <mime-type>
+#
+# Default: none
+#
+#https_static_fakefile /path/                  sample_gui.exe  x-msdos-program
+#https_static_fakefile /path/to/file.exe       sample_gui.exe  x-msdos-program
+
+
+#########################################
+# https_ssl_keyfile
+#
+# Name of the SSL private key PEM file.
+# The key MUST NOT be encrypted!
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: https_ssl_keyfile <filename>
+#
+# Default: default_key.pem
+#
+#https_ssl_keyfile     https_key.pem
+
+
+#########################################
+# https_ssl_certfile
+#
+# Name of the SSL certificate file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: https_ssl_certfile <filename>
+#
+# Default: default_cert.pem
+#
+#https_ssl_certfile    https_cert.pem
+
+
+#########################################
+# https_ssl_dhfile
+#
+# Name of the Diffie-Hellman parameter PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: https_ssl_dhfile <filename>
+#
+# Default: none
+#
+#https_ssl_dhfile      https_dh1024.pem
+
+
+#############################################################
+# Service SMTP
+#############################################################
+
+#########################################
+# smtp_bind_port
+#
+# Port number to bind SMTP service to
+#
+# Syntax: smtp_bind_port <port number>
+#
+# Default: 25
+#
+smtp_bind_port         65025
+
+
+#########################################
+# smtp_fqdn_hostname
+#
+# The FQDN hostname used for SMTP
+#
+# Syntax: smtp_fqdn_hostname <string>
+#
+# Default: mail.inetsim.org
+#
+#smtp_fqdn_hostname    foo.bar.org
+
+
+#########################################
+# smtp_banner
+#
+# The banner string used in SMTP greeting message
+#
+# Syntax: smtp_banner <string>
+#
+# Default: "INetSim Mail Service ready."
+#
+#smtp_banner           "SMTP Mailer ready."
+
+
+#########################################
+# smtp_helo_required
+#
+# Client has to send HELO/EHLO before any other command
+#
+# Syntax: smtp_helo_required [yes|no]
+#
+# Default: no
+#
+smtp_helo_required     yes
+
+
+#########################################
+# smtp_extended_smtp
+#
+# Turn support for extended smtp (ESMTP) on or off
+#
+# Syntax: smtp_extended_smtp [yes|no]
+#
+# Default: yes
+#
+#smtp_extended_smtp    no
+
+
+#########################################
+# smtp_service_extension
+#
+# SMTP service extensions offered to client.
+# For more information, see
+# <http://www.iana.org/assignments/mail-parameters>
+#
+# Syntax: smtp_service_extension <extension [parameter(s)]>
+#
+# Supported extensions and parameters:
+# VRFY
+# EXPN
+# HELP
+# 8BITMIME
+# SIZE                 # one optional parameter
+# ENHANCEDSTATUSCODES
+# AUTH                         # one or more of [PLAIN LOGIN ANONYMOUS CRAM-MD5 CRAM-SHA1]
+# DSN
+# SEND
+# SAML
+# SOML
+# TURN
+# ETRN
+# ATRN
+# VERP
+# MTRK
+# CHUNKING
+# STARTTLS
+# DELIVERBY            # one optional parameter
+# SUBMITTER
+# CHECKPOINT
+# BINARYMIME
+# NO-SOLICITING                # one optional parameter
+# FUTURERELEASE                # two required parameters
+#
+# Default: none
+#
+smtp_service_extension         VRFY
+smtp_service_extension         EXPN
+smtp_service_extension         HELP
+smtp_service_extension         8BITMIME
+smtp_service_extension         SIZE 102400000
+smtp_service_extension         ENHANCEDSTATUSCODES
+smtp_service_extension         AUTH PLAIN LOGIN CRAM-MD5 CRAM-SHA1
+smtp_service_extension         DSN
+smtp_service_extension         ETRN
+smtp_service_extension         STARTTLS
+#
+
+
+#########################################
+# smtp_auth_reversibleonly
+#
+# Only offer authentication mechanisms which allow reversing
+# the authentication information sent by a client
+# to clear text username/password.
+# This option only takes effect if 'smtp_extended_smtp' is
+# enabled and 'smtp_service_extension AUTH' is configured.
+#
+# Syntax: smtp_auth_reversibleonly [yes|no]
+#
+# Default: no
+#
+#smtp_auth_reversibleonly      yes
+
+
+#########################################
+# smtp_auth_required
+#
+# Force the client to authenticate.
+# This option only takes effect if 'smtp_extended_smtp' is
+# enabled and 'smtp_service_extension AUTH' is configured.
+#
+# Syntax: smtp_auth_required [yes|no]
+#
+# Default: no
+#
+smtp_auth_required     yes
+
+
+#########################################
+# smtp_ssl_keyfile
+#
+# Name of the SSL private key PEM file.
+# The key MUST NOT be encrypted!
+#
+# This option only takes effect if 'smtp_extended_smtp' is
+# enabled and 'smtp_service_extension STARTTLS' is configured.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Note: If no key file is specified, the extension STARTTLS
+# will be disabled.
+#
+# Syntax: smtp_ssl_keyfile <filename>
+#
+# Default: default_key.pem
+#
+#smtp_ssl_keyfile      default_key.pem
+
+
+#########################################
+# smtp_ssl_certfile
+#
+# Name of the SSL certificate PEM file.
+#
+# This option only takes effect if 'smtp_extended_smtp' is
+# enabled and 'smtp_service_extension STARTTLS' is configured.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Note: If no cert file is specified, the extension STARTTLS
+# will be disabled.
+#
+# Syntax: smtp_ssl_certfile <filename>
+#
+# Default: default_cert.pem
+#
+#smtp_ssl_certfile     default_cert.pem
+
+
+#########################################
+# smtp_ssl_dhfile
+#
+# Name of the Diffie-Hellman parameter PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: smtp_ssl_dhfile <filename>
+#
+# Default: none
+#
+#smtp_ssl_dhfile       smtp_dh1024.pem
+
+
+
+#############################################################
+# Service SMTPS
+#############################################################
+
+#########################################
+# smtps_bind_port
+#
+# Port number to bind SMTPS service to
+#
+# Syntax: smtps_bind_port <port number>
+#
+# Default: 465
+#
+smtps_bind_port        65465
+
+
+#########################################
+# smtps_fqdn_hostname
+#
+# The FQDN hostname used for SMTPS
+#
+# Syntax: smtps_fqdn_hostname <string>
+#
+# Default: mail.inetsim.org
+#
+#smtps_fqdn_hostname   foo.bar.org
+
+
+#########################################
+# smtps_banner
+#
+# The banner string used in SMTPS greeting message
+#
+# Syntax: smtps_banner <string>
+#
+# Default: "INetSim Mail Service ready."
+#
+#smtps_banner          "SMTPS Mailer ready."
+
+
+#########################################
+# smtps_helo_required
+#
+# Client has to send HELO/EHLO before any other command
+#
+# Syntax: smtps_helo_required [yes|no]
+#
+# Default: no
+#
+smtps_helo_required    yes
+
+
+#########################################
+# smtps_extended_smtp
+#
+# Turn support for extended smtp (ESMTP) on or off
+#
+# Syntax: smtps_extended_smtp [yes|no]
+#
+# Default: yes
+#
+#smtps_extended_smtp   no
+
+
+#########################################
+# smtps_service_extension
+#
+# SMTP service extensions offered to client.
+# For more information, see
+# <http://www.iana.org/assignments/mail-parameters>
+#
+# Syntax: smtp_service_extension <extension [parameter(s)]>
+#
+# Supported extensions and parameters:
+# VRFY
+# EXPN
+# HELP
+# 8BITMIME
+# SIZE                 # one optional parameter
+# ENHANCEDSTATUSCODES
+# AUTH                         # one or more of [PLAIN LOGIN ANONYMOUS CRAM-MD5 CRAM-SHA1]
+# DSN
+# SEND
+# SAML
+# SOML
+# TURN
+# ETRN
+# ATRN
+# VERP
+# MTRK
+# CHUNKING
+# DELIVERBY            # one optional parameter
+# SUBMITTER
+# CHECKPOINT
+# BINARYMIME
+# NO-SOLICITING                # one optional parameter
+# FUTURERELEASE                # two required parameters
+#
+# Default: none
+#
+smtps_service_extension                VRFY
+smtps_service_extension                EXPN
+smtps_service_extension                HELP
+smtps_service_extension                8BITMIME
+smtps_service_extension                SIZE 102400000
+smtps_service_extension                ENHANCEDSTATUSCODES
+smtps_service_extension                AUTH PLAIN LOGIN CRAM-MD5 CRAM-SHA1
+smtps_service_extension                DSN
+smtps_service_extension                ETRN
+#
+
+
+#########################################
+# smtps_auth_reversibleonly
+#
+# Only offer authentication mechanisms which allow reversing
+# the authentication information sent by a client
+# to clear text username/password.
+# This option only takes effect if 'smtps_extended_smtp' is
+# enabled and 'smtps_service_extension AUTH' is configured.
+#
+# Syntax: smtps_auth_reversibleonly [yes|no]
+#
+# Default: no
+#
+#smtps_auth_reversibleonly     yes
+
+
+#########################################
+# smtps_auth_required
+#
+# Force the client to authenticate.
+# This option only takes effect if 'smtps_extended_smtp' is
+# enabled and 'smtp_service_extension AUTH' is configured.
+#
+# Syntax: smtps_auth_required [yes|no]
+#
+# Default: no
+#
+smtps_auth_required    yes
+
+
+#########################################
+# smtps_ssl_keyfile
+#
+# Name of the SSL private key PEM file.
+# The key MUST NOT be encrypted!
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: smtps_ssl_keyfile <filename>
+#
+# Default: default_key.pem
+#
+#smtps_ssl_keyfile     default_key.pem
+
+
+#########################################
+# smtps_ssl_certfile
+#
+# Name of the SSL certificate PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: smtps_ssl_certfile <filename>
+#
+# Default: default_cert.pem
+#
+#smtps_ssl_certfile    default_cert.pem
+
+
+#########################################
+# smtps_ssl_dhfile
+#
+# Name of the Diffie-Hellman parameter PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: smtps_ssl_dhfile <filename>
+#
+# Default: none
+#
+#smtps_ssl_dhfile      smtps_dh1024.pem
+
+
+#############################################################
+# Service POP3
+#############################################################
+
+#########################################
+# pop3_bind_port
+#
+# Port number to bind POP3 service to
+#
+# Syntax: pop3_bind_port <port number>
+#
+# Default: 110
+#
+#pop3_bind_port                110
+
+
+#########################################
+# pop3_banner
+#
+# The banner string used in POP3 greeting message
+#
+# Syntax: pop3_banner <string>
+#
+# Default: "INetSim POP3 Server ready"
+#
+#pop3_banner           "POP3 Server ready"
+
+
+#########################################
+# pop3_hostname
+#
+# The hostname used in POP3 greeting message
+#
+# Syntax: pop3_hostname <string>
+#
+# Default: pop3host
+#
+#pop3_hostname         pop3server
+
+
+#########################################
+# pop3_mbox_maxmails
+#
+# Maximum number of e-mails to select from supplied mbox files
+# for creation of random POP3 mailbox
+#
+# Syntax: pop3_mbox_maxmails <number>
+#
+# Default: 10
+#
+#pop3_mbox_maxmails    20
+
+
+#########################################
+# pop3_mbox_reread
+#
+# Re-read supplied mbox files if POP3 service was inactive
+# for <number> seconds
+#
+# Syntax: pop3_mbox_reread <number>
+#
+# Default: 180
+#
+#pop3_mbox_reread      300
+
+
+#########################################
+# pop3_mbox_rebuild
+#
+# Rebuild random POP3 mailbox if POP3 service was inactive
+# for <number> seconds
+#
+# Syntax: pop3_mbox_rebuild <number>
+#
+# Default: 60
+#
+#pop3_mbox_rebuild     120
+
+
+#########################################
+# pop3_enable_apop
+#
+# Turn APOP on or off
+#
+# Syntax: pop3_enable_apop [yes|no]
+#
+# Default: yes
+#
+#pop3_enable_apop      no
+
+
+#########################################
+# pop3_auth_reversibleonly
+#
+# Only offer authentication mechanisms which allow reversing
+# the authentication information sent by a client
+# to clear text username/password
+#
+# Syntax: pop3_auth_reversibleonly [yes|no]
+#
+# Default: no
+#
+#pop3_auth_reversibleonly      yes
+
+
+#########################################
+# pop3_enable_capabilities
+#
+# Turn support for pop3 capabilities on or off
+#
+# Syntax: pop3_enable_capabilities [yes|no]
+#
+# Default: yes
+#
+#pop3_enable_capabilities      no
+
+
+#########################################
+# pop3_capability
+#
+# POP3 capabilities offered to client.
+# For more information, see
+# <http://www.iana.org/assignments/pop3-extension-mechanism>
+#
+# Syntax: pop3_capability <capability [parameter(s)]>
+#
+# Supported capabilities and parameters:
+# TOP
+# USER
+# UIDL
+# SASL                 # one or more of [PLAIN LOGIN ANONYMOUS CRAM-MD5 CRAM-SHA1]
+# RESP-CODES
+# EXPIRE               # one required parameter and one optional parameter
+# LOGIN-DELAY          # one required parameter and one optional parameter
+# IMPLEMENTATION       # one required parameter
+# AUTH-RESP-CODE
+# STLS
+#
+# Default: none
+#
+pop3_capability                TOP
+pop3_capability                USER
+pop3_capability                SASL PLAIN LOGIN ANONYMOUS CRAM-MD5 CRAM-SHA1
+pop3_capability                UIDL
+pop3_capability                IMPLEMENTATION "INetSim POP3 server"
+pop3_capability                STLS
+#
+
+
+#########################################
+# pop3_ssl_keyfile
+#
+# Name of the SSL private key PEM file.
+# The key MUST NOT be encrypted!
+#
+# This option only takes effect if 'pop3_enable_capabilities' is
+# true and 'pop3_capability STLS' is configured.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Note: If no key file is specified, capability STLS will be disabled.
+#
+# Syntax: pop3_ssl_keyfile <filename>
+#
+# Default: default_key.pem
+#
+#pop3_ssl_keyfile      pop3_key.pem
+
+
+#########################################
+# pop3_ssl_certfile
+#
+# Name of the SSL certificate PEM file.
+#
+# This option only takes effect if 'pop3_enable_capabilities' is
+# true and 'pop3_capability STLS' is configured.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Note: If no cert file is specified, capability STLS will be disabled.
+#
+# Syntax: pop3_ssl_certfile <filename>
+#
+# Default: default_cert.pem
+#
+#pop3_ssl_certfile     pop3_cert.pem
+
+
+#########################################
+# pop3_ssl_dhfile
+#
+# Name of the Diffie-Hellman parameter PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: pop3_ssl_dhfile <filename>
+#
+# Default: none
+#
+#pop3_ssl_dhfile       pop3_dh1024.pem
+
+
+#############################################################
+# Service POP3S
+#############################################################
+
+#########################################
+# pop3s_bind_port
+#
+# Port number to bind POP3S service to
+#
+# Syntax: pop3s_bind_port <port number>
+#
+# Default: 995
+#
+#pop3s_bind_port               995
+
+
+#########################################
+# pop3s_banner
+#
+# The banner string used in POP3 greeting message
+#
+# Syntax: pop3s_banner <string>
+#
+# Default: "INetSim POP3 Server ready"
+#
+#pop3s_banner          "POP3 Server ready"
+
+
+#########################################
+# pop3s_hostname
+#
+# The hostname used in POP3 greeting message
+#
+# Syntax: pop3s_hostname <string>
+#
+# Default: pop3host
+#
+#pop3s_hostname                pop3server
+
+
+#########################################
+# pop3s_mbox_maxmails
+#
+# Maximum number of e-mails to select from supplied mbox files
+# for creation of random POP3 mailbox
+#
+# Syntax: pop3s_mbox_maxmails <number>
+#
+# Default: 10
+#
+#pop3s_mbox_maxmails   20
+
+
+#########################################
+# pop3s_mbox_reread
+#
+# Re-read supplied mbox files if POP3S service was inactive
+# for <number> seconds
+#
+# Syntax: pop3s_mbox_reread <number>
+#
+# Default: 180
+#
+#pop3s_mbox_reread     300
+
+
+#########################################
+# pop3s_mbox_rebuild
+#
+# Rebuild random POP3 mailbox if POP3S service was inactive
+# for <number> seconds
+#
+# Syntax: pop3s_mbox_rebuild <number>
+#
+# Default: 60
+#
+#pop3s_mbox_rebuild    120
+
+
+#########################################
+# pop3s_enable_apop
+#
+# Turn APOP on or off
+#
+# Syntax: pop3s_enable_apop [yes|no]
+#
+# Default: yes
+#
+#pop3s_enable_apop     no
+
+
+#########################################
+# pop3s_auth_reversibleonly
+#
+# Only offer authentication mechanisms which allow reversing
+# the authentication information sent by a client
+# to clear text username/password
+#
+# Syntax: pop3s_auth_reversibleonly [yes|no]
+#
+# Default: no
+#
+#pop3s_auth_reversibleonly     yes
+
+
+#########################################
+# pop3s_enable_capabilities
+#
+# Turn support for pop3 capabilities on or off
+#
+# Syntax: pop3s_enable_capabilities [yes|no]
+#
+# Default: yes
+#
+#pop3s_enable_capabilities     no
+
+
+#########################################
+# pop3s_capability
+#
+# POP3 capabilities offered to client.
+# For more information, see
+# <http://www.iana.org/assignments/pop3-extension-mechanism>
+#
+# Syntax: pop3s_capability <capability [parameter(s)]>
+#
+# Supported capabilities and parameters:
+# TOP
+# USER
+# UIDL
+# SASL                 # one or more of [PLAIN LOGIN ANONYMOUS CRAM-MD5 CRAM-SHA1]
+# RESP-CODES
+# EXPIRE               # one required parameter and one optional parameter
+# LOGIN-DELAY          # one required parameter and one optional parameter
+# IMPLEMENTATION       # one required parameter
+# AUTH-RESP-CODE
+#
+# Default: none
+#
+pop3s_capability       TOP
+pop3s_capability       USER
+pop3s_capability       SASL PLAIN LOGIN ANONYMOUS CRAM-MD5 CRAM-SHA1
+pop3s_capability       UIDL
+pop3s_capability       IMPLEMENTATION "INetSim POP3s server"
+#
+
+
+#########################################
+# pop3s_ssl_keyfile
+#
+# Name of the SSL private key PEM file.
+# The key MUST NOT be encrypted!
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: pop3s_ssl_keyfile <filename>
+#
+# Default: default_key.pem
+#
+#pop3s_ssl_keyfile     pop3s_key.pem
+
+
+#########################################
+# pop3s_ssl_certfile
+#
+# Name of the SSL certificate PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: pop3s_ssl_certfile <filename>
+#
+# Default: default_cert.pem
+#
+#pop3s_ssl_certfile    pop3s_cert.pem
+
+
+#########################################
+# pop3s_ssl_dhfile
+#
+# Name of the Diffie-Hellman parameter PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: pop3s_ssl_dhfile <filename>
+#
+# Default: none
+#
+#pop3s_ssl_dhfile      pop3s_dh1024.pem
+
+
+#############################################################
+# Service TFTP
+#############################################################
+
+#########################################
+# tftp_bind_port
+#
+# Port number to bind TFTP service to
+#
+# Syntax: tftp_bind_port <port number>
+#
+# Default: 69
+#
+#tftp_bind_port                69
+
+
+#########################################
+# tftp_allow_overwrite
+#
+# Allow overwriting of existing files
+#
+# Syntax: tftp_allow_overwrite [yes|no]
+#
+# Default: no
+#
+#tftp_allow_overwrite  yes
+
+
+#########################################
+# tftp_enable_options
+#
+# Turn support for tftp options on or off
+#
+# Syntax: tftp_enable_options [yes|no]
+#
+# Default: yes
+#
+#tftp_enable_options   no
+
+
+#########################################
+# tftp_option
+#
+# TFTP extensions offered to client.
+# For more information, see RFC 2347
+#
+# Syntax: tftp_option <option [parameter(s)]>
+#
+# Supported extensions and parameters:
+# BLKSIZE              # two optional parameters
+# TIMEOUT              # two optional parameters
+# TSIZE                        # one optional parameter
+#
+# Default: none
+#
+tftp_option            BLKSIZE 512 65464
+tftp_option            TIMEOUT 5 60
+tftp_option            TSIZE 10485760
+#
+
+
+#############################################################
+# Service FTP
+#############################################################
+
+#########################################
+# ftp_bind_port
+#
+# Port number to bind FTP service to
+#
+# Syntax: ftp_bind_port <port number>
+#
+# Default: 21
+#
+#ftp_bind_port         21
+
+
+#########################################
+# ftp_version
+#
+# Version string to return in replies to the STAT command
+#
+# Syntax: ftp_version <string>
+#
+# Default: "INetSim FTP Server"
+#
+#ftp_version           "vsFTPd 2.0.4 - secure, fast, stable"
+
+
+#########################################
+# ftp_banner
+#
+# The banner string used in FTP greeting message
+#
+# Syntax: ftp_banner <string>
+#
+# Default: "INetSim FTP Service ready."
+#
+#ftp_banner            "FTP Server ready"
+
+
+#########################################
+# ftp_recursive_delete
+#
+# Allow recursive deletion of directories,
+# even if they are not empty
+#
+# Syntax: ftp_recursive_delete [yes|no]
+#
+# Default: no
+#
+#ftp_recursive_delete  yes
+
+
+#############################################################
+# Service FTPS
+#############################################################
+
+#########################################
+# ftps_bind_port
+#
+# Port number to bind FTP service to
+#
+# Syntax: ftp_bind_port <port number>
+#
+# Default: 990
+#
+#ftps_bind_port                990
+
+
+#########################################
+# ftps_version
+#
+# Version string to return in replies to the STAT command
+#
+# Syntax: ftps_version <string>
+#
+# Default: "INetSim FTPs Server"
+#
+#ftps_version          "vsFTPd 2.0.4 - secure, fast, stable"
+
+
+#########################################
+# ftps_banner
+#
+# The banner string used in FTP greeting message
+#
+# Syntax: ftps_banner <string>
+#
+# Default: "INetSim FTP Service ready."
+#
+#ftps_banner           "FTP Server ready"
+
+
+#########################################
+# ftps_recursive_delete
+#
+# Allow recursive deletion of directories,
+# even if they are not empty
+#
+# Syntax: ftps_recursive_delete [yes|no]
+#
+# Default: no
+#
+#ftps_recursive_delete yes
+
+
+#########################################
+# ftps_ssl_keyfile
+#
+# Name of the SSL private key PEM file.
+# The key MUST NOT be encrypted!
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: ftps_ssl_keyfile <filename>
+#
+# Default: default_key.pem
+#
+#ftps_ssl_keyfile      ftps_key.pem
+
+
+#########################################
+# ftps_ssl_certfile
+#
+# Name of the SSL certificate PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: ftps_ssl_certfile <filename>
+#
+# Default: default_cert.pem
+#
+#ftps_ssl_certfile     ftps_cert.pem
+
+
+#########################################
+# ftps_ssl_dhfile
+#
+# Name of the Diffie-Hellman parameter PEM file.
+#
+# The file must be placed in <data-dir>/certs/
+#
+# Syntax: ftps_ssl_dhfile <filename>
+#
+# Default: none
+#
+#ftps_ssl_dhfile       ftps_dh1024.pem
+
+
+#############################################################
+# Service NTP
+#############################################################
+
+#########################################
+# ntp_bind_port
+#
+# Port number to bind NTP service to
+#
+# Syntax: ntp_bind_port <port number>
+#
+# Default: 123
+#
+#ntp_bind_port         123
+
+
+#########################################
+# ntp_server_ip
+#
+# The IP address to return in NTP replies
+#
+# Syntax: ntp_server_ip <IP address>
+#
+# Default: 127.0.0.1
+#
+#ntp_server_ip         10.15.20.30
+
+
+#########################################
+# ntp_strict_checks
+#
+# Turn strict checks for client packets on or off
+#
+# Syntax: ntp_strict_checks [yes|no]
+#
+# Default: yes
+#
+#ntp_strict_checks     no
+
+
+#############################################################
+# Service IRC
+#############################################################
+
+#########################################
+# irc_bind_port
+#
+# Port number to bind IRC service to
+#
+# Syntax: irc_bind_port <port number>
+#
+# Default: 6667
+#
+#irc_bind_port         6667
+
+
+#########################################
+# irc_fqdn_hostname
+#
+# The FQDN hostname used for IRC
+#
+# Syntax: irc_fqdn_hostname <string>
+#
+# Default: irc.inetsim.org
+#
+#irc_fqdn_hostname     foo.bar.org
+
+
+#########################################
+# irc_version
+#
+# Version string to return
+#
+# Syntax: irc_version <string>
+#
+# Default: "INetSim IRC Server"
+#
+#irc_version           "Unreal3.2.7"
+
+
+#############################################################
+# Service Time
+#############################################################
+
+#########################################
+# time_bind_port
+#
+# Port number to bind time service to
+#
+# Syntax: time_bind_port <port number>
+#
+# Default: 37
+#
+#time_bind_port                37
+
+
+#############################################################
+# Service Daytime
+#############################################################
+
+#########################################
+# daytime_bind_port
+#
+# Port number to bind daytime service to
+#
+# Syntax: daytime_bind_port <port number>
+#
+# Default: 13
+#
+#daytime_bind_port     13
+
+
+#############################################################
+# Service Echo
+#############################################################
+
+#########################################
+# echo_bind_port
+#
+# Port number to bind echo service to
+#
+# Syntax: echo_bind_port <port number>
+#
+# Default: 7
+#
+#echo_bind_port                7
+
+
+#############################################################
+# Service Discard
+#############################################################
+
+#########################################
+# discard_bind_port
+#
+# Port number to bind discard service to
+#
+# Syntax: discard_bind_port <port number>
+#
+# Default: 9
+#
+#discard_bind_port     9
+
+
+#############################################################
+# Service Quotd
+#############################################################
+
+#########################################
+# quotd_bind_port
+#
+# Port number to bind quotd service to
+#
+# Syntax: quotd_bind_port <port number>
+#
+# Default: 17
+#
+#quotd_bind_port       17
+
+
+#############################################################
+# Service Chargen
+#############################################################
+
+#########################################
+# chargen_bind_port
+#
+# Port number to bind chargen service to
+#
+# Syntax: chargen_bind_port <port number>
+#
+# Default: 19
+#
+#chargen_bind_port     19
+
+
+#############################################################
+# Service Finger
+#############################################################
+
+#########################################
+# finger_bind_port
+#
+# Port number to bind finger service to
+#
+# Syntax: finger_bind_port <port number>
+#
+# Default: 79
+#
+#finger_bind_port      79
+
+
+#############################################################
+# Service Ident
+#############################################################
+
+#########################################
+# ident_bind_port
+#
+# Port number to bind ident service to
+#
+# Syntax: ident_bind_port <port number>
+#
+# Default: 113
+#
+#ident_bind_port       113
+
+
+#############################################################
+# Service Syslog
+#############################################################
+
+#########################################
+# syslog_bind_port
+#
+# Port number to bind syslog service to
+#
+# Syntax: syslog_bind_port <port number>
+#
+# Default: 514
+#
+#syslog_bind_port      514
+
+
+#########################################
+# syslog_trim_maxlength
+#
+# Chop syslog messages at 1024 bytes.
+#
+# Syntax: syslog_trim_maxlength [yes|no]
+#
+# Default: no
+#
+#syslog_trim_maxlength         yes
+
+
+#########################################
+# syslog_accept_invalid
+#
+# Accept invalid syslog messages.
+#
+# Syntax: syslog_accept_invalid [yes|no]
+#
+# Default: no
+#
+#syslog_accept_invalid         yes
+
+
+#############################################################
+# Service Dummy
+#############################################################
+
+#########################################
+# dummy_bind_port
+#
+# Port number to bind dummy service to
+#
+# Syntax: dummy_bind_port <port number>
+#
+# Default: 1
+#
+#dummy_bind_port       1
+
+
+#########################################
+# dummy_banner
+#
+# Banner string sent to client if no data has been
+# received for 'dummy_banner_wait' seconds since
+# the client has established the connection.
+# If set to an empty string (""), only CRLF will be sent.
+# This option only takes effect if 'dummy_banner_wait'
+# is not set to '0'.
+#
+# Syntax: dummy_banner <string>
+#
+# Default: "220 ESMTP FTP +OK POP3 200 OK"
+#
+#dummy_banner          ""
+
+
+#########################################
+# dummy_banner_wait
+#
+# Number of seconds to wait for client sending any data
+# after establishing a new connection.
+# If no data has been received within this amount of time,
+# 'dummy_banner' will be sent to the client.
+# Setting to '0' disables sending of a banner string.
+#
+# Syntax: dummy_banner_wait [0..600]
+#
+# Default: 5
+#
+#dummy_banner_wait     3
+
+
+#############################################################
+# Redirect
+#############################################################
+
+#########################################
+# redirect_enabled
+#
+# Turn connection redirection on or off.
+#
+# Syntax: redirect_enabled [yes|no]
+#
+# Default: no
+#
+#redirect_enabled      yes
+
+
+#########################################
+# redirect_unknown_services
+#
+# Redirect connection attempts to unbound ports
+# to dummy service
+#
+# Syntax: redirect_unknown_services [yes|no]
+#
+# Default: yes
+#
+#redirect_unknown_services     no
+
+
+#########################################
+# redirect_external_address
+#
+# IP address used as source address if INetSim
+# acts as a router for redirecting packets to
+# external networks.
+# This option only takes effect if static rules
+# for redirecting packets to external networks
+# are defined (see 'redirect_static_rule' below).
+#
+# Syntax: redirect_external_address <IP address>
+#
+# Default: none
+#
+redirect_external_address      10.10.10.1
+
+
+#########################################
+# redirect_static_rule
+#
+# Static mappings for connection redirection.
+# Note: Currently only protocols tcp, udp and icmp are supported.
+#
+# Syntax: redirect_static_rule tcp|udp <IP address:port>      <IP address:port>
+#         redirect_static_rule tcp|udp <IP address:>          <IP address:>
+#         redirect_static_rule tcp|udp <:port>                <IP address:>
+#         redirect_static_rule tcp|udp <:port>                <:port>
+#         redirect_static_rule icmp    <IP address:icmp-type> <IP address>
+#         redirect_static_rule icmp    <IP address:>          <IP address>
+#         redirect_static_rule icmp    <:icmp-type>           <IP address>
+#
+# Default: none 
+#
+# Examples:
+#
+# WWW caching service
+#redirect_static_rule  tcp             :8080                   :80
+#
+# Submission [RFC4409]
+#redirect_static_rule  tcp             :587                    :25
+#
+# Echo-Request [RFC792]
+#redirect_static_rule  icmp 10.10.10.20:echo-request   10.1.0.25
+#
+# Redirection based on IP address and/or port:
+#redirect_static_rule  tcp     10.10.10.55:88           10.10.10.1:80
+#redirect_static_rule  tcp                :99          192.168.1.1:25
+#redirect_static_rule  tcp     10.10.10.20:             172.16.1.2:
+
+
+#########################################
+# redirect_change_ttl
+#
+# Change the time-to-live header field to a random value
+# in outgoing IP packets.
+#
+# Syntax: redirect_change_ttl [yes|no]
+#
+# Default: no
+#
+#redirect_change_ttl   yes
+
+
+#########################################
+# redirect_exclude_port
+#
+# Connections to <service_bind_address> on this port
+# are not redirected
+#
+# Syntax: redirect_exclude_port <protocol:port>
+#
+# Default: none
+#
+redirect_exclude_port          tcp:22
+#redirect_exclude_port         udp:111
+
+
+#########################################
+# redirect_ignore_bootp
+#
+# If set to 'yes', BOOTP (DHCP) broadcasts will not be redirected
+# (UDP packets with source address 0.0.0.0, port 68 and
+# destination address 255.255.255.255, port 67 or vice versa)
+#
+# Syntax: redirect_ignore_bootp [yes|no]
+#
+# Default: no
+#
+#redirect_ignore_bootp         yes
+
+
+#########################################
+# redirect_ignore_netbios
+#
+# If set to 'yes', NetBIOS broadcasts will not be redirected
+# (UDP packets with source/destination port 137/138
+# and destination address x.x.x.255 on the local network)
+#
+# Syntax: redirect_ignore_netbios [yes|no]
+#
+# Default: no
+#
+#redirect_ignore_netbios       yes
+
+
+#########################################
+# redirect_icmp_timestamp
+#
+# If set to 'ms', ICMP Timestamp requests will be answered
+# with number of milliseconds since midnight UTC according
+# to faketime.
+# If set to 'sec', ICMP Timestamp requests will be answered
+# with number of seconds since epoch (high order bit of the
+# timestamp will be set to indicate non-standard value).
+# Setting to 'no' disables manipulation of ICMP Timestamp
+# requests.
+#
+# Syntax: redirect_icmp_timestamp [ms|sec|no]
+#
+# Default: ms
+#
+#redirect_icmp_timestamp       sec
+
+
+#############################################################
+# End of INetSim configuration file
+#############################################################
diff --git a/libnetclient/test/start-test-env.sh b/libnetclient/test/start-test-env.sh
new file mode 100755
index 0000000..c79d331
--- /dev/null
+++ b/libnetclient/test/start-test-env.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# $Id$
+
+echo "starting test environment:"
+
+echo "GnuTLS server w/o client checking @ port 65001 as s_server1..."
+ -d -m -S s_server1 \
+        -a --x509keyfile=cert_u.pem --x509certfile=cert_u.pem --echo -p 65001
+echo "GnuTLS server w/ client checking @ port 65002 as s_server2..."
+ -d -m -S s_server2 \
+        -r --verify-client-cert --x509keyfile=cert_u.pem --x509certfile=cert_u.pem \
+       --x509cafile=ca_cert.pem --echo -p 65002
+ -ls
+
+echo "inetsim (as root)..."
+  --config inetsim.conf
+
+echo "shut down GnuTLS servers..."
+ -S s_server1 -X quit
+ -S s_server2 -X quit
diff --git a/libnetclient/test/start-test-env.sh.in b/libnetclient/test/start-test-env.sh.in
new file mode 100644
index 0000000..5479612
--- /dev/null
+++ b/libnetclient/test/start-test-env.sh.in
@@ -0,0 +1,20 @@
+#!/bin/sh
+# $Id$
+
+echo "starting test environment:"
+
+echo "GnuTLS server w/o client checking @ port 65001 as s_server1..."
+@SCREEN@ -d -m -S s_server1 \
+       @GTLSSRV@ -a --x509keyfile=cert_u.pem --x509certfile=cert_u.pem --echo -p 65001
+echo "GnuTLS server w/ client checking @ port 65002 as s_server2..."
+@SCREEN@ -d -m -S s_server2 \
+       @GTLSSRV@ -r --verify-client-cert --x509keyfile=cert_u.pem --x509certfile=cert_u.pem \
+       --x509cafile=ca_cert.pem --echo -p 65002
+@SCREEN@ -ls
+
+echo "inetsim (as root)..."
+@SUDO@ @INETSIM@ --config inetsim.conf
+
+echo "shut down GnuTLS servers..."
+@SCREEN@ -S s_server1 -X quit
+@SCREEN@ -S s_server2 -X quit
diff --git a/libnetclient/test/tests.c b/libnetclient/test/tests.c
new file mode 100644
index 0000000..d636a0b
--- /dev/null
+++ b/libnetclient/test/tests.c
@@ -0,0 +1,470 @@
+/*
+ * tests.c
+ *
+ *  Created on: 07.01.2017
+ *      Author: albrecht
+ */
+
+#include <sys/types.h>
+#include <signal.h>
+#include <string.h>
+#include <sput.h>
+#include "net-client.h"
+#include "net-client-smtp.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_utils(void);
+
+
+int
+main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv)
+{
+       sput_start_testing();
+
+       sput_enter_suite("test basic (plain)");
+       sput_run_test(test_basic);
+
+       sput_enter_suite("test basic (encrypted)");
+       sput_run_test(test_basic_crypt);
+
+       sput_enter_suite("test SMTP");
+       sput_run_test(test_smtp);
+
+       //sput_enter_suite("test POP3");
+       //sput_run_test(test_pop3);
+
+       sput_enter_suite("test utility functions");
+       sput_run_test(test_utils);
+
+       sput_finish_testing();
+
+       return sput_get_return_value();
+}
+
+
+static void
+test_basic(void)
+{
+       static gchar *nc_args[] = { NCAT, "-l", "65000", "--exec", SED " -u -e s/x/ThisIsLong/g", NULL };
+       NetClient *basic;
+       GPid child;
+       GError *error = NULL;
+       gboolean op_res;
+       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");
+       sput_fail_unless(strcmp(net_client_get_host(basic), "localhost") == 0, "read host ok");
+       sput_fail_unless(net_client_connect(basic, NULL) == FALSE, "connect failed");
+       g_object_unref(basic);
+
+       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");
+       sput_fail_unless(net_client_set_timeout(basic, 10) == TRUE, "set timeout");
+
+       op_res =  net_client_write_line(basic, "Hi There", &error);
+       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");
+       g_clear_error(&error);
+
+       op_res =
+               g_spawn_async(NULL, nc_args, NULL, G_SPAWN_STDOUT_TO_DEV_NULL + G_SPAWN_STDERR_TO_DEV_NULL, 
NULL, NULL, &child, &error);
+       if (!op_res) {
+               g_error("launching %s failed: %s", nc_args[0], error->message);
+               g_assert_not_reached();
+       }
+       sleep(1);
+
+       sput_fail_unless(net_client_connect(basic, NULL) == TRUE, "connect succeeded");
+       op_res = net_client_connect(basic, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_CONNECTED), "cannot connect 
already connected");
+       g_clear_error(&error);
+       op_res = net_client_configure(basic, "localhost", 65000, 42, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_CONNECTED), "cannot configure 
already connected");
+       g_clear_error(&error);
+
+       sput_fail_unless(net_client_write_buffer(NULL, "xxx", 3U, NULL) == FALSE, "write buffer w/o client");
+       sput_fail_unless(net_client_write_buffer(basic, NULL, 3U, NULL) == FALSE, "write buffer w/o buffer");
+       sput_fail_unless(net_client_write_buffer(basic, "xxx", 0U, NULL) == FALSE, "write buffer w/o count");
+
+       sput_fail_unless(net_client_write_line(NULL, "%100s", NULL, "x") == FALSE, "write line w/o client");
+       sput_fail_unless(net_client_write_line(basic, NULL, NULL) == FALSE, "write line w/o format string");
+       op_res = net_client_write_line(basic, "%100s", &error, "x");
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_LINE_TOO_LONG), "write w/ line 
too long");
+       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");
+       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);
+       g_clear_error(&error);
+
+       sput_fail_unless(net_client_execute(NULL, NULL, "Hi There", NULL) == FALSE, "execute w/o client");
+       sput_fail_unless(net_client_execute(basic, NULL, NULL, NULL) == FALSE, "execute w/o format string");
+       sput_fail_unless(net_client_execute(basic, NULL, "%100s", NULL, "x") == FALSE, "execute w/ xmit line 
too long");
+       op_res = net_client_execute(basic, &read_res, "Hi There", NULL);
+       sput_fail_unless((op_res == TRUE) && (strcmp("Hi There", read_res) == 0), "execute ok");
+       g_free(read_res);
+
+       sput_fail_unless(net_client_write_buffer(basic, "H", 1U, NULL) == TRUE, "write buffer part 1");
+       sput_fail_unless(net_client_write_buffer(basic, "i Y", 3U, NULL) == TRUE, "write buffer part 2");
+       sput_fail_unless(net_client_write_buffer(basic, "ou\r\n", 4U, NULL) == TRUE, "write buffer part 3");
+       op_res = net_client_read_line(basic, &read_res, NULL);
+       sput_fail_unless((op_res == TRUE) && (strcmp("Hi You", read_res) == 0), "read back ok");
+       g_free(read_res);
+
+       op_res = net_client_execute(basic, &read_res, "xxxxxxxxxx", &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_LINE_TOO_LONG), "read line too 
long");
+       g_clear_error(&error);
+
+       kill(child, SIGTERM);
+
+       op_res = net_client_read_line(basic, NULL, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code = NET_CLIENT_ERROR_CONNECTION_LOST), "read line, 
client lost");
+       g_clear_error(&error);
+
+       g_object_unref(basic);
+}
+
+
+static gboolean
+check_cert(NetClient *client, GTlsCertificate *peer_cert, GTlsCertificateFlags errors, gpointer user_data)
+{
+       gchar *hash;
+       GByteArray *cert_der = NULL;
+
+       g_object_get(G_OBJECT(peer_cert), "certificate", &cert_der, NULL);
+       hash = g_compute_checksum_for_data(G_CHECKSUM_SHA256, cert_der->data, cert_der->len);
+       g_byte_array_unref(cert_der);
+       g_message("%s(%p, %p, %x, %p) -> fp(sha256) = %s", __func__, client, peer_cert, errors, user_data, 
hash);
+       g_free(hash);
+       return TRUE;
+}
+
+
+static gchar *
+get_cert_passwd(NetClient *client, const GByteArray *cert_der, gpointer user_data)
+{
+       g_message("%s(%p, %p, %p)", __func__, client, cert_der, user_data);
+       return g_strdup("test-server");
+}
+
+
+static void
+test_basic_crypt(void)
+{
+       NetClient *basic;
+       gboolean op_res;
+       GError *error = NULL;
+
+       /* tests without client cert check */
+       sput_fail_unless((basic = net_client_new("localhost", 65001, 42)) != NULL, "localhost; port 65001");
+       sput_fail_unless(net_client_is_encrypted(NULL) == FALSE, "NULL client is unencrypted");
+       sput_fail_unless(net_client_is_encrypted(basic) == FALSE, "unconnected client is unencrypted");
+       sput_fail_unless(net_client_start_tls(NULL, NULL) == FALSE, "start tls: no client");
+       op_res = net_client_start_tls(basic, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_NOT_CONNECTED), "start tls: 
not connected");
+       g_clear_error(&error);
+       sput_fail_unless(net_client_connect(basic, NULL) == TRUE, "connect ok");
+       sput_fail_unless(net_client_is_encrypted(basic) == FALSE, "still unencrypted");
+       op_res = net_client_start_tls(basic, &error);
+       sput_fail_unless((op_res == FALSE) && (error != NULL), "start tls: bad server cert");
+       g_clear_error(&error);
+       g_object_unref(basic);
+
+       sput_fail_unless((basic = net_client_new("localhost", 65001, 42)) != NULL, "localhost; port 65001");
+       g_signal_connect(G_OBJECT(basic), "cert-check", G_CALLBACK(check_cert), NULL);
+       sput_fail_unless(net_client_connect(basic, NULL) == TRUE, "connect ok");
+       sput_fail_unless(net_client_start_tls(basic, NULL) == TRUE, "start tls: success");
+       sput_fail_unless(net_client_is_encrypted(basic) == TRUE, "is encrypted");
+       op_res = net_client_start_tls(basic, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_TLS_ACTIVE), "start tls: 
already started");
+       g_clear_error(&error);
+       g_object_unref(basic);
+
+       /* tests with client cert check */
+       sput_fail_unless((basic = net_client_new("localhost", 65002, 42)) != NULL, "localhost; port 65002");
+       g_signal_connect(G_OBJECT(basic), "cert-check", G_CALLBACK(check_cert), NULL);
+       sput_fail_unless(net_client_connect(basic, NULL) == TRUE, "connect ok");
+       sput_fail_unless(net_client_start_tls(basic, NULL) == FALSE, "start tls: fails");
+       sput_fail_unless(net_client_is_encrypted(basic) == FALSE, "not encrypted");
+       g_object_unref(basic);
+
+       sput_fail_unless((basic = net_client_new("localhost", 65002, 42)) != NULL, "localhost; port 65002");
+       g_signal_connect(G_OBJECT(basic), "cert-check", G_CALLBACK(check_cert), NULL);
+       sput_fail_unless(net_client_set_cert_from_file(NULL, "cert_u.pem", NULL) == FALSE, "load cert file 
w/o client");
+       sput_fail_unless(net_client_set_cert_from_file(basic, NULL, NULL) == FALSE, "load cert file w/o 
file");
+       sput_fail_unless(net_client_set_cert_from_file(basic, "no_such_file.crt", NULL) == FALSE, "load cert 
file, file missing");
+       sput_fail_unless(net_client_set_cert_from_pem(NULL, "This is no cert", NULL) == FALSE, "load cert 
buffer w/o client");
+       sput_fail_unless(net_client_set_cert_from_pem(basic, NULL, NULL) == FALSE, "load cert buffer w/o 
buffer");
+       op_res = net_client_set_cert_from_pem(basic, "This is no cert", &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_GNUTLS), "load cert buffer w/ 
broken pem data");
+       g_clear_error(&error);
+       sput_fail_unless(net_client_set_cert_from_file(basic, "cert_u.pem", NULL) == TRUE, "load cert file w/ 
plain key ok");
+       sput_fail_unless(net_client_connect(basic, NULL) == TRUE, "connect ok");
+       sput_fail_unless(net_client_start_tls(basic, NULL) == TRUE, "start tls: ok");
+       sput_fail_unless(net_client_is_encrypted(basic) == TRUE, "encrypted");
+       g_object_unref(basic);
+
+       sput_fail_unless((basic = net_client_new("localhost", 65002, 42)) != NULL, "localhost; port 65002");
+       g_signal_connect(G_OBJECT(basic), "cert-check", G_CALLBACK(check_cert), NULL);
+       sput_fail_unless(net_client_set_cert_from_file(basic, "cert_u.pem", NULL) == TRUE, "load cert file w/ 
plain key ok");
+       sput_fail_unless(net_client_set_cert_from_file(basic, "cert.pem", NULL) == FALSE, "load cert file w/ 
crypt key fails");
+       g_signal_connect(G_OBJECT(basic), "cert-pass", G_CALLBACK(get_cert_passwd), NULL);
+       sput_fail_unless(net_client_set_cert_from_file(basic, "cert.pem", NULL) == TRUE, "load cert file w/ 
crypt key ok");
+       sput_fail_unless(net_client_connect(basic, NULL) == TRUE, "connect ok");
+       sput_fail_unless(net_client_start_tls(basic, NULL) == TRUE, "start tls: ok");
+       sput_fail_unless(net_client_is_encrypted(basic) == TRUE, "encrypted");
+       g_object_unref(basic);
+}
+
+
+typedef struct {
+       gchar *msg_text;
+       gchar *read_ptr;
+} msg_data_t;
+
+
+static gssize
+msg_data_cb(gchar *buffer, gsize count, gpointer user_data, GError **error)
+{
+       msg_data_t *msg_data = (msg_data_t *) user_data;
+       size_t msg_len;
+       gssize result;
+
+       g_message("%s(%p, %lu, %p, %p)", __func__, buffer, count, user_data, error);
+       msg_len = strlen(msg_data->read_ptr);
+       if (msg_len > 0) {
+               if (msg_len > count) {
+                       result = count;
+               } else {
+                       result = msg_len;
+               }
+               memcpy(buffer, msg_data->read_ptr, result);
+               msg_data->read_ptr = &msg_data->read_ptr[result];
+       } else {
+               result = 0;
+       }
+       g_message("%s: return %ld", __func__, result);
+       return result;
+}
+
+
+#define MSG_TEXT                                                                               \
+       "From: \"Sender Name\" <me here com>\r\n"                       \
+       "To: \"Recipient Name\" <you there com>\r\n"            \
+       "Cc: \"Other Recipient\" <other there com>\r\n"         \
+       "Subject: This is just a test message\r\n"                      \
+       "Date: Fri, 13 Jan 2017 21:27:11 +0100\r\n"             \
+       "Message-ID: <20170113212711 19833 here com>\r\n"       \
+       "\r\n"                                                                                          \
+       "This is the body of the test message.\r\n"
+
+
+static gchar **
+get_auth(NetClient *client, gpointer user_data)
+{
+       gchar ** result;
+
+       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");
+       return result;
+}
+
+
+static void
+test_smtp(void)
+{
+       msg_data_t msg_buf;
+       NetClientSmtp *smtp;
+       NetClientSmtpMessage *msg;
+       GError *error = NULL;
+       gboolean op_res;
+       gchar *read_res;
+
+       // message creation
+       msg_buf.msg_text = msg_buf.read_ptr = MSG_TEXT;
+       sput_fail_unless(net_client_smtp_msg_new(NULL, NULL) == NULL, "create msg: no callback");
+       sput_fail_unless((msg = net_client_smtp_msg_new(msg_data_cb, &msg_buf)) != NULL, "create msg: ok");
+
+       sput_fail_unless(net_client_smtp_msg_set_sender(NULL, "some sender com") == FALSE, "set sender, no 
message");
+       sput_fail_unless(net_client_smtp_msg_set_sender(msg, NULL) == FALSE, "set sender, no address");
+
+       sput_fail_unless(net_client_smtp_msg_add_recipient(NULL, "you there com", NET_CLIENT_SMTP_DSN_NEVER)  
== FALSE,
+               "add recipient, no message");
+       sput_fail_unless(net_client_smtp_msg_add_recipient(msg, NULL, NET_CLIENT_SMTP_DSN_NEVER)  == FALSE,
+               "add recipient, no address");
+
+       sput_fail_unless(net_client_smtp_msg_set_dsn_opts(NULL, NULL, FALSE) == FALSE, "set dsn opts, no 
message");
+       sput_fail_unless(net_client_smtp_msg_set_dsn_opts(msg, NULL, FALSE) == TRUE, "set dsn opts ok");
+
+
+       // smtp stuff - test various failures
+       sput_fail_unless(net_client_smtp_new(NULL, 0, NET_CLIENT_CRYPT_NONE) == NULL, "missing host");
+
+       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");
+       g_object_unref(smtp);
+
+       sput_fail_unless((smtp = net_client_smtp_new("localhost", 65025, NET_CLIENT_CRYPT_NONE)) != NULL, 
"localhost:65025");
+       op_res = net_client_smtp_connect(smtp, &read_res, NULL);
+       sput_fail_unless((op_res == TRUE) && (strcmp(read_res, "mail.inetsim.org INetSim Mail Service 
ready.") == 0),
+               "connect: success");
+       g_free(read_res);
+       sput_fail_unless(net_client_is_encrypted(NET_CLIENT(smtp)) == FALSE, "not encrypted");
+       op_res = net_client_smtp_connect(smtp, NULL, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_CONNECTED), "cannot 
reconnect");
+       g_clear_error(&error);
+       sput_fail_unless(net_client_smtp_can_dsn(NULL) == FALSE, "NULL client: no dsn");
+       sput_fail_unless(net_client_smtp_can_dsn(smtp) == TRUE, "inetsim: can dsn");
+
+       sput_fail_unless(net_client_smtp_send_msg(NULL, msg, NULL) == FALSE, "send msg, NULL client");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, NULL, NULL) == FALSE, "send msg, NULL message");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, msg, NULL) == FALSE, "send msg: error, no sender");
+       sput_fail_unless(net_client_smtp_msg_set_sender(msg, "some sender com") == TRUE, "set sender ok");
+       sput_fail_unless(net_client_smtp_msg_set_sender(msg, "me here com") == TRUE, "replace sender ok");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, msg, NULL) == FALSE, "send msg: error, no recipient");
+       sput_fail_unless(net_client_smtp_msg_add_recipient(msg, "you there com", NET_CLIENT_SMTP_DSN_NEVER)  
== TRUE,
+               "add recipient ok (no dsn)");
+       op_res = net_client_smtp_send_msg(smtp, msg, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_SMTP_PERMANENT), "send failed: 
not authenticated");
+       g_clear_error(&error);
+       g_object_unref(smtp);
+
+       sput_fail_unless((smtp = net_client_smtp_new("localhost", 65025, NET_CLIENT_CRYPT_STARTTLS)) != NULL,
+               "localhost:65025, starttls");
+       op_res = net_client_smtp_connect(smtp, NULL, &error);
+       sput_fail_unless((op_res == FALSE) && (error != NULL), "connect: fails (untrusted cert)");
+       g_clear_error(&error);
+       g_object_unref(smtp);
+
+       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(smtp, FALSE, 0U) == TRUE, "force no auth mech available");
+       g_signal_connect(G_OBJECT(smtp), "auth", G_CALLBACK(get_auth), smtp);
+       op_res = net_client_smtp_connect(smtp, NULL, &error);
+       sput_fail_unless((op_res == FALSE) && (error->code == NET_CLIENT_ERROR_SMTP_NO_AUTH), "connect: 
fails");
+       g_clear_error(&error);
+       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");
+       sput_fail_unless(net_client_smtp_allow_auth(smtp, FALSE, NET_CLIENT_SMTP_AUTH_PLAIN) == TRUE, "force 
auth meth PLAIN");
+       g_signal_connect(G_OBJECT(smtp), "auth", G_CALLBACK(get_auth), smtp);
+       sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL) == TRUE, "connect: success");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, msg, NULL) == TRUE, "send msg: success");
+       g_object_unref(smtp);
+
+       // STARTTLS required, LOGIN auth
+       sput_fail_unless(net_client_smtp_msg_add_recipient(msg, "other1 there com", 
NET_CLIENT_SMTP_DSN_SUCCESS) == TRUE,
+               "add recipient ok (dsn)");
+       sput_fail_unless((smtp = net_client_smtp_new("localhost", 65025, NET_CLIENT_CRYPT_STARTTLS)) != NULL,
+               "localhost:65025, starttls");
+       sput_fail_unless(net_client_smtp_allow_auth(smtp, TRUE, NET_CLIENT_SMTP_AUTH_LOGIN) == TRUE, "force 
auth meth LOGIN");
+       g_signal_connect(G_OBJECT(smtp), "cert-check", G_CALLBACK(check_cert), NULL);
+       g_signal_connect(G_OBJECT(smtp), "auth", G_CALLBACK(get_auth), smtp);
+       sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL) == TRUE, "connect: success");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, msg, NULL) == TRUE, "send msg: success");
+       g_object_unref(smtp);
+
+       // STARTTLS optional, CRAM-MD5 auth
+       sput_fail_unless(net_client_smtp_msg_add_recipient(msg, "other2 there com", 
NET_CLIENT_SMTP_DSN_FAILURE) == TRUE,
+               "add recipient ok (dsn)");
+       sput_fail_unless((smtp = net_client_smtp_new("localhost", 65025, NET_CLIENT_CRYPT_STARTTLS_OPT)) != 
NULL,
+               "localhost:65025, starttls");
+       sput_fail_unless(net_client_smtp_allow_auth(smtp, TRUE, NET_CLIENT_SMTP_AUTH_CRAM_MD5) == TRUE, 
"force auth meth CRAM-MD5");
+       g_signal_connect(G_OBJECT(smtp), "cert-check", G_CALLBACK(check_cert), NULL);
+       g_signal_connect(G_OBJECT(smtp), "auth", G_CALLBACK(get_auth), smtp);
+       sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL) == TRUE, "connect: success");
+       sput_fail_unless(net_client_smtp_msg_set_dsn_opts(msg, "20170113212711 19833 here com", FALSE) == 
TRUE, "dsn: envid, headers");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, msg, NULL) == TRUE, "send msg: success");
+       g_object_unref(smtp);
+
+       // SSL, CRAM-SHA1 auth
+       sput_fail_unless(net_client_smtp_msg_add_recipient(msg, "other3 there com", 
NET_CLIENT_SMTP_DSN_DELAY) == TRUE,
+               "add recipient ok (dsn)");
+       sput_fail_unless((smtp = net_client_smtp_new("localhost", 65465, NET_CLIENT_CRYPT_ENCRYPTED)) != 
NULL, "localhost:65025, ssl");
+       sput_fail_unless(net_client_smtp_allow_auth(smtp, TRUE, NET_CLIENT_SMTP_AUTH_CRAM_SHA1) == TRUE, 
"force auth meth CRAM-SHA1");
+       g_signal_connect(G_OBJECT(smtp), "cert-check", G_CALLBACK(check_cert), NULL);
+       g_signal_connect(G_OBJECT(smtp), "auth", G_CALLBACK(get_auth), smtp);
+       sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL) == TRUE, "connect: success");
+       sput_fail_unless(net_client_smtp_msg_set_dsn_opts(msg, NULL, TRUE) == TRUE, "dsn: no envid, message");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, msg, NULL) == TRUE, "send msg: success");
+       g_object_unref(smtp);
+
+       // SSL, auto select auth
+       sput_fail_unless(net_client_smtp_msg_add_recipient(msg, "other4 there com",
+               NET_CLIENT_SMTP_DSN_SUCCESS + NET_CLIENT_SMTP_DSN_FAILURE + NET_CLIENT_SMTP_DSN_DELAY) == 
TRUE, "add recipient ok (dsn)");
+       sput_fail_unless((smtp = net_client_smtp_new("localhost", 65465, NET_CLIENT_CRYPT_ENCRYPTED)) != 
NULL, "localhost:65025, ssl");
+       g_signal_connect(G_OBJECT(smtp), "cert-check", G_CALLBACK(check_cert), NULL);
+       g_signal_connect(G_OBJECT(smtp), "auth", G_CALLBACK(get_auth), smtp);
+       sput_fail_unless(net_client_smtp_connect(smtp, NULL, NULL) == TRUE, "connect: success");
+       sput_fail_unless(net_client_smtp_msg_set_dsn_opts(msg, "20170113212711 19833 here com", TRUE) == TRUE,
+               "dsn: envid, message");
+       sput_fail_unless(net_client_smtp_send_msg(smtp, msg, NULL) == TRUE, "send msg: success");
+       g_object_unref(smtp);
+
+       net_client_smtp_msg_free(NULL);
+       net_client_smtp_msg_free(msg);
+}
+
+
+static void
+test_utils(void)
+{
+       gchar *authstr;
+
+       sput_fail_unless(strcmp(net_client_chksum_to_str(G_CHECKSUM_MD5), "MD5") == 0, "checksum string for 
MD5");
+       sput_fail_unless(strcmp(net_client_chksum_to_str(G_CHECKSUM_SHA1), "SHA1") == 0, "checksum string for 
SHA1");
+       sput_fail_unless(strcmp(net_client_chksum_to_str(G_CHECKSUM_SHA256), "SHA256") == 0, "checksum string 
for SHA256");
+       sput_fail_unless(strcmp(net_client_chksum_to_str(G_CHECKSUM_SHA512), "SHA512") == 0, "checksum string 
for SHA512");
+       sput_fail_unless(strcmp(net_client_chksum_to_str((GChecksumType) -1), "_UNKNOWN_") == 0, "checksum 
string for unknown");
+
+       /* note: test the md5 example from rfc2195, sect. 2; other hashes calculated from
+        *       http://www.freeformatter.com/hmac-generator.html */
+#define CRAM_MD5_CHAL  "PDE4OTYuNjk3MTcwOTUyQHBvc3RvZmZpY2UucmVzdG9uLm1jaS5uZXQ+"
+#define CRAM_MD5_USER  "tim"
+#define CRAM_MD5_PASS  "tanstaaftanstaaf"
+#define CRAM_MD5_RES   "dGltIGI5MTNhNjAyYzdlZGE3YTQ5NWI0ZTZlNzMzNGQzODkw"
+#define CRAM_SHA1_RES  "dGltIDhjYjEwZWQwYThiNzY0YWIwZTA1MTI1ZGQ2ZWFhMjk1ZjE5YjU5NDM="
+#define CRAM_SHA256_RES 
"dGltIGYwMDExYmJmN2MxNjE4ZWY5MGUzZjE4MTQ4ZTVlZGE0ZTE1NjMxYzljMzhlYmVmMDYyYmY3MTQzZmY3MmU5NDQ="
+       sput_fail_unless(net_client_cram_calc(NULL, G_CHECKSUM_MD5, CRAM_MD5_USER, CRAM_MD5_PASS) == NULL, 
"cram-md5: no challenge");
+       sput_fail_unless(net_client_cram_calc(CRAM_MD5_CHAL, G_CHECKSUM_MD5, NULL, CRAM_MD5_PASS) == NULL, 
"cram-md5: no user");
+       sput_fail_unless(net_client_cram_calc(CRAM_MD5_CHAL, G_CHECKSUM_MD5, CRAM_MD5_USER, NULL) == NULL, 
"cram-md5: no password");
+       authstr = net_client_cram_calc(CRAM_MD5_CHAL, G_CHECKSUM_MD5, CRAM_MD5_USER, CRAM_MD5_PASS);
+       sput_fail_unless(strcmp(authstr, CRAM_MD5_RES) == 0, "cram-md5: auth string ok");
+       g_free(authstr);
+
+       authstr = net_client_cram_calc(CRAM_MD5_CHAL, G_CHECKSUM_SHA1, CRAM_MD5_USER, CRAM_MD5_PASS);
+       sput_fail_unless(strcmp(authstr, CRAM_SHA1_RES) == 0, "cram-sha1: auth string ok");
+       g_free(authstr);
+
+       authstr = net_client_cram_calc(CRAM_MD5_CHAL, G_CHECKSUM_SHA256, CRAM_MD5_USER, CRAM_MD5_PASS);
+       sput_fail_unless(strcmp(authstr, CRAM_SHA256_RES) == 0, "cram-sha256: auth string ok");
+       g_free(authstr);
+
+#define AUTH_PLAIN_RES "dGltAHRpbQB0YW5zdGFhZnRhbnN0YWFm"
+       sput_fail_unless(net_client_auth_plain_calc(NULL, CRAM_MD5_PASS) == NULL, "auth plain: no user");
+       sput_fail_unless(net_client_auth_plain_calc(CRAM_MD5_USER, NULL) == NULL, "auth plain: no password");
+       authstr = net_client_auth_plain_calc(CRAM_MD5_USER, CRAM_MD5_PASS);
+       sput_fail_unless(strcmp(authstr, AUTH_PLAIN_RES) == 0, "auth plain: auth string ok");
+       g_free(authstr);
+}
diff --git a/libnetclient/test/valgrind.supp b/libnetclient/test/valgrind.supp
new file mode 100644
index 0000000..b8ba298
--- /dev/null
+++ b/libnetclient/test/valgrind.supp
@@ -0,0 +1,17 @@
+# Valgrind suppressions
+{
+   g_type_register_fundamental
+   Memcheck:Leak
+   match-leak-kinds:all
+   ...
+   fun:g_type_register_fundamental
+}
+
+{
+   g_type_register_static
+   Memcheck:Leak
+   match-leak-kinds:all
+   ...
+   fun:g_type_register_static
+}
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]