client-authenticate.c revision f1e9611e93dcb3b745c1904029084fa81644e1b3
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen/* Copyright (C) 2002-2004 Timo Sirainen */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "common.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "base64.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "buffer.h"
3343a61404603b21c246783a7963b77833095f31Timo Sirainen#include "ioloop.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "istream.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "ostream.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "safe-memset.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "str.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "str-sanitize.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "imap-parser.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "auth-client.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "client.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "client-authenticate.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include "imap-proxy.h"
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen#include <stdlib.h>
3f190f4cbb9233a3a6830956cb5c7ae56a577b79Timo Sirainen
3f190f4cbb9233a3a6830956cb5c7ae56a577b79Timo Sirainen#define IMAP_SERVICE_NAME "imap"
3f190f4cbb9233a3a6830956cb5c7ae56a577b79Timo Sirainen
3f190f4cbb9233a3a6830956cb5c7ae56a577b79Timo Sirainenconst char *client_authenticate_get_capabilities(bool secured)
3f190f4cbb9233a3a6830956cb5c7ae56a577b79Timo Sirainen{
3f190f4cbb9233a3a6830956cb5c7ae56a577b79Timo Sirainen const struct auth_mech_desc *mech;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen unsigned int i, count;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen string_t *str;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen str = t_str_new(128);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen mech = auth_client_get_available_mechs(auth_client, &count);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen for (i = 0; i < count; i++) {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen /* a) transport is secured
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen b) auth mechanism isn't plaintext
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen c) we allow insecure authentication
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if ((mech[i].flags & MECH_SEC_PRIVATE) == 0 &&
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen (secured || !disable_plaintext_auth ||
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen (mech[i].flags & MECH_SEC_PLAINTEXT) == 0)) {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen str_append_c(str, ' ');
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen str_append(str, "AUTH=");
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen str_append(str, mech[i].name);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen }
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen }
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen return str_c(str);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen}
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainenstatic void client_auth_input(struct imap_client *client)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen{
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen char *line;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if (!client_read(client))
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen return;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if (client->skip_line) {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if (i_stream_next_line(client->input) == NULL)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen return;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen client->skip_line = FALSE;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen }
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen /* @UNSAFE */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen line = i_stream_next_line(client->input);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if (line == NULL)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen return;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if (strcmp(line, "*") == 0) {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen sasl_server_auth_client_error(&client->common,
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen "Authentication aborted");
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen } else if (client->common.waiting_auth_reply) {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen sasl_server_auth_client_error(&client->common,
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen "Don't send unrequested data");
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen } else {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen auth_client_request_continue(client->common.auth_request, line);
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen client->common.waiting_auth_reply = TRUE;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen /* clear sensitive data */
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen safe_memset(line, 0, strlen(line));
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen }
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen}
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainenstatic bool client_handle_args(struct imap_client *client,
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen const char *const *args, bool success)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen{
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen const char *reason = NULL, *host = NULL, *destuser = NULL, *pass = NULL;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen string_t *reply;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen unsigned int port = 143;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen bool proxy = FALSE, temp = FALSE, nologin = !success;
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen for (; *args != NULL; args++) {
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen if (strcmp(*args, "nologin") == 0)
8fcff4c5b52f24d9c681805fdf06b486f1d0fcbeTimo Sirainen nologin = TRUE;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen else if (strcmp(*args, "proxy") == 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen proxy = TRUE;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen else if (strcmp(*args, "temp") == 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen temp = TRUE;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen else if (strncmp(*args, "reason=", 7) == 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen reason = *args + 7;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen else if (strncmp(*args, "host=", 5) == 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen host = *args + 5;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen else if (strncmp(*args, "port=", 5) == 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen port = atoi(*args + 5);
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen else if (strncmp(*args, "destuser=", 9) == 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen destuser = *args + 9;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen else if (strncmp(*args, "pass=", 5) == 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen pass = *args + 5;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen }
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen if (destuser == NULL)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen destuser = client->common.virtual_user;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen if (proxy) {
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen /* we want to proxy the connection to another server.
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen don't do this unless authentication succeeded. with
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen master user proxying we can get FAIL with proxy still set.
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen proxy host=.. [port=..] [destuser=..] pass=.. */
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen if (!success)
7501b9f694460101b41d1d708ebc3ec2b0400b1cTimo Sirainen return FALSE;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen if (imap_proxy_new(client, host, port, destuser, pass) < 0)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen client_destroy_internal_failure(client);
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen return TRUE;
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen }
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen if (host != NULL) {
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen /* IMAP referral
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen [nologin] referral host=.. [port=..] [destuser=..]
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen [reason=..]
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen NO [REFERRAL imap://destuser;AUTH=..@host:port/] Can't login.
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen OK [...] Logged in, but you should use this server instead.
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen .. [REFERRAL ..] (Reason from auth server)
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen */
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen reply = t_str_new(128);
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen str_append(reply, nologin ? "NO " : "OK ");
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen str_printfa(reply, "[REFERRAL imap://%s;AUTH=%s@%s",
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen destuser, client->common.auth_mech_name, host);
3343a61404603b21c246783a7963b77833095f31Timo Sirainen if (port != 143)
3343a61404603b21c246783a7963b77833095f31Timo Sirainen str_printfa(reply, ":%u", port);
3343a61404603b21c246783a7963b77833095f31Timo Sirainen str_append(reply, "/] ");
3343a61404603b21c246783a7963b77833095f31Timo Sirainen if (reason != NULL)
3343a61404603b21c246783a7963b77833095f31Timo Sirainen str_append(reply, reason);
3343a61404603b21c246783a7963b77833095f31Timo Sirainen else if (nologin)
3343a61404603b21c246783a7963b77833095f31Timo Sirainen str_append(reply, "Try this server instead.");
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen else {
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen str_append(reply, "Logged in, but you should use "
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen "this server instead.");
f1901fd21906911f7be075c965ac882f6a87b4c3Timo Sirainen }
client_send_tagline(client, str_c(reply));
if (!nologin) {
client_destroy(client, "Login with referral");
return TRUE;
}
} else if (nologin) {
/* Authentication went ok, but for some reason user isn't
allowed to log in. Shouldn't probably happen. */
reply = t_str_new(128);
if (reason != NULL)
str_printfa(reply, "NO %s", reason);
else if (temp)
str_append(reply, "NO "AUTH_TEMP_FAILED_MSG);
else
str_append(reply, "NO "AUTH_FAILED_MSG);
client_send_tagline(client, str_c(reply));
} else {
/* normal login/failure */
return FALSE;
}
i_assert(nologin);
if (!client->destroyed) {
/* get back to normal client input. */
if (client->io != NULL)
io_remove(&client->io);
client->io = io_add(client->common.fd, IO_READ,
client_input, client);
}
return TRUE;
}
static void sasl_callback(struct client *_client, enum sasl_server_reply reply,
const char *data, const char *const *args)
{
struct imap_client *client = (struct imap_client *)_client;
struct const_iovec iov[3];
const char *msg;
size_t data_len;
i_assert(!client->destroyed ||
reply == SASL_SERVER_REPLY_CLIENT_ERROR ||
reply == SASL_SERVER_REPLY_MASTER_FAILED);
switch (reply) {
case SASL_SERVER_REPLY_SUCCESS:
if (args != NULL) {
if (client_handle_args(client, args, TRUE))
break;
}
client_send_tagline(client, "OK Logged in.");
client_destroy(client, "Login");
break;
case SASL_SERVER_REPLY_AUTH_FAILED:
case SASL_SERVER_REPLY_CLIENT_ERROR:
if (args != NULL) {
if (client_handle_args(client, args, FALSE))
break;
}
msg = reply == SASL_SERVER_REPLY_AUTH_FAILED ? "NO " : "BAD ";
msg = t_strconcat(msg, data != NULL ? data : AUTH_FAILED_MSG,
NULL);
client_send_tagline(client, msg);
if (!client->destroyed) {
/* get back to normal client input. */
if (client->io != NULL)
io_remove(&client->io);
client->io = io_add(client->common.fd, IO_READ,
client_input, client);
}
break;
case SASL_SERVER_REPLY_MASTER_FAILED:
client_destroy_internal_failure(client);
break;
case SASL_SERVER_REPLY_CONTINUE:
data_len = strlen(data);
iov[0].iov_base = "+ ";
iov[0].iov_len = 2;
iov[1].iov_base = data;
iov[1].iov_len = data_len;
iov[2].iov_base = "\r\n";
iov[2].iov_len = 2;
/* don't check return value here. it gets tricky if we try
to call client_destroy() in here. */
(void)o_stream_sendv(client->output, iov, 3);
return;
}
client_unref(client);
}
int cmd_authenticate(struct imap_client *client, const struct imap_arg *args)
{
const char *mech_name, *init_resp = NULL;
/* we want only one argument: authentication mechanism name */
if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING)
return -1;
if (args[1].type != IMAP_ARG_EOL) {
/* optional SASL initial response */
if (args[1].type != IMAP_ARG_ATOM ||
args[2].type != IMAP_ARG_EOL)
return -1;
init_resp = IMAP_ARG_STR(&args[1]);
}
mech_name = IMAP_ARG_STR(&args[0]);
if (*mech_name == '\0')
return 0;
client_ref(client);
sasl_server_auth_begin(&client->common, IMAP_SERVICE_NAME, mech_name,
init_resp, sasl_callback);
if (!client->common.authenticating)
return 1;
/* following input data will go to authentication */
if (client->io != NULL)
io_remove(&client->io);
client->io = io_add(client->common.fd, IO_READ,
client_auth_input, client);
return 0;
}
int cmd_login(struct imap_client *client, const struct imap_arg *args)
{
const char *user, *pass;
string_t *plain_login, *base64;
/* two arguments: username and password */
if (args[0].type != IMAP_ARG_ATOM && args[0].type != IMAP_ARG_STRING)
return -1;
if (args[1].type != IMAP_ARG_ATOM && args[1].type != IMAP_ARG_STRING)
return -1;
if (args[2].type != IMAP_ARG_EOL)
return -1;
user = IMAP_ARG_STR(&args[0]);
pass = IMAP_ARG_STR(&args[1]);
if (!client->common.secured && disable_plaintext_auth) {
if (verbose_auth) {
client_syslog(&client->common, "Login failed: "
"Plaintext authentication disabled");
}
client_send_line(client,
"* BAD [ALERT] Plaintext authentication is disabled, "
"but your client sent password in plaintext anyway. "
"If anyone was listening, the password was exposed.");
client_send_tagline(client, "NO "AUTH_PLAINTEXT_DISABLED_MSG);
return 1;
}
/* authorization ID \0 authentication ID \0 pass */
plain_login = buffer_create_dynamic(pool_datastack_create(), 64);
buffer_append_c(plain_login, '\0');
buffer_append(plain_login, user, strlen(user));
buffer_append_c(plain_login, '\0');
buffer_append(plain_login, pass, strlen(pass));
base64 = buffer_create_dynamic(pool_datastack_create(),
MAX_BASE64_ENCODED_SIZE(plain_login->used));
base64_encode(plain_login->data, plain_login->used, base64);
client_ref(client);
sasl_server_auth_begin(&client->common, IMAP_SERVICE_NAME, "PLAIN",
str_c(base64), sasl_callback);
if (!client->common.authenticating)
return 1;
/* don't read any input from client until login is finished */
if (client->io != NULL)
io_remove(&client->io);
return 0;
}