auth-postfix-connection.c revision 5f1d689131a75c39f064cbd4202373e7edf78f18
/* Copyright (c) 2011-2017 Dovecot authors, see the included COPYING file */
#include "auth-common.h"
#include "ioloop.h"
#include "net.h"
#include "istream.h"
#include "ostream.h"
#include "llist.h"
#include "str.h"
#include "strescape.h"
#include "str-sanitize.h"
#include "master-service.h"
#include "userdb.h"
#include "auth-postfix-connection.h"
#include <unistd.h>
#define MAX_INBUF_SIZE 1024
#define MAX_OUTBUF_SIZE (1024*50)
struct auth_postfix_connection {
struct auth_postfix_connection *prev, *next;
struct auth *auth;
int refcount;
int fd;
char *path;
struct istream *input;
struct ostream *output;
struct io *io;
bool destroyed:1;
};
static void postfix_input(struct auth_postfix_connection *conn);
static void auth_postfix_connection_ref(struct auth_postfix_connection *conn);
static void auth_postfix_connection_destroy(struct auth_postfix_connection **_conn);
static void auth_postfix_connection_unref(struct auth_postfix_connection **_conn);
static struct auth_postfix_connection *auth_postfix_connections;
static bool
postfix_input_auth_request(struct auth_postfix_connection *conn,
const char *username,
struct auth_request **request_r, const char **error_r)
{
struct auth_request *auth_request;
auth_request = auth_request_new_dummy();
auth_request->id = 1;
auth_request->context = conn;
auth_postfix_connection_ref(conn);
(void)auth_request_import_info(auth_request, "service", "postfix");
auth_request_init(auth_request);
if (!auth_request_set_username(auth_request, username, error_r)) {
*request_r = auth_request;
return FALSE;
}
*request_r = auth_request;
return TRUE;
}
static void
user_callback(enum userdb_result result, struct auth_request *auth_request)
{
struct auth_postfix_connection *conn = auth_request->context;
string_t *str;
const char *value;
if (auth_request->userdb_lookup_tempfailed)
result = USERDB_RESULT_INTERNAL_FAILURE;
str = t_str_new(128);
switch (result) {
case USERDB_RESULT_INTERNAL_FAILURE:
if (auth_request->userdb_lookup_tempfailed)
value = auth_fields_find(auth_request->userdb_reply, "reason");
else
value = NULL;
str_printfa(str, "400 %s",
value != NULL ? value: "Internal failure");
break;
case USERDB_RESULT_USER_UNKNOWN:
str_append(str, "500 User not found");
break;
case USERDB_RESULT_OK:
str_append(str, "200 1");
break;
}
if (conn->auth->set->debug)
i_debug("postfix out: %s", str_c(str));
str_append_c(str, '\n');
o_stream_nsend(conn->output, str_data(str), str_len(str));
i_assert(conn->io == NULL);
if (!conn->destroyed)
conn->io = io_add(conn->fd, IO_READ, postfix_input, conn);
auth_request_unref(&auth_request);
auth_postfix_connection_unref(&conn);
}
static bool
postfix_input_user(struct auth_postfix_connection *conn, const char *username)
{
struct auth_request *auth_request;
const char *error;
io_remove(&conn->io);
if (!postfix_input_auth_request(conn, username,
&auth_request, &error)) {
auth_request_log_info(auth_request, "postfix", "%s", error);
user_callback(USERDB_RESULT_USER_UNKNOWN, auth_request);
} else {
auth_request_set_state(auth_request, AUTH_REQUEST_STATE_USERDB);
auth_request_lookup_user(auth_request, user_callback);
}
return TRUE;
}
static bool
auth_postfix_input_line(struct auth_postfix_connection *conn, const char *line)
{
if (conn->auth->set->debug)
i_debug("postfix in: %s", line);
if (strncasecmp(line, "get ", 4) == 0)
return postfix_input_user(conn, line + 4);
i_error("BUG: Unknown command in postfix socket: %s",
str_sanitize(line, 80));
return FALSE;
}
static void postfix_input(struct auth_postfix_connection *conn)
{
char *line;
bool ret;
switch (i_stream_read(conn->input)) {
case 0:
return;
case -1:
/* disconnected */
auth_postfix_connection_destroy(&conn);
return;
case -2:
/* buffer full */
i_error("BUG: Postfix sent us more than %d bytes",
(int)MAX_INBUF_SIZE);
auth_postfix_connection_destroy(&conn);
return;
}
while ((line = i_stream_next_line(conn->input)) != NULL) {
T_BEGIN {
ret = auth_postfix_input_line(conn, line);
} T_END;
if (!ret) {
auth_postfix_connection_destroy(&conn);
return;
}
}
}
struct auth_postfix_connection *
auth_postfix_connection_create(struct auth *auth, int fd)
{
struct auth_postfix_connection *conn;
conn = i_new(struct auth_postfix_connection, 1);
conn->refcount = 1;
conn->fd = fd;
conn->auth = auth;
conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE);
conn->output = o_stream_create_fd(fd, (size_t)-1);
o_stream_set_no_error_handling(conn->output, TRUE);
conn->io = io_add(fd, IO_READ, postfix_input, conn);
DLLIST_PREPEND(&auth_postfix_connections, conn);
return conn;
}
static void
auth_postfix_connection_destroy(struct auth_postfix_connection **_conn)
{
struct auth_postfix_connection *conn = *_conn;
*_conn = NULL;
if (conn->destroyed)
return;
conn->destroyed = TRUE;
DLLIST_REMOVE(&auth_postfix_connections, conn);
if (conn->input != NULL)
i_stream_close(conn->input);
if (conn->output != NULL)
o_stream_close(conn->output);
io_remove(&conn->io);
if (conn->fd != -1) {
if (close(conn->fd) < 0)
i_error("close(%s): %m", conn->path);
conn->fd = -1;
}
master_service_client_connection_destroyed(master_service);
auth_postfix_connection_unref(&conn);
}
static void auth_postfix_connection_ref(struct auth_postfix_connection *conn)
{
i_assert(conn->refcount > 0);
conn->refcount++;
}
static void
auth_postfix_connection_unref(struct auth_postfix_connection **_conn)
{
struct auth_postfix_connection *conn = *_conn;
*_conn = NULL;
i_assert(conn->refcount > 0);
if (--conn->refcount > 0)
return;
if (conn->input != NULL)
i_stream_unref(&conn->input);
if (conn->output != NULL)
o_stream_unref(&conn->output);
i_free(conn);
}
void auth_postfix_connections_destroy_all(void)
{
struct auth_postfix_connection *conn;
while (auth_postfix_connections != NULL) {
conn = auth_postfix_connections;
auth_postfix_connection_destroy(&conn);
}
}