login-proxy-state.c revision 02c335c23bf5fa225a467c19f2c063fb0dc7b8c3
/* Copyright (c) 2009-2016 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "net.h"
#include "ioloop.h"
#include "hash.h"
#include "strescape.h"
#include "fd-set-nonblock.h"
#include "login-proxy-state.h"
#include <unistd.h>
#include <fcntl.h>
#define NOTIFY_RETRY_REOPEN_MSECS (60*1000)
struct login_proxy_state {
HASH_TABLE(struct login_proxy_record *,
struct login_proxy_record *) hash;
pool_t pool;
const char *notify_path;
int notify_fd;
struct timeout *to_reopen;
};
static int login_proxy_state_notify_open(struct login_proxy_state *state);
static unsigned int
login_proxy_record_hash(const struct login_proxy_record *rec)
{
return net_ip_hash(&rec->ip) ^ rec->port;
}
static int login_proxy_record_cmp(struct login_proxy_record *rec1,
struct login_proxy_record *rec2)
{
if (!net_ip_compare(&rec1->ip, &rec2->ip))
return 1;
return (int)rec1->port - (int)rec2->port;
}
struct login_proxy_state *login_proxy_state_init(const char *notify_path)
{
struct login_proxy_state *state;
state = i_new(struct login_proxy_state, 1);
state->pool = pool_alloconly_create("login proxy state", 1024);
hash_table_create(&state->hash, state->pool, 0,
login_proxy_record_hash, login_proxy_record_cmp);
state->notify_path = p_strdup(state->pool, notify_path);
state->notify_fd = -1;
return state;
}
static void login_proxy_state_close(struct login_proxy_state *state)
{
if (state->notify_fd != -1) {
if (close(state->notify_fd) < 0)
i_error("close(%s) failed: %m", state->notify_path);
state->notify_fd = -1;
}
}
void login_proxy_state_deinit(struct login_proxy_state **_state)
{
struct login_proxy_state *state = *_state;
struct hash_iterate_context *iter;
struct login_proxy_record *rec;
*_state = NULL;
/* sanity check: */
iter = hash_table_iterate_init(state->hash);
while (hash_table_iterate(iter, state->hash, &rec, &rec))
i_assert(rec->num_waiting_connections == 0);
hash_table_iterate_deinit(&iter);
if (state->to_reopen != NULL)
timeout_remove(&state->to_reopen);
login_proxy_state_close(state);
hash_table_destroy(&state->hash);
pool_unref(&state->pool);
i_free(state);
}
struct login_proxy_record *
login_proxy_state_get(struct login_proxy_state *state,
const struct ip_addr *ip, in_port_t port)
{
struct login_proxy_record *rec, key;
memset(&key, 0, sizeof(key));
key.ip = *ip;
key.port = port;
rec = hash_table_lookup(state->hash, &key);
if (rec == NULL) {
rec = p_new(state->pool, struct login_proxy_record, 1);
rec->ip = *ip;
rec->port = port;
hash_table_insert(state->hash, rec, rec);
}
return rec;
}
static void login_proxy_state_reopen(struct login_proxy_state *state)
{
timeout_remove(&state->to_reopen);
(void)login_proxy_state_notify_open(state);
}
static int login_proxy_state_notify_open(struct login_proxy_state *state)
{
if (state->to_reopen != NULL) {
/* reopen later */
return -1;
}
state->notify_fd = open(state->notify_path, O_WRONLY);
if (state->notify_fd == -1) {
i_error("open(%s) failed: %m", state->notify_path);
state->to_reopen = timeout_add(NOTIFY_RETRY_REOPEN_MSECS,
login_proxy_state_reopen, state);
return -1;
}
fd_set_nonblock(state->notify_fd, TRUE);
return 0;
}
static bool login_proxy_state_try_notify(struct login_proxy_state *state,
const char *user)
{
unsigned int len;
ssize_t ret;
if (state->notify_fd == -1) {
if (login_proxy_state_notify_open(state) < 0)
return TRUE;
}
T_BEGIN {
const char *cmd;
cmd = t_strconcat(str_tabescape(user), "\n", NULL);
len = strlen(cmd);
ret = write(state->notify_fd, cmd, len);
} T_END;
if (ret != (ssize_t)len) {
if (ret < 0)
i_error("write(%s) failed: %m", state->notify_path);
else {
i_error("write(%s) wrote partial update",
state->notify_path);
}
login_proxy_state_close(state);
/* retry sending */
return FALSE;
}
return TRUE;
}
void login_proxy_state_notify(struct login_proxy_state *state,
const char *user)
{
if (!login_proxy_state_try_notify(state, user))
(void)login_proxy_state_try_notify(state, user);
}