]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Imap module with unit test (#3474)
authorStephen Blackwell <sblackwell@networkradius.com>
Mon, 22 Jun 2020 17:39:30 +0000 (13:39 -0400)
committerGitHub <noreply@github.com>
Mon, 22 Jun 2020 17:39:30 +0000 (12:39 -0500)
* imap with working authentication

35 files changed:
.travis.yml
raddb/mods-available/imap [new file with mode: 0644]
scripts/travis/build.sh
scripts/travis/dovecot/fr_dovecot.conf [new file with mode: 0644]
scripts/travis/imap-setup.sh [new file with mode: 0755]
src/lib/curl/base.c
src/lib/curl/base.h
src/modules/rlm_imap/README.md [new file with mode: 0644]
src/modules/rlm_imap/all.mk [new file with mode: 0644]
src/modules/rlm_imap/rlm_imap.c [new file with mode: 0644]
src/tests/modules/all.mk
src/tests/modules/imap/all.mk [new file with mode: 0644]
src/tests/modules/imap/auth_bad_pw.attrs [new file with mode: 0644]
src/tests/modules/imap/auth_bad_pw.unlang [new file with mode: 0644]
src/tests/modules/imap/auth_bad_usr.attrs [new file with mode: 0644]
src/tests/modules/imap/auth_bad_usr.unlang [new file with mode: 0644]
src/tests/modules/imap/auth_plaintext.attrs [new file with mode: 0644]
src/tests/modules/imap/auth_plaintext.unlang [new file with mode: 0644]
src/tests/modules/imap/imap_bad_tls/all.mk [new file with mode: 0644]
src/tests/modules/imap/imap_bad_tls/auth_bad_tls.attrs [new file with mode: 0644]
src/tests/modules/imap/imap_bad_tls/auth_bad_tls.unlang [new file with mode: 0644]
src/tests/modules/imap/imap_bad_tls/module.conf [new file with mode: 0644]
src/tests/modules/imap/imap_opt_tls/all.mk [new file with mode: 0644]
src/tests/modules/imap/imap_opt_tls/auth_try_tls.attrs [new file with mode: 0644]
src/tests/modules/imap/imap_opt_tls/auth_try_tls.unlang [new file with mode: 0644]
src/tests/modules/imap/imap_opt_tls/module.conf [new file with mode: 0644]
src/tests/modules/imap/imap_tls/all.mk [new file with mode: 0644]
src/tests/modules/imap/imap_tls/auth_tls.attrs [new file with mode: 0644]
src/tests/modules/imap/imap_tls/auth_tls.unlang [new file with mode: 0644]
src/tests/modules/imap/imap_tls/module.conf [new file with mode: 0644]
src/tests/modules/imap/module.conf [new file with mode: 0644]
src/tests/modules/imap_plaintext/all.mk [new file with mode: 0644]
src/tests/modules/imap_plaintext/auth.attrs [new file with mode: 0644]
src/tests/modules/imap_plaintext/auth.unlang [new file with mode: 0644]
src/tests/modules/imap_plaintext/module.conf [new file with mode: 0644]

index c11f05320c105cbc553b1c3c4ca7065a5347e985..9bf5965a077c99c8ffec65503f0132d5dc7b0ca4 100644 (file)
@@ -58,6 +58,9 @@ addons:
     - debhelper
     - devscripts
     - dh-make
+    - dovecot-core
+    - dovecot-dev
+    - dovecot-imapd
     - doxygen
     - fakeroot
     - firebird-dev
diff --git a/raddb/mods-available/imap b/raddb/mods-available/imap
new file mode 100644 (file)
index 0000000..15287d1
--- /dev/null
@@ -0,0 +1,162 @@
+#  -*- text -*-
+#
+#
+#  $Id$
+
+#######################################################################
+#
+#  = Imap Module
+#
+#  This modules validates a gives user-name user-password combination.
+#  If called in Authenticate, it will look for User-Name and User-Password in Access-Request.
+#  If it can find both a username and password, the module will prepare an imap login request with those credentials.
+#
+#  This module can optionally perform a tls handshake, enabled with require_cert
+#
+
+imap {
+       #
+       #  tls { ... }:: Configure the tls related items that control
+       #  how FreeRADIUS connects to a imap server.
+       #
+       tls {
+               #
+               #  .Certificate validation options
+               #
+               #  Specifies how the certificate(s) presented by the imap server being contacted
+               #  are validated, and which certificates (if any) to send to the imap server.
+               #
+
+               #
+               #  ca_file:: PEM formatted file containing the chain to validate the imap server's cert
+               #
+               #  Should usually contain a concatenation of one or more intermediary CA
+               #  files, shallowest (i.e. the one that signed the imap server's cert) first, and
+               #  deepest (the root CA) last.
+               #
+               #  Providing a complete certificate chain here is the most common way of validating
+               #  the certificate presented by a imap server.
+               #
+#              ca_file              = "${certdir}/cacert.pem"
+
+               #
+               #  ca_issuer_file:: PEM formatted file containing the CA that signed the imap server's cert
+               #
+               #  Specifies the certificate that directly signed the certificate presented by the
+               #  imaps server.
+               #
+               #  This configuration option can be used to prevent certificates passing validation that
+               #  were signed by other intermediary CAs or root CAs in the trusted certificate chain.
+               #
+#              ca_issuer_file     = "${certdir}/caissuer.pem"
+
+               #
+               #  ca_path:: A directory containing multiple root CA certs named by their hash
+               #
+               #  See the OpenSSL documentation for more details:
+               #  - https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_default_verify_paths.html
+               #  - https://www.openssl.org/docs/man1.1.1/man1/c_rehash.html
+               #
+               #  This configuration option should only be used when the imap server being contacted
+               #  is not known ahead of time (using a URL from an external source), and/or the CA used
+               #  to sign the imap server certificate is unknown.
+               #
+#              ca_path              = "${certdir}"
+
+               #
+               #  certificate_file:: PEM formatted file containing the certificate we present to the imap server
+               #
+               #  Specifies a certificate and any intermediary CAs we should send to the imap server.
+               #
+               #  This file should usually contain the client certificate file first, then any
+               #  intermediary signing CAs, shallowest (direct signee of the certificate_file)
+               #  to deepest (signed directly by the root CA).
+               #
+#              certificate_file     = /path/to/radius.pem
+
+               #
+               #  private_key_file:: PEM formatted file containing the private key for the specified certificate_file
+               #
+               #  Must be specified if certificate_file is being used.
+               #
+#              private_key_file     = /path/to/radius.key
+
+               #
+               #  private_key_password:: Password used to decrypt the private key file.
+               #
+               #  Should only be specified in the private_key_file is encrypted.
+               #
+#              private_key_password = "supersecret"
+
+               #
+               #  random_file:: Source of random data used for various cryptographic functions.
+               #
+#              random_file          = /dev/urandom
+
+               #
+               #  check_cert:: Server certificate verification requirements.
+               #
+               #  May be one of:
+               #
+               #  [options="header,autowidth"]
+               #  |===
+               #  | Option | Description
+               #  | `no`   | Server certificate can be signed by any CA or be self-signed.
+               #  | `yes`  | Server certificate must be issued by one of the trusted CAs.
+               #  |===
+               #
+               #  Default is `yes`
+               #
+#              check_cert = no
+
+               #
+               #  request_cert:: To what degree curl should use ssl
+               #
+               #  May be one of:
+               #
+               #  [options="header,autowidth"]
+               #  |===
+               #  | Option | Description
+               #  | `demand`   | Require ssl validation to accept login
+               #  | `never`  | Do not try to establish ssl connection
+               #  | `allow`  | Try to establish ssl connection, continue even if it cannot
+               #  |===
+               #
+               #  default is 'allow'
+               #
+#              require_cert = allow
+
+               #
+               #  check_cert_cn:: Server certificate CN verification requirements.
+               #
+               #  May be one of:
+               #
+               #  [options="header,autowidth"]
+               #  |===
+               #  | Option | Description
+               #  | `no`   | Server certificate CN can be any value.
+               #  | `yes`  | Server certificate CN matches the host in the URI.
+               #  |===
+               #
+               #  Default is `yes`
+               #
+#              check_cert_cn = no
+
+               #
+               #  extract_cert_attrs:: Extract OIDs from presented certificates as OIDs.
+               #
+               #  Default is `no`
+               #
+#              extract_cert_attrs = no
+       }
+
+       #
+       #  imap_uri:: It will open a connection to the server specified in and send the tls
+       #  request here
+       #
+       imap_uri = "imap://127.0.0.1:1430/"
+       #
+       #  timeout:: Number of milliseconds FreeRADIUS will wait for a response
+       #
+       timeout = 5000
+}
index 54b270e932e5527d286a51748c9b0d9e3f7361e5..75f8c83eef557633c4b4c975dce4cfa8cee74be7 100755 (executable)
@@ -66,6 +66,7 @@ echo "Setting up fixtures"
 
 for i in \
     postgresql-setup.sh \
+    imap-setup.sh \
     mysql-setup.sh \
     ldap-setup.sh \
     redis-setup.sh; do
diff --git a/scripts/travis/dovecot/fr_dovecot.conf b/scripts/travis/dovecot/fr_dovecot.conf
new file mode 100644 (file)
index 0000000..b4e88d6
--- /dev/null
@@ -0,0 +1,20 @@
+protocols = imap 
+
+# Disable SSL for now.
+disable_plaintext_auth = no
+
+# Authentication configuration:
+auth_verbose = yes
+auth_mechanisms = plain
+
+login_trusted_networks = 127.0.0.1 
+
+# Disable delays for bad login attempts
+service anvil {
+       unix_listener anvil-auth-penalty {
+               mode = 0
+       }
+       chroot = 
+}
+
+# ALL CODE BELOW THIS IS ADDED BY THE SHELL SCRIPT scripts/travis/imap-setup.sh
diff --git a/scripts/travis/imap-setup.sh b/scripts/travis/imap-setup.sh
new file mode 100755 (executable)
index 0000000..c431efa
--- /dev/null
@@ -0,0 +1,191 @@
+#!/bin/sh -e
+
+#
+# ### This is a script to setup a dovecot imap server for testing rlm_imap
+#
+
+#
+# Declare the important path variables
+#
+
+# Directories For storing dovecot setup files
+BASEDIR=$(git rev-parse --show-toplevel)
+BUILDDIR="${BASEDIR}/build/ci/dovecot"
+CERTDIR="${BASEDIR}/raddb/certs/rsa"
+ETCDIR="${BUILDDIR}/etc"
+
+# Directories for running dovecot 
+RUNDIR="${BUILDDIR}/run"
+TLSRUNDIR="${BUILDDIR}/tls_run"
+MAILDIR="${ETCDIR}/dovecot_mail"
+LOGDIR="${BUILDDIR}/log"
+
+# Important files for running dovecot 
+CONF="${ETCDIR}/dovecot.conf"
+TLSCONF="${ETCDIR}/tls_dovecot.conf"
+PASSPATH="${ETCDIR}/dovecot.passwd"
+
+# The path to the two log files
+LOGPATH="${LOGDIR}/dovecot.log"
+LOGINFOPATH="${LOGDIR}/dovecot-info.log"
+
+# Used for creating `imap-stop.sh`
+TRAVISDIR="${BASEDIR}/scripts/travis"
+
+#
+# Create all the necessary files
+#
+
+# Make the build directory
+mkdir -p "${BUILDDIR}"
+
+# Create folders for running, logging, and all parents
+mkdir -p "${ETCDIR}"
+mkdir -p "${LOGDIR}"
+mkdir -p "${RUNDIR}"
+mkdir -p "${TLSRUNDIR}"
+mkdir -p "${MAILDIR}"
+
+# Make sure there is a password file
+touch  "${PASSPATH}"
+
+# Make sure there are log files
+touch "${LOGPATH}"
+touch "${LOGINFOPATH}" 
+
+#
+# Add users to the password file
+#
+
+# Generate passwords for the users
+USER1P=$(doveadm pw -p test1 -s CRYPT)
+USER2P=$(doveadm pw -p test2 -s CRYPT)
+USER3P=$(doveadm pw -p test3 -s CRYPT)
+
+# Add user password combinations
+echo "\
+user1:${USER1P}:::::: 
+" >"${PASSPATH}"
+
+echo "\
+user2:${USER2P}:::::: 
+" >>"${PASSPATH}"
+
+echo "\
+user3:${USER3P}:::::: 
+" >>"${PASSPATH}"
+
+#
+# Configure instance specific dovecot information
+#
+
+# Load the template config file for both dovecot instances
+cp "${TRAVISDIR}/dovecot/fr_dovecot.conf" "${CONF}"
+cp "${TRAVISDIR}/dovecot/fr_dovecot.conf" "${TLSCONF}"
+
+# Configure the specifics for the non_tls dovecot server
+echo "
+instance_name = "fr_dovecot"
+
+ssl = no
+
+base_dir = ${RUNDIR}
+
+service imap-login {
+       process_min_avail = 16
+       user = ${USER} 
+       chroot =
+       inet_listener imap {
+               port = 1430
+       }
+} \
+" >> "${CONF}"
+
+# Configure the specifics for the tls dovecot server
+echo "
+instance_name = "fr_tls_dovecot"
+
+
+base_dir = ${TLSRUNDIR}
+
+service imap-login {
+       process_min_avail = 16
+       user = ${USER}
+       chroot =
+       inet_listener imap {
+               port = 1431
+       }
+       inet_listener imaps {
+               port = 1432
+       }
+} 
+# TLS specific configurations
+ssl = required
+ssl_protocols = !SSLv3
+ssl_cert = <${CERTDIR}/server.pem
+ssl_key = <${CERTDIR}/server.key
+ssl_key_password = whatever
+ssl_ca = <${CERTDIR}/ca.pem
+
+verbose_ssl = yes
+
+# ssl_client_ca_file = <${CERTDIR}/ca.pem
+# ssl_verify_client_cert = yes
+# auth_ssl_require_client_cert=yes
+" >> "${TLSCONF}"
+
+# Make sure there is a clean imap-stop.sh file
+> ${TRAVISDIR}/imap-stop.sh
+
+#
+# Add system specific dovecot information
+#
+for CONFPATH in $CONF $TLSCONF
+do
+# Add the path to the log files
+echo "
+log_path = ${LOGPATH}
+info_log_path = ${LOGINFOPATH} \
+" >> "${CONFPATH}"
+
+# Add the Password File to the config
+echo  "
+passdb {
+       driver = passwd-file
+       args = ${PASSPATH}
+}" >> "${CONFPATH}"
+
+# Add the mail directory to the config
+echo "
+mail_location = maildir:${MAILDIR} \
+" >> "${CONFPATH}"
+
+# Set user for permissions
+echo "
+default_internal_user = ${USER}
+default_login_user = ${USER} \
+" >> "${CONFPATH}"
+
+#Configure the user mailbox privileges
+echo "
+userdb {
+       driver = static
+       args = uid=${USER} gid=${USER}
+} \
+" >> "${CONFPATH}"
+
+# Run the imap server
+echo "Starting a dovecot imap server at ${CONFPATH}"
+
+if ! dovecot -c ${CONFPATH}; then
+       echo "The server failed to start up. Here is fr_dovecot.log"
+       cat ${LOGPATH}
+       echo "And here is fr_dovecot-info.log"
+       cat ${LOGINFOPATH}
+fi
+
+echo "dovecot -c ${CONFPATH} stop" >> "${TRAVISDIR}/imap-stop.sh"
+
+done
+
+exit 0
index b20f158e1e4392c77f83edbba41c7fa317126261..e203b0559a39e251086f09fd820b1403e8263398 100644 (file)
@@ -37,6 +37,13 @@ static fr_dict_autoload_t curl_dict[] = {
        { NULL }
 };
 
+fr_table_num_sorted_t const fr_curl_sslcode_table[] = {
+       {"allow",       CURLUSESSL_TRY          },
+       {"demand",      CURLUSESSL_ALL          },
+       {"never",       CURLUSESSL_NONE         },
+};
+static size_t fr_curl_sslcode_table_len = NUM_ELEMENTS(fr_curl_sslcode_table);
+
 CONF_PARSER fr_curl_tls_config[] = {
        { FR_CONF_OFFSET("ca_file", FR_TYPE_FILE_INPUT, fr_curl_tls_t, ca_file) },
        { FR_CONF_OFFSET("ca_issuer_file", FR_TYPE_FILE_INPUT, fr_curl_tls_t, ca_issuer_file) },
@@ -45,6 +52,9 @@ CONF_PARSER fr_curl_tls_config[] = {
        { FR_CONF_OFFSET("private_key_file", FR_TYPE_FILE_INPUT, fr_curl_tls_t, private_key_file) },
        { FR_CONF_OFFSET("private_key_password", FR_TYPE_STRING | FR_TYPE_SECRET, fr_curl_tls_t, private_key_password) },
        { FR_CONF_OFFSET("random_file", FR_TYPE_STRING, fr_curl_tls_t, random_file) },
+       { FR_CONF_OFFSET("require_cert", FR_TYPE_VOID, fr_curl_tls_t, require_cert), 
+               .func = cf_table_parse_int32, .uctx = &(cf_table_parse_ctx_t){ .table = fr_curl_sslcode_table, .len = &fr_curl_sslcode_table_len }, 
+       .dflt = "allow" },
        { FR_CONF_OFFSET("check_cert", FR_TYPE_BOOL, fr_curl_tls_t, check_cert), .dflt = "yes" },
        { FR_CONF_OFFSET("check_cert_cn", FR_TYPE_BOOL, fr_curl_tls_t, check_cert_cn), .dflt = "yes" },
        { FR_CONF_OFFSET("extract_cert_attrs", FR_TYPE_BOOL, fr_curl_tls_t, extract_cert_attrs), .dflt = "no" },
@@ -123,6 +133,7 @@ int fr_curl_easy_tls_init(fr_curl_io_request_t *randle, fr_curl_tls_t const *con
        if (conf->ca_issuer_file) FR_CURL_SET_OPTION(CURLOPT_ISSUERCERT, conf->ca_issuer_file);
        if (conf->ca_path) FR_CURL_SET_OPTION(CURLOPT_CAPATH, conf->ca_path);
        if (conf->random_file) FR_CURL_SET_OPTION(CURLOPT_RANDOM_FILE, conf->random_file);
+       FR_CURL_SET_OPTION(CURLOPT_USE_SSL, conf->require_cert);
 
        FR_CURL_SET_OPTION(CURLOPT_SSL_VERIFYPEER, (conf->check_cert == true) ? 1L : 0L);
        FR_CURL_SET_OPTION(CURLOPT_SSL_VERIFYHOST, (conf->check_cert_cn == true) ? 2L : 0L);
index 109d8c7f0a59cd729d19d72cc0ccbfc16dde2d00..63f64f6aa726582c3855f524b85de170bf972613 100644 (file)
@@ -77,6 +77,7 @@ typedef struct {
        char const              *ca_issuer_file;
        char const              *ca_path;
        char const              *random_file;
+       long                    *require_cert;
        bool                    check_cert;
        bool                    check_cert_cn;
        bool                    extract_cert_attrs;
diff --git a/src/modules/rlm_imap/README.md b/src/modules/rlm_imap/README.md
new file mode 100644 (file)
index 0000000..1ff0174
--- /dev/null
@@ -0,0 +1,8 @@
+# rlm_imap
+## Metadata
+<dl>
+  <dt>category</dt><dd>authentication</dd>
+</dl>
+
+## Summary
+This module authenticates users against an IMAP server
diff --git a/src/modules/rlm_imap/all.mk b/src/modules/rlm_imap/all.mk
new file mode 100644 (file)
index 0000000..24d368f
--- /dev/null
@@ -0,0 +1,12 @@
+#  Check to see if we libfreeradius-curl, as that's a hard dependency
+#  which in turn depends on json-c.
+TARGETNAME     :=
+-include $(top_builddir)/src/lib/curl/all.mk
+TARGET         :=
+
+ifneq "$(TARGETNAME)" ""
+TARGET         := rlm_imap.a
+TGT_PREREQS    += libfreeradius-curl.a
+endif
+
+SOURCES                := rlm_imap.c
diff --git a/src/modules/rlm_imap/rlm_imap.c b/src/modules/rlm_imap/rlm_imap.c
new file mode 100644 (file)
index 0000000..5ba89b7
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ *   This program is is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or (at
+ *   your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_imap.c
+ * @brief imap server authentication.
+ *
+ * @copyright 2020 The FreeRADIUS server project
+ * @copyright 2020 Network RADIUS SARL <legal@networkradius.com>
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/server/base.h>
+#include <freeradius-devel/server/module.h>
+#include <freeradius-devel/curl/base.h>
+/*
+ *     A mapping of configuration file names to internal variables.
+ */
+static fr_dict_t       const           *dict_radius; /*dictionary for radius protocol*/
+static fr_dict_t       const           *dict_freeradius; /*internal dictionary for server*/
+
+static fr_dict_attr_t  const           *attr_auth_type;
+
+static fr_dict_attr_t  const           *attr_user_password;
+static fr_dict_attr_t  const           *attr_user_name;
+
+extern fr_dict_autoload_t rlm_imap_dict[];
+fr_dict_autoload_t rlm_imap_dict[] = {
+       { .out = &dict_radius, .proto = "radius" },
+       { .out = &dict_freeradius, .proto = "freeradius" },
+       { NULL }
+};
+
+extern fr_dict_attr_autoload_t rlm_imap_dict_attr[];
+fr_dict_attr_autoload_t rlm_imap_dict_attr[] = {
+       { .out = &attr_auth_type, .name = "Auth-Type", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
+       { .out = &attr_user_name, .name = "User-Name", .type = FR_TYPE_STRING, .dict = &dict_radius },
+       { .out = &attr_user_password, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius },
+       { NULL },
+};
+
+typedef struct {
+       fr_dict_enum_t                  *auth_type;
+       char const                      *name;
+       char const                      *imap_uri;      //!<URL of imap server
+       fr_time_delta_t                 timeout;        //!<Timeout for connection and server response
+       fr_curl_tls_t                   tls;
+} rlm_imap_t;
+
+typedef struct {
+       rlm_imap_t const                *inst;        //!< Instance of rlm_imap.
+       fr_curl_handle_t                *mhandle;    //!< Thread specific multi handle.  Serves as the dispatch and coralling structure for imap requests.
+} rlm_imap_thread_t;
+
+static const CONF_PARSER module_config[] = {
+       { FR_CONF_OFFSET("imap_uri", FR_TYPE_STRING, rlm_imap_t, imap_uri) },
+       { FR_CONF_OFFSET("timeout",FR_TYPE_TIME_DELTA, rlm_imap_t, timeout) },
+       { FR_CONF_OFFSET("tls", FR_TYPE_SUBSECTION, rlm_imap_t, tls), .subcs = (void const *) fr_curl_tls_config },//!<loading the tls values
+       CONF_PARSER_TERMINATOR
+};
+
+/*
+ * Check to see that we have a username and password
+ * print out the username
+ * if a value is missing return NOOP
+ */
+static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request)
+{
+       VALUE_PAIR                      *vp;
+       rlm_imap_t                      *inst   = instance;
+
+       vp = fr_pair_find_by_da(request->packet->vps, attr_user_name, TAG_ANY);
+       if (vp == NULL) return RLM_MODULE_NOOP;
+       RDEBUG2("Authorizing: %pP", vp);
+       vp = fr_pair_find_by_da(request->packet->vps, attr_user_password, TAG_ANY);
+       if (vp == NULL) return RLM_MODULE_NOOP;
+       /*
+        *Check to see if there is already a section that will be working on the request
+        */
+       if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) return RLM_MODULE_NOOP;
+
+       return RLM_MODULE_OK;
+}
+/*
+ *Called when the IMAP server responds
+ *It checks if the response was CURLE_OK
+ *If it wasn't we returns REJECT, if it was we returns OK
+*/
+static rlm_rcode_t CC_HINT(nonnull) mod_authenticate_resume(void *instance, UNUSED void *thread, REQUEST *request, void *rctx)
+{
+       fr_curl_io_request_t            *randle   = rctx;
+       rlm_imap_t                      *inst   = instance;
+       fr_curl_tls_t                   *tls;
+       long                            curl_out;
+       long                            curl_out_valid;
+
+       tls = &inst->tls;
+       
+       curl_out_valid = curl_easy_getinfo(randle->candle, CURLINFO_SSL_VERIFYRESULT, &curl_out);
+       if(curl_out_valid == CURLE_OK){
+               RDEBUG2("server certificate %s verified\n", curl_out? "was":"not");
+       } else {
+               RDEBUG2("server certificate result not found");
+       }
+
+       if (randle->result != CURLE_OK) {
+               talloc_free(randle);
+               return RLM_MODULE_REJECT;
+       }
+
+       if (tls->extract_cert_attrs) fr_curl_response_certinfo(request, randle);
+
+       talloc_free(randle);
+       return RLM_MODULE_OK;
+}
+/*
+ * Checks that there is a User-Name and User-Password field in the request
+ * Checks that User-Password is not Blank
+ * Sets the: username, password
+ * website URI
+ * timeout information
+ * and TLS information
+ * Then it queues the request and yeilds until a response is given
+ * When it responds, mod_authenticate_resume is called
+ */
+static rlm_rcode_t CC_HINT(nonnull(1,2)) mod_authenticate(void *instance, void *thread, REQUEST *request)
+{
+       VALUE_PAIR const                *username;
+       VALUE_PAIR const                *password;
+       rlm_imap_t                      *inst = instance;
+       
+       rlm_imap_thread_t               *t = thread;
+       fr_curl_io_request_t            *randle;
+    
+       fr_time_delta_t                 timeout = inst->timeout;
+       char const                      *imap_uri = inst->imap_uri;
+
+       randle = fr_curl_io_request_alloc(request);
+       if (!randle){
+       error:
+               return RLM_MODULE_FAIL;
+       }
+       
+       username = fr_pair_find_by_da(request->packet->vps, attr_user_name, TAG_ANY);
+       password = fr_pair_find_by_da(request->packet->vps, attr_user_password, TAG_ANY);
+       
+       if (!username) {
+               REDEBUG("Attribute \"User-Name\" is required for authentication");
+               return RLM_MODULE_INVALID;
+       }
+       if (!password) {
+               RDEBUG2("Attribute \"User-Password\" is required for authentication");
+               return RLM_MODULE_INVALID;
+       }
+       if (password->vp_length == 0) {
+               RDEBUG2("\"User-Password\" must not be empty");
+               return RLM_MODULE_INVALID;
+       }
+       
+       FR_CURL_SET_OPTION(CURLOPT_USERNAME, username->vp_strvalue);
+       FR_CURL_SET_OPTION(CURLOPT_PASSWORD, password->vp_strvalue);
+       
+       FR_CURL_SET_OPTION(CURLOPT_DEFAULT_PROTOCOL, "imap");
+       FR_CURL_SET_OPTION(CURLOPT_URL, imap_uri);
+    
+       FR_CURL_SET_OPTION(CURLOPT_CONNECTTIMEOUT_MS, timeout);
+       FR_CURL_SET_OPTION(CURLOPT_TIMEOUT_MS, timeout);
+
+       FR_CURL_SET_OPTION(CURLOPT_VERBOSE, 1L);
+
+       if(fr_curl_easy_tls_init(randle, &inst->tls) != 0) return RLM_MODULE_INVALID;
+    
+       if(fr_curl_io_request_enqueue(t->mhandle, request, randle)) return RLM_MODULE_INVALID;
+    
+       return unlang_module_yield(request, mod_authenticate_resume, NULL, randle);
+}
+
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
+{
+       char const                      *name;
+       rlm_imap_t                      *inst = instance;
+       name = cf_section_name2(conf);
+       if (!name) name = cf_section_name1(conf);
+       inst->name = name;
+       if (fr_dict_enum_add_name_next(fr_dict_attr_unconst(attr_auth_type), inst->name) < 0) {
+               PERROR("Failed adding %s alias", attr_auth_type->name);
+               return -1;
+       }
+       inst->auth_type = fr_dict_enum_by_name(attr_auth_type, inst->name, -1);
+       fr_assert(inst->auth_type);
+       return 0;
+}
+/*
+ * Initialize global curl instance
+ */
+static int mod_load(void)
+{
+       if (fr_curl_init() < 0) return -1;
+       return 0;
+}
+/*
+ * Close global curl instance
+ */
+static void mod_unload(void)
+{
+       fr_curl_free();
+}
+/*
+ * Initialie a new thread with a curl instance
+ */
+static int mod_thread_instantiate(UNUSED CONF_SECTION const *conf, void *instance, fr_event_list_t *el, void *thread)
+{
+       rlm_imap_thread_t               *t = thread;
+       fr_curl_handle_t                *mhandle;
+
+       t->inst = instance;
+   
+       mhandle = fr_curl_io_init(t, el, false);
+       if (!mhandle) return -1;
+       t->mhandle = mhandle;
+           
+       return 0;
+}
+/*
+ * Close the thread and free the memory
+ */
+static int mod_thread_detach(UNUSED fr_event_list_t *el, void *thread)
+{
+    rlm_imap_thread_t    *t = thread;
+    talloc_free(t->mhandle);    /* Ensure this is shutdown before the pool */
+    return 0;
+}
+/*
+ * External module to call rlm_imap's functions
+ */
+extern module_t rlm_imap;
+module_t rlm_imap = {
+       .magic                  = RLM_MODULE_INIT,
+       .name                   = "imap",
+       .type                   = RLM_TYPE_THREAD_SAFE,
+       .inst_size              = sizeof(rlm_imap_t),
+       .thread_inst_size       = sizeof(rlm_imap_thread_t),
+       .config                 = module_config,
+       .onload                 = mod_load,
+       .unload                 = mod_unload,
+       .bootstrap              = mod_bootstrap,
+       .thread_instantiate     = mod_thread_instantiate,
+       .thread_detach          = mod_thread_detach,
+    
+       .methods = {
+               [MOD_AUTHENTICATE]      = mod_authenticate,
+               [MOD_AUTHORIZE]         = mod_authorize,
+       },
+};
index 38fbecdfb7c3c9d9db44dbffe271560c14676c7d..e02ba240dd7a982e050ae2ac43d87bce615d7108 100644 (file)
@@ -12,9 +12,12 @@ FILES := $(sort $(patsubst $(DIR)/%.unlang,%,$(call FIND_FILES_SUFFIX,$(DIR),*.u
 
 #
 #  Remove things which are known to fail on travis.
+#  Or which are known to have long runtimes 
 #
 ifeq "$(TRAVIS)" "1"
-FILES_SKIP := $(filter icmp/%,$(FILES))
+  FILES_SKIP := $(filter icmp/%,$(FILES))
+else ifneq "$(RUN_SLOW_TESTS)" "1"
+  FILES_SKIP += $(filter imap/auth_bad%,$(FILES))
 endif
 
 #
diff --git a/src/tests/modules/imap/all.mk b/src/tests/modules/imap/all.mk
new file mode 100644 (file)
index 0000000..fd021dc
--- /dev/null
@@ -0,0 +1,3 @@
+#
+# Test the "imap" module
+#
diff --git a/src/tests/modules/imap/auth_bad_pw.attrs b/src/tests/modules/imap/auth_bad_pw.attrs
new file mode 100644 (file)
index 0000000..356b7e1
--- /dev/null
@@ -0,0 +1,11 @@
+#
+#  Input packet
+#
+User-Name = 'user2'
+User-Password = 'test_fail'
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Reject
+
diff --git a/src/tests/modules/imap/auth_bad_pw.unlang b/src/tests/modules/imap/auth_bad_pw.unlang
new file mode 100644 (file)
index 0000000..6c5ddfa
--- /dev/null
@@ -0,0 +1,9 @@
+imap.authenticate
+if (reject) {
+    update control {
+        &Auth-Type := Accept
+    }
+}
+else {
+    reject
+}
diff --git a/src/tests/modules/imap/auth_bad_usr.attrs b/src/tests/modules/imap/auth_bad_usr.attrs
new file mode 100644 (file)
index 0000000..f132347
--- /dev/null
@@ -0,0 +1,11 @@
+#
+#  Input packet
+#
+User-Name = 'fail_user'
+User-Password = 'test1'
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Reject
+
diff --git a/src/tests/modules/imap/auth_bad_usr.unlang b/src/tests/modules/imap/auth_bad_usr.unlang
new file mode 100644 (file)
index 0000000..6c5ddfa
--- /dev/null
@@ -0,0 +1,9 @@
+imap.authenticate
+if (reject) {
+    update control {
+        &Auth-Type := Accept
+    }
+}
+else {
+    reject
+}
diff --git a/src/tests/modules/imap/auth_plaintext.attrs b/src/tests/modules/imap/auth_plaintext.attrs
new file mode 100644 (file)
index 0000000..e36f788
--- /dev/null
@@ -0,0 +1,11 @@
+#
+#  Input packet
+#
+User-Name = 'user1'
+User-Password = 'test1'
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/imap/auth_plaintext.unlang b/src/tests/modules/imap/auth_plaintext.unlang
new file mode 100644 (file)
index 0000000..a400bbb
--- /dev/null
@@ -0,0 +1,9 @@
+imap.authenticate
+if (ok) {
+    update control {
+        &Auth-Type := Accept
+    }
+}
+else {
+    reject
+}
diff --git a/src/tests/modules/imap/imap_bad_tls/all.mk b/src/tests/modules/imap/imap_bad_tls/all.mk
new file mode 100644 (file)
index 0000000..fd021dc
--- /dev/null
@@ -0,0 +1,3 @@
+#
+# Test the "imap" module
+#
diff --git a/src/tests/modules/imap/imap_bad_tls/auth_bad_tls.attrs b/src/tests/modules/imap/imap_bad_tls/auth_bad_tls.attrs
new file mode 100644 (file)
index 0000000..9c27244
--- /dev/null
@@ -0,0 +1,11 @@
+
+#  Input packet
+#
+User-Name = 'user1'
+User-Password = 'test1'
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Reject
+
diff --git a/src/tests/modules/imap/imap_bad_tls/auth_bad_tls.unlang b/src/tests/modules/imap/imap_bad_tls/auth_bad_tls.unlang
new file mode 100644 (file)
index 0000000..055617a
--- /dev/null
@@ -0,0 +1,9 @@
+imap_bad_tls.authenticate
+if (ok) {
+    update control {
+        &Auth-Type := Accept
+    }
+}
+else {
+    reject
+}
diff --git a/src/tests/modules/imap/imap_bad_tls/module.conf b/src/tests/modules/imap/imap_bad_tls/module.conf
new file mode 100644 (file)
index 0000000..5edc219
--- /dev/null
@@ -0,0 +1,30 @@
+#IMAP unit test config
+
+imap imap_bad_tls{
+       imap_uri        = "127.0.0.1:1431"
+       timeout         = 1000
+
+       tls {
+               ca_file                 = "$ENV{top_srcdir}raddb/certs/rsa/client.pem"
+
+                       certificate_file        = "$ENV{top_srcdir}raddb/certs/rsa/client.pem" 
+               private_key_file        = "$ENV{top_srcdir}raddb/certs/rsa/client.key"
+
+                       private_key_password    = "whatever"
+
+                       random_file             = /dev/urandom
+
+                check_cert_cn          = no
+               
+               require_cert            = demand
+
+                       extract_cert_attrs      = yes
+        }
+       
+}
+
+
+
+
+
diff --git a/src/tests/modules/imap/imap_opt_tls/all.mk b/src/tests/modules/imap/imap_opt_tls/all.mk
new file mode 100644 (file)
index 0000000..fd021dc
--- /dev/null
@@ -0,0 +1,3 @@
+#
+# Test the "imap" module
+#
diff --git a/src/tests/modules/imap/imap_opt_tls/auth_try_tls.attrs b/src/tests/modules/imap/imap_opt_tls/auth_try_tls.attrs
new file mode 100644 (file)
index 0000000..e36f788
--- /dev/null
@@ -0,0 +1,11 @@
+#
+#  Input packet
+#
+User-Name = 'user1'
+User-Password = 'test1'
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/imap/imap_opt_tls/auth_try_tls.unlang b/src/tests/modules/imap/imap_opt_tls/auth_try_tls.unlang
new file mode 100644 (file)
index 0000000..5f82167
--- /dev/null
@@ -0,0 +1,25 @@
+imap_try_tls.authenticate
+
+# if (&request:cert-attrs.Issuer) {
+#        test_fail
+#}
+# else {
+#        test_pass
+#}
+
+
+if (ok) {
+    update control {
+        &Auth-Type := Accept
+    }
+}
+else {
+    reject
+}
+
+if (&request:TLS-Cert-Issuer =~ /@example\.org/) {
+        test_pass
+} else {
+       test_fail
+}
+
diff --git a/src/tests/modules/imap/imap_opt_tls/module.conf b/src/tests/modules/imap/imap_opt_tls/module.conf
new file mode 100644 (file)
index 0000000..62ec0ed
--- /dev/null
@@ -0,0 +1,25 @@
+#IMAP unit test config
+
+imap imap_try_tls {
+       imap_uri        = "127.0.0.1:1431"
+       timeout         = 1000
+
+       tls {
+               ca_file                 = "$ENV{top_srcdir}raddb/certs/rsa/ca.pem"
+
+               certificate_file        = "$ENV{top_srcdir}raddb/certs/rsa/client.pem" 
+
+               private_key_file        = "$ENV{top_srcdir}raddb/certs/rsa/client.key"
+
+               private_key_password    = "whatever"
+
+               random_file             = /dev/urandom
+               
+               check_cert_cn           = no
+               
+               require_cert            = allow
+
+               extract_cert_attrs      = yes
+        }
+       
+}
diff --git a/src/tests/modules/imap/imap_tls/all.mk b/src/tests/modules/imap/imap_tls/all.mk
new file mode 100644 (file)
index 0000000..fd021dc
--- /dev/null
@@ -0,0 +1,3 @@
+#
+# Test the "imap" module
+#
diff --git a/src/tests/modules/imap/imap_tls/auth_tls.attrs b/src/tests/modules/imap/imap_tls/auth_tls.attrs
new file mode 100644 (file)
index 0000000..e36f788
--- /dev/null
@@ -0,0 +1,11 @@
+#
+#  Input packet
+#
+User-Name = 'user1'
+User-Password = 'test1'
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/imap/imap_tls/auth_tls.unlang b/src/tests/modules/imap/imap_tls/auth_tls.unlang
new file mode 100644 (file)
index 0000000..393266b
--- /dev/null
@@ -0,0 +1,25 @@
+imap_tls.authenticate
+
+# if (&request:cert-attrs.Issuer) {
+#        test_fail
+#}
+# else {
+#        test_pass
+#}
+
+
+if (ok) {
+    update control {
+        &Auth-Type := Accept
+    }
+}
+else {
+    reject
+}
+
+if (&request:TLS-Cert-Issuer =~ /@example\.org/) {
+        test_pass
+} else {
+       test_fail
+}
+
diff --git a/src/tests/modules/imap/imap_tls/module.conf b/src/tests/modules/imap/imap_tls/module.conf
new file mode 100644 (file)
index 0000000..dd41ea5
--- /dev/null
@@ -0,0 +1,25 @@
+#IMAP unit test config
+
+imap imap_tls {
+       imap_uri        = "127.0.0.1:1431"
+       timeout         = 1000
+
+       tls {
+               ca_file                 = "$ENV{top_srcdir}raddb/certs/rsa/ca.pem"
+
+               certificate_file        = "$ENV{top_srcdir}raddb/certs/rsa/client.pem" 
+
+               private_key_file        = "$ENV{top_srcdir}raddb/certs/rsa/client.key"
+
+               private_key_password    = "whatever"
+                
+               random_file             = /dev/urandom
+               
+               check_cert_cn           = no
+               
+               require_cert            = demand
+
+               extract_cert_attrs      = yes
+        }
+       
+}
diff --git a/src/tests/modules/imap/module.conf b/src/tests/modules/imap/module.conf
new file mode 100644 (file)
index 0000000..32d4f43
--- /dev/null
@@ -0,0 +1,5 @@
+#IMAP unit test config
+imap {
+       imap_uri        = "127.0.0.1:1430"
+       timeout         = 1000
+}
diff --git a/src/tests/modules/imap_plaintext/all.mk b/src/tests/modules/imap_plaintext/all.mk
new file mode 100644 (file)
index 0000000..876f4d0
--- /dev/null
@@ -0,0 +1,3 @@
+#
+# Test the "imap_plaintext" module
+#
diff --git a/src/tests/modules/imap_plaintext/auth.attrs b/src/tests/modules/imap_plaintext/auth.attrs
new file mode 100644 (file)
index 0000000..e36f788
--- /dev/null
@@ -0,0 +1,11 @@
+#
+#  Input packet
+#
+User-Name = 'user1'
+User-Password = 'test1'
+
+#
+#  Expected answer
+#
+Packet-Type == Access-Accept
+
diff --git a/src/tests/modules/imap_plaintext/auth.unlang b/src/tests/modules/imap_plaintext/auth.unlang
new file mode 100644 (file)
index 0000000..78ca91d
--- /dev/null
@@ -0,0 +1,9 @@
+imap_plaintext.authenticate
+if (ok) {
+    update control {
+        &Auth-Type := Accept
+    }
+}
+else {
+    reject
+}
diff --git a/src/tests/modules/imap_plaintext/module.conf b/src/tests/modules/imap_plaintext/module.conf
new file mode 100644 (file)
index 0000000..9ebd21d
--- /dev/null
@@ -0,0 +1,5 @@
+imap_plaintext {
+       imap_uri = "127.0.0.1:1431"
+       timeout = 1000
+}
+