#include "str.h"
#include "llist.h"
#include "array.h"
+#include "base64.h"
#include "path-util.h"
#include "hostpid.h"
#include "ioloop.h"
#include "iostream-ssl-test.h"
#include "iostream-openssl.h"
#include "connection.h"
+#include "password-scheme.h"
+#include "dsasl-client.h"
+#include "sasl-server.h"
#include "test-common.h"
#include "test-subprocess.h"
#include "settings.h"
unsigned int max_pending;
bool unknown_size;
enum test_ssl_mode ssl_mode;
+
+ const char *sasl_mech;
+ const char *authid;
+ const char *authzid;
+ const char *password;
} tset;
static struct ip_addr bind_ip;
struct client *prev, *next;
struct smtp_server_connection *smtp_conn;
+
+ struct {
+ struct smtp_server_cmd_ctx *cmd;
+
+ struct sasl_server_req_ctx sasl_req;
+ const char *password_scheme;
+ sasl_server_passdb_callback_t *passdb_callback;
+ struct timeout *to_passdb;
+
+ const char *authid;
+ const char *authzid;
+ const char *realm;
+ const char *password;
+ } auth;
};
struct client_transaction {
struct istream *payload, *file;
};
+static struct sasl_server *sasl_server;
+static struct sasl_server_instance *sasl_server_inst;
static struct smtp_server *smtp_server;
static struct io *io_listen;
return client_transaction_read_more(ctrans);
}
+/* authentication */
+
+static int
+test_server_conn_cmd_helo(void *conn_ctx ATTR_UNUSED,
+ struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_cmd_helo *data)
+{
+ struct smtp_server_reply *reply;
+
+ reply = smtp_server_reply_create_ehlo(cmd->cmd);
+ if (data->helo.old_smtp) {
+ smtp_server_reply_submit(reply);
+ return 1;
+ }
+
+ smtp_server_reply_ehlo_add_8bitmime(reply);
+ if (tset.sasl_mech != NULL) T_BEGIN {
+ struct sasl_server_mech_iter *mech_iter;
+ string_t *param = t_str_new(128);
+
+ mech_iter =
+ sasl_server_instance_mech_iter_new(sasl_server_inst);
+ while (sasl_server_mech_iter_next(mech_iter)) {
+ if (str_len(param) > 0)
+ str_append_c(param, ' ');
+ str_append(param, mech_iter->name);
+ }
+ sasl_server_mech_iter_free(&mech_iter);
+
+ if (str_len(param) > 0) {
+ smtp_server_reply_ehlo_add_param(reply, "AUTH", "%s",
+ str_c(param));
+ }
+ } T_END;
+ smtp_server_reply_ehlo_add_binarymime(reply);
+ smtp_server_reply_ehlo_add_chunking(reply);
+ smtp_server_reply_ehlo_add_dsn(reply);
+ smtp_server_reply_ehlo_add_enhancedstatuscodes(reply);
+ smtp_server_reply_ehlo_add_starttls(reply);
+ smtp_server_reply_ehlo_add_pipelining(reply);
+ smtp_server_reply_ehlo_add_vrfy(reply);
+ smtp_server_reply_submit(reply);
+ return 1;
+}
+
+static bool
+test_server_sasl_set_authid(struct sasl_server_req_ctx *rctx,
+ enum sasl_server_authid_type authid_type,
+ const char *authid)
+{
+ struct client *client =
+ container_of(rctx, struct client, auth.sasl_req);
+
+ test_assert(authid_type == SASL_SERVER_AUTHID_TYPE_USERNAME);
+
+ client->auth.authid = p_strdup(client->pool, authid);
+ return TRUE;
+}
+
+static bool
+test_server_sasl_set_authzid(struct sasl_server_req_ctx *rctx,
+ const char *authzid)
+{
+ struct client *client =
+ container_of(rctx, struct client, auth.sasl_req);
+
+ client->auth.authzid = p_strdup(client->pool, authzid);
+ return TRUE;
+}
+
+static void
+test_server_sasl_set_realm(struct sasl_server_req_ctx *rctx,
+ const char *realm)
+{
+ struct client *client =
+ container_of(rctx, struct client, auth.sasl_req);
+
+ client->auth.realm = p_strdup(client->pool, realm);
+}
+
+static void test_server_sasl_passdb_result(struct client *client)
+{
+ sasl_server_passdb_callback_t *callback = client->auth.passdb_callback;
+ struct sasl_passdb_result result;
+
+ timeout_remove(&client->auth.to_passdb);
+
+ i_zero(&result);
+
+ if (tset.authid == NULL ||
+ strcmp(client->auth.authid, tset.authid) != 0) {
+ result.status = SASL_PASSDB_RESULT_USER_UNKNOWN;
+ callback(&client->auth.sasl_req, &result);
+ return;
+ }
+ if (client->auth.authzid != NULL &&
+ (tset.authzid == NULL ||
+ strcmp(client->auth.authzid, tset.authzid) != 0)) {
+ result.status = SASL_PASSDB_RESULT_USER_UNKNOWN;
+ callback(&client->auth.sasl_req, &result);
+ return;
+ }
+
+ if (client->auth.password != NULL) {
+ if (strcmp(tset.password, client->auth.password) == 0) {
+ result.status = SASL_PASSDB_RESULT_OK;
+ callback(&client->auth.sasl_req, &result);
+ } else {
+ result.status = SASL_PASSDB_RESULT_PASSWORD_MISMATCH;
+ callback(&client->auth.sasl_req, &result);
+ }
+ return;
+ }
+
+ const struct password_generate_params params = {
+ .user = (client->auth.realm == NULL ? tset.authid :
+ t_strconcat(tset.authid, "@", client->auth.realm, NULL)),
+ };
+
+ if (!password_generate(tset.password, ¶ms,
+ client->auth.password_scheme,
+ &result.credentials.data,
+ &result.credentials.size)) {
+ i_zero(&result);
+ result.status = SASL_PASSDB_RESULT_INTERNAL_FAILURE;
+ callback(&client->auth.sasl_req, &result);
+ return;
+ }
+
+ result.status = SASL_PASSDB_RESULT_OK;
+ callback(&client->auth.sasl_req, &result);
+}
+
+static void
+test_server_sasl_verify_plain(struct sasl_server_req_ctx *rctx,
+ const char *password,
+ sasl_server_passdb_callback_t *callback)
+{
+ struct client *client =
+ container_of(rctx, struct client, auth.sasl_req);
+
+ i_assert(client->auth.to_passdb == NULL);
+
+ /* Simulate async lookup */
+ client->auth.passdb_callback = callback;
+ client->auth.password = p_strdup(client->pool, password);
+ client->auth.to_passdb = timeout_add_short(0,
+ test_server_sasl_passdb_result, client);
+}
+
+static void
+test_server_sasl_lookup_credentials(struct sasl_server_req_ctx *rctx,
+ const char *scheme,
+ sasl_server_passdb_callback_t *callback)
+{
+ struct client *client =
+ container_of(rctx, struct client, auth.sasl_req);
+
+ i_assert(client->auth.to_passdb == NULL);
+
+ /* Simulate async lookup */
+ client->auth.passdb_callback = callback;
+ client->auth.password_scheme = p_strdup(client->pool, scheme);
+ client->auth.to_passdb = timeout_add_short(0,
+ test_server_sasl_passdb_result, client);
+}
+
+static void
+test_server_sasl_output(struct sasl_server_req_ctx *rctx,
+ const struct sasl_server_output *output)
+{
+ struct client *client =
+ container_of(rctx, struct client, auth.sasl_req);
+ struct smtp_server_cmd_ctx *cmd = client->auth.cmd;
+
+ switch (output->status) {
+ case SASL_SERVER_OUTPUT_INTERNAL_FAILURE:
+ smtp_server_reply(cmd, 454, "4.7.0", "Internal error");
+ return;
+ case SASL_SERVER_OUTPUT_PASSWORD_MISMATCH:
+ case SASL_SERVER_OUTPUT_FAILURE:
+ smtp_server_reply(cmd, 535, "5.7.8", "Authentication failed");
+ return;
+ case SASL_SERVER_OUTPUT_SUCCESS:
+ if (output->data_size == 0) {
+ smtp_server_reply(cmd, 235, "2.7.0", "Logged in");
+ return;
+ }
+ break;
+ case SASL_SERVER_OUTPUT_CONTINUE:
+ break;
+ }
+
+ i_assert(output->data_size > 0);
+
+ buffer_t *chal = t_base64_encode(0, 0, output->data, output->data_size);
+ smtp_server_cmd_auth_send_challenge(cmd, str_c(chal));
+}
+
+static int
+test_server_conn_cmd_auth(void *conn_ctx, struct smtp_server_cmd_ctx *cmd,
+ struct smtp_server_cmd_auth *data)
+{
+ struct client *client = conn_ctx;
+ const struct sasl_server_mech *server_mech;
+
+ server_mech = sasl_server_mech_find(sasl_server_inst, data->sasl_mech);
+ if (server_mech == NULL) {
+ smtp_server_reply(cmd, 504, "5.5.4", "Invalid mechanism");
+ return -1;
+ }
+
+ i_zero(&client->auth);
+ client->auth.cmd = cmd;
+ sasl_server_request_create(&client->auth.sasl_req, server_mech, "smtp",
+ NULL);
+
+ const unsigned char *sasl_data;
+ size_t sasl_data_size;
+
+ if (data->initial_response == NULL) {
+ sasl_data = NULL;
+ sasl_data_size = 0;
+ } else if (strcmp(data->initial_response, "=") == 0) {
+ sasl_data = uchar_empty_ptr;
+ sasl_data_size = 0;
+ } else {
+ size_t b64_size = strlen(data->initial_response);
+ buffer_t *resp = t_buffer_create(MAX_BASE64_DECODED_SIZE(b64_size));
+ if (base64_decode(data->initial_response, b64_size, resp) < 0) {
+ smtp_server_reply(cmd, 501, "5.5.2",
+ "Invalid Base64 encoding");
+ return -1;
+ }
+
+ sasl_data = resp->data;
+ sasl_data_size = resp->used;
+ }
+ sasl_server_request_initial(&client->auth.sasl_req,
+ sasl_data, sasl_data_size);
+ return 0;
+}
+
+static int
+test_server_conn_cmd_auth_continue(void *conn_ctx,
+ struct smtp_server_cmd_ctx *cmd,
+ const char *response)
+{
+ struct client *client = conn_ctx;
+
+ size_t b64_size = strlen(response);
+ buffer_t *resp = t_buffer_create(MAX_BASE64_DECODED_SIZE(b64_size));
+ if (base64_decode(response, b64_size, resp) < 0) {
+ smtp_server_reply(cmd, 501, "5.5.2",
+ "Invalid Base64 encoding");
+ sasl_server_request_destroy(&client->auth.sasl_req);
+ return -1;
+ }
+
+ sasl_server_request_input(&client->auth.sasl_req,
+ resp->data, resp->used);
+ return 0;
+}
+
/* client connection */
static void test_server_connection_free(void *context);
static const struct smtp_server_callbacks server_callbacks =
{
+ .conn_cmd_helo = test_server_conn_cmd_helo,
+ .conn_cmd_auth = test_server_conn_cmd_auth,
+ .conn_cmd_auth_continue = test_server_conn_cmd_auth_continue,
+
.conn_cmd_rcpt = test_server_conn_cmd_rcpt,
.conn_cmd_data_begin = test_server_conn_cmd_data_begin,
.conn_cmd_data_continue = test_server_conn_cmd_data_continue,
smtp_server_connection_terminate(&client->smtp_conn,
NULL, "deinit");
}
+ timeout_remove(&client->auth.to_passdb);
+ sasl_server_request_destroy(&client->auth.sasl_req);
pool_unref(&client->pool);
}
/* */
+static const struct sasl_server_request_funcs server_sasl_funcs = {
+ .request_set_authid = test_server_sasl_set_authid,
+ .request_set_authzid = test_server_sasl_set_authzid,
+ .request_set_realm = test_server_sasl_set_realm,
+
+ .request_verify_plain = test_server_sasl_verify_plain,
+ .request_lookup_credentials = test_server_sasl_lookup_credentials,
+
+ .request_output = test_server_sasl_output,
+};
+
static void test_server_init(const struct smtp_server_settings *server_set)
{
+ const struct sasl_server_settings sasl_set = {};
+
/* open server socket */
io_listen = io_add(fd_listen, IO_READ, client_accept, NULL);
+ /* init SASL server */
+ sasl_server = sasl_server_init(server_set->event_parent,
+ &server_sasl_funcs);
+ sasl_server_inst = sasl_server_instance_create(sasl_server, &sasl_set);
+
+ sasl_server_mech_register_plain(sasl_server_inst);
+ sasl_server_mech_register_login(sasl_server_inst);
+ sasl_server_mech_register_scram_sha256(sasl_server_inst);
+
+ /* init SMTP server */
smtp_server = smtp_server_init(server_set);
}
/* deinitialize */
smtp_server_deinit(&smtp_server);
+ sasl_server_instance_unref(&sasl_server_inst);
+ sasl_server_deinit(&sasl_server);
}
/*
}
if (test_conns[i].conn == NULL) {
+ struct smtp_client_settings set;
+
+ i_zero(&set);
+ if (tset.sasl_mech != NULL) {
+ i_assert(tset.authid != NULL);
+ set.username = tset.authid;
+ set.password = tset.password;
+ }
+
test_conns[i].conn = smtp_client_connection_create(
smtp_client, client_protocol,
- net_ip2addr(&bind_ip), bind_port,
- ssl_mode, NULL);
+ net_ip2addr(&bind_ip), bind_port, ssl_mode, &set);
}
return &test_conns[i];
}
smtp_server_set.hostname = "localhost";
smtp_server_set.max_client_idle_time_msecs = CLIENT_PROGRESS_TIMEOUT_MSECS;
smtp_server_set.max_pipelined_commands = 1;
- smtp_server_set.auth_optional = TRUE;
+ smtp_server_set.auth_optional = (tset.sasl_mech == NULL);
smtp_server_set.ssl = &ssl_server_set;
smtp_server_set.debug = debug;
smtp_client_set.temp_path_prefix = "/tmp";
smtp_client_set.command_timeout_msecs = CLIENT_PROGRESS_TIMEOUT_MSECS;
smtp_client_set.connect_timeout_msecs = CLIENT_PROGRESS_TIMEOUT_MSECS;
+ smtp_client_set.sasl_mechanisms = tset.sasl_mech;
smtp_client_set.ssl = &ssl_client_set;
smtp_client_set.debug = debug;
test_end();
}
+
+static void test_smtp_authentication(void)
+{
+ i_zero(&tset);
+ test_begin("smtp payload - auth - PLAIN");
+ tset.sasl_mech = "PLAIN";
+ tset.authid = "user";
+ tset.password = "password";
+ test_run_scenarios(SMTP_PROTOCOL_SMTP,
+ SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_AUTH,
+ test_client);
+ test_end();
+
+ i_zero(&tset);
+ test_begin("smtp payload - auth - LOGIN");
+ tset.sasl_mech = "LOGIN";
+ tset.authid = "user";
+ tset.password = "password";
+ test_run_scenarios(SMTP_PROTOCOL_SMTP,
+ SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_AUTH,
+ test_client);
+ test_end();
+
+ i_zero(&tset);
+ test_begin("smtp payload - auth - SCRAM-SHA-256");
+ tset.sasl_mech = "SCRAM-SHA-256";
+ tset.authid = "user";
+ tset.password = "password";
+ test_run_scenarios(SMTP_PROTOCOL_SMTP,
+ SMTP_CAPABILITY_DSN | SMTP_CAPABILITY_AUTH,
+ test_client);
+ test_end();
+}
+
static void (*const test_functions[])(void) = {
test_smtp_normal,
test_smtp_chunking,
test_lmtp_normal,
test_lmtp_chunking,
+ test_smtp_authentication,
NULL
};
static void main_init(void)
{
ssl_iostream_openssl_init();
+ password_schemes_init();
+ dsasl_clients_init();
}
static void main_deinit(void)
{
+ dsasl_clients_deinit();
+ password_schemes_deinit();
ssl_iostream_context_cache_free();
ssl_iostream_openssl_deinit();
}