view src/imap-login/client-authenticate.c @ 1275:af685269ead0 HEAD

login: Wait until we're connected to auth process before executing command from client. inetd usage: --group=name can now specify which login group to use. Default is the binary name before '-' character (ie. imap or pop3).
author Timo Sirainen <tss@iki.fi>
date Wed, 05 Mar 2003 00:38:07 +0200
parents 40c2be9abdd3
children 7709e997903f
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "base64.h"
#include "buffer.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "safe-memset.h"
#include "str.h"
#include "imap-parser.h"
#include "auth-connection.h"
#include "../auth/auth-mech-desc.h"
#include "client.h"
#include "client-authenticate.h"
#include "auth-common.h"
#include "master.h"

static enum auth_mech auth_mechs = 0;
static char *auth_mechs_capability = NULL;

const char *client_authenticate_get_capabilities(void)
{
	string_t *str;
	int i;

	if (auth_mechs == available_auth_mechs)
		return auth_mechs_capability;

	auth_mechs = available_auth_mechs;
	i_free(auth_mechs_capability);

	str = t_str_new(128);

	for (i = 0; i < AUTH_MECH_COUNT; i++) {
		if ((auth_mechs & auth_mech_desc[i].mech) &&
		    auth_mech_desc[i].name != NULL) {
			str_append_c(str, ' ');
			str_append(str, "AUTH=");
			str_append(str, auth_mech_desc[i].name);
		}
	}

	auth_mechs_capability = i_strdup_empty(str_c(str));
	return auth_mechs_capability;
}

static struct auth_mech_desc *auth_mech_find(const char *name)
{
	int i;

	for (i = 0; i < AUTH_MECH_COUNT; i++) {
		if (auth_mech_desc[i].name != NULL &&
		    strcasecmp(auth_mech_desc[i].name, name) == 0)
			return &auth_mech_desc[i];
	}

	return NULL;
}

static void client_auth_abort(struct imap_client *client, const char *msg)
{
	if (client->common.auth_request != NULL) {
		auth_abort_request(client->common.auth_request);
                auth_request_unref(client->common.auth_request);
		client->common.auth_request = NULL;
	}

	client_send_tagline(client, msg != NULL ?
			    t_strconcat("NO ", msg, NULL) :
			    "NO Authentication failed.");
	o_stream_flush(client->output);

	/* get back to normal client input */
	if (client->common.io != NULL)
		io_remove(client->common.io);
	client->common.io = client->common.fd == -1 ? NULL :
		io_add(client->common.fd, IO_READ, client_input, client);

	client_unref(client);
}

static void master_callback(struct client *_client, int success)
{
	struct imap_client *client = (struct imap_client *) _client;
	const char *reason = NULL;

	if (success) {
		reason = t_strconcat("Login: ", client->common.virtual_user,
				     NULL);
	} else {
		reason = t_strconcat("Internal login failure: ",
				     client->common.virtual_user, NULL);
		client_send_line(client, "* BYE Internal login failure.");
	}

	client_destroy(client, reason);
	client_unref(client);
}

static void client_send_auth_data(struct imap_client *client,
				  const unsigned char *data, size_t size)
{
	buffer_t *buf;

	t_push();

	buf = buffer_create_dynamic(data_stack_pool, size*2, (size_t)-1);
	buffer_append(buf, "+ ", 2);
	base64_encode(data, size, buf);
	buffer_append(buf, "\r\n", 2);

	o_stream_send(client->output, buffer_get_data(buf, NULL),
		      buffer_get_used_size(buf));
	o_stream_flush(client->output);

	t_pop();
}

static void login_callback(struct auth_request *request,
			   struct auth_login_reply *reply,
			   const unsigned char *data, struct client *_client)
{
	struct imap_client *client = (struct imap_client *) _client;
	const char *error;
	const void *ptr;
	size_t size;

	switch (auth_callback(request, reply, data, _client,
			      master_callback, &error)) {
	case -1:
		/* login failed */
                client->authenticating = FALSE;
		client_auth_abort(client, error);
		break;

	case 0:
		/* continue */
		ptr = buffer_get_data(client->plain_login, &size);
		auth_continue_request(request, ptr, size);

		buffer_set_used_size(client->plain_login, 0);
		break;
	default:
		/* success, we should be able to log in. if we fail, just
		   disconnect the client. */
                client->authenticating = FALSE;
		client_send_tagline(client, "OK Logged in.");
	}
}

int cmd_login(struct imap_client *client, struct imap_arg *args)
{
	const char *user, *pass, *error;

	/* two arguments: username and password */
	if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING)
		return FALSE;
	if (args[1].type != IMAP_ARG_ATOM && args[1].type != IMAP_ARG_STRING)
		return FALSE;
	if (args[2].type != IMAP_ARG_EOL)
		return FALSE;

	user = IMAP_ARG_STR(&args[0]);
	pass = IMAP_ARG_STR(&args[1]);

	if (!client->tls && disable_plaintext_auth) {
		client_send_tagline(client,
				    "NO Plaintext authentication disabled.");
		return TRUE;
	}

	/* authorization ID \0 authentication ID \0 pass */
	buffer_set_used_size(client->plain_login, 0);
	buffer_append_c(client->plain_login, '\0');
	buffer_append(client->plain_login, user, strlen(user));
	buffer_append_c(client->plain_login, '\0');
	buffer_append(client->plain_login, pass, strlen(pass));

	client_ref(client);
	if (auth_init_request(AUTH_MECH_PLAIN, AUTH_PROTOCOL_IMAP,
			      login_callback, &client->common, &error)) {
		/* don't read any input from client until login is finished */
		if (client->common.io != NULL) {
			io_remove(client->common.io);
			client->common.io = NULL;
		}
                client->authenticating = TRUE;
		return TRUE;
	} else {
		client_send_tagline(client, t_strconcat(
			"NO Login failed: ", error, NULL));
		client_unref(client);
		return TRUE;
	}
}

static void authenticate_callback(struct auth_request *request,
				  struct auth_login_reply *reply,
				  const unsigned char *data,
				  struct client *_client)
{
	struct imap_client *client = (struct imap_client *) _client;
	const char *error;

	switch (auth_callback(request, reply, data, _client,
			      master_callback, &error)) {
	case -1:
		/* login failed */
                client->authenticating = FALSE;
		client_auth_abort(client, error);
		break;

	case 0:
		/* continue */
		client_send_auth_data(client, data, reply->data_size);
		break;
	default:
		/* success, we should be able to log in. if we fail, just
		   disconnect the client. */
                client->authenticating = FALSE;
		client_send_tagline(client, "OK Logged in.");
	}
}

static void client_auth_input(void *context)
{
	struct imap_client *client = context;
	buffer_t *buf;
	char *line;
	size_t linelen, bufsize;

	if (!client_read(client))
		return;

	if (client->skip_line) {
		if (i_stream_next_line(client->input) == NULL)
			return;

		client->skip_line = FALSE;
	}

	/* @UNSAFE */
	line = i_stream_next_line(client->input);
	if (line == NULL)
		return;

	if (strcmp(line, "*") == 0) {
		client_auth_abort(client, "Authentication aborted");
		return;
	}

	linelen = strlen(line);
	buf = buffer_create_static_hard(data_stack_pool, linelen);

	if (base64_decode((const unsigned char *) line, linelen,
			  NULL, buf) <= 0) {
		/* failed */
		client_auth_abort(client, "Invalid base64 data");
	} else if (client->common.auth_request == NULL) {
		client_auth_abort(client, "Don't send unrequested data");
	} else {
		auth_continue_request(client->common.auth_request,
				      buffer_get_data(buf, NULL),
				      buffer_get_used_size(buf));
	}

	/* clear sensitive data */
	safe_memset(line, 0, linelen);

	bufsize = buffer_get_used_size(buf);
	safe_memset(buffer_free_without_data(buf), 0, bufsize);
}

int cmd_authenticate(struct imap_client *client, struct imap_arg *args)
{
	struct auth_mech_desc *mech;
	const char *mech_name, *error;

	/* we want only one argument: authentication mechanism name */
	if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING)
		return FALSE;
	if (args[1].type != IMAP_ARG_EOL)
		return FALSE;

	mech_name = IMAP_ARG_STR(&args[0]);
	if (*mech_name == '\0')
		return FALSE;

	mech = auth_mech_find(mech_name);
	if (mech == NULL) {
		client_send_tagline(client,
				    "NO Unsupported authentication mechanism.");
		return TRUE;
	}

	if (!client->tls && mech->plaintext && disable_plaintext_auth) {
		client_send_tagline(client,
				    "NO Plaintext authentication disabled.");
		return TRUE;
	}

	client_ref(client);
	if (auth_init_request(mech->mech, AUTH_PROTOCOL_IMAP,
			      authenticate_callback, &client->common, &error)) {
		/* following input data will go to authentication */
		if (client->common.io != NULL)
			io_remove(client->common.io);
		client->common.io = io_add(client->common.fd, IO_READ,
					   client_auth_input, client);
                client->authenticating = TRUE;
	} else {
		client_send_tagline(client, t_strconcat(
			"NO Authentication failed: ", error, NULL));
		client_unref(client);
	}

	return TRUE;
}