dict-client.c revision 7cb128dc4cae2a03a742f63ba7afee23c78e3af0
/* Copyright (c) 2005-2015 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "llist.h"
#include "str.h"
#include "net.h"
#include "istream.h"
#include "ostream.h"
#include "eacces-error.h"
#include "dict-private.h"
#include "dict-client.h"
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
/* Disconnect from dict server after this many milliseconds of idling after
sending a command. Because dict server does blocking dict accesses, it can
handle only one client at a time. This is why the default timeout is zero,
so that there won't be many dict processes just doing nothing. Zero means
that the socket is disconnected immediately after returning to ioloop. */
#define DICT_CLIENT_TIMEOUT_MSECS 0
/* Abort dict lookup after this many seconds. */
#define DICT_CLIENT_READ_TIMEOUT_SECS 30
/* Log a warning if dict lookup takes longer than this many seconds. */
#define DICT_CLIENT_READ_WARN_TIMEOUT_SECS 5
struct client_dict {
int fd;
const char *uri;
const char *username;
const char *path;
enum dict_data_type value_type;
unsigned int connect_counter;
unsigned int transaction_id_counter;
unsigned int async_commits;
unsigned int in_iteration:1;
unsigned int handshaked:1;
};
struct client_dict_iterate_context {
struct dict_iterate_context ctx;
bool failed;
};
struct client_dict_transaction_context {
struct dict_transaction_context ctx;
/* for async commits */
void *context;
unsigned int id;
unsigned int connect_counter;
unsigned int failed:1;
unsigned int sent_begin:1;
unsigned int async:1;
unsigned int committed:1;
};
const char *dict_client_escape(const char *src)
{
const char *p;
/* first do a quick lookup to see if there's anything to escape.
probably not. */
for (p = src; *p != '\0'; p++) {
if (*p == '\t' || *p == '\n' || *p == '\001')
break;
}
if (*p == '\0')
return src;
for (; *p != '\0'; p++) {
switch (*p) {
case '\t':
break;
case '\n':
break;
case '\001':
break;
default:
str_append_c(dest, *p);
break;
}
}
}
const char *dict_client_unescape(const char *src)
{
const char *p;
/* first do a quick lookup to see if there's anything to unescape.
probably not. */
for (p = src; *p != '\0'; p++) {
if (*p == '\001')
break;
}
if (*p == '\0')
return src;
for (; *p != '\0'; p++) {
if (*p != '\001')
str_append_c(dest, *p);
else if (p[1] != '\0') {
p++;
switch (*p) {
case '1':
break;
case 't':
break;
case 'n':
break;
}
}
}
}
{
/* not connected currently */
if (client_dict_connect(dict) < 0)
return -1;
}
/* Send failed */
if (!dict->handshaked) {
/* we're trying to send hello, don't try to reconnect */
return -1;
}
/* Reconnect and try again. */
if (client_dict_connect(dict) < 0)
return -1;
return -1;
}
}
return 0;
}
static int
{
return -1;
T_BEGIN {
const char *query;
else
} T_END;
}
static int ATTR_NOWARN_UNUSED_RESULT
const char *query)
{
if (!ctx->sent_begin) {
if (client_dict_transaction_send_begin(ctx) < 0)
return -1;
}
return -1;
/* not connected, this'll fail */
return -1;
}
/* Send failed. Our transactions have died, so don't even try
to re-send the command */
return -1;
}
return 0;
}
static struct client_dict_transaction_context *
{
struct client_dict_transaction_context *ctx;
return ctx;
}
return NULL;
}
static void
{
struct client_dict_transaction_context *ctx;
return;
}
return;
/* the callback may call the dict code again, so remove this
transaction before calling it */
if (--dict->async_commits == 0)
}
{
unsigned int diff;
do {
alarm(0);
if (ret != 0)
break;
/* interrupted most likely because of timeout,
but check anyway. */
if (ret > 0) {
if (diff >= DICT_CLIENT_READ_WARN_TIMEOUT_SECS) {
i_warning("read(%s): dict lookup took %u seconds",
}
}
return ret;
}
{
unsigned int id;
char *line;
switch (ret) {
case -1:
else {
i_error("read(%s) failed: Remote disconnected",
}
return -1;
case -2:
return -1;
case 0:
i_error("read(%s) failed: Timeout after %u seconds",
return -1;
default:
break;
}
}
if (*line == DICT_PROTOCOL_REPLY_ASYNC_COMMIT) {
switch (line[1]) {
case DICT_PROTOCOL_REPLY_OK:
ret = 1;
break;
ret = 0;
break;
case DICT_PROTOCOL_REPLY_FAIL:
ret = -1;
break;
default:
i_error("dict-client: Invalid async commit line: %s",
line);
return 0;
}
i_error("dict-client: Invalid ID");
return 0;
}
return 0;
}
return 1;
}
{
dict->async_commits == 0;
}
{
if (client_dict_is_finished(dict))
}
{
#if DICT_CLIENT_TIMEOUT_MSECS > 0
#endif
} else if (client_dict_is_finished(dict)) {
}
}
{
char *line;
;
return line;
}
{
const char *query;
/* Try again later */
return -1;
}
} else {
i_error("net_connect_unix(%s) failed: %m",
}
return -1;
}
/* Dictionary lookups are blocking */
return -1;
}
return 0;
}
{
dict->connect_counter++;
/* abort all pending async commits */
}
}
}
static int
const struct dict_settings *set,
{
struct client_dict *dict;
const char *dest_uri;
/* uri = [<path>] ":" <uri> */
return -1;
}
if (*uri != ':') {
/* path given */
} else {
}
return 0;
}
{
}
{
char *line;
int ret = 0;
if (!dict->handshaked)
return -1;
while (dict->async_commits > 0) {
ret = -1;
break;
}
}
return ret;
}
{
const char *line;
int ret;
T_BEGIN {
const char *query;
} T_END;
if (ret < 0)
return -1;
/* read reply */
return -1;
if (*line == DICT_PROTOCOL_REPLY_OK) {
return 1;
} else {
}
}
static struct dict_iterate_context *
enum dict_iterate_flags flags)
{
struct client_dict_iterate_context *ctx;
if (dict->in_iteration)
i_panic("dict-client: Only one iteration supported");
T_BEGIN {
unsigned int i;
}
} T_END;
}
{
struct client_dict_iterate_context *ctx =
(struct client_dict_iterate_context *)_ctx;
return FALSE;
/* read next reply */
return FALSE;
}
if (*line == '\0') {
/* end of iteration */
return FALSE;
}
/* line contains key \t value */
switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
break;
case DICT_PROTOCOL_REPLY_FAIL:
return FALSE;
default:
break;
}
/* broken protocol */
return FALSE;
}
*value++ = '\0';
return TRUE;
}
{
struct client_dict_iterate_context *ctx =
(struct client_dict_iterate_context *)_ctx;
return ret;
}
static struct dict_transaction_context *
{
struct client_dict_transaction_context *ctx;
}
{
char *line;
int ret;
do {
if (ret < 0)
}
static int
bool async,
void *context)
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
ret = -1;
else if (async) {
if (dict->async_commits++ == 0) {
}
} else {
/* sync commit, read reply */
ret = -1;
else if (*line == DICT_PROTOCOL_REPLY_OK)
ret = 1;
else if (*line == DICT_PROTOCOL_REPLY_NOTFOUND)
ret = 0;
else
ret = -1;
}
} T_END;
}
return ret;
}
static void
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
const char *query;
} T_END;
}
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
T_BEGIN {
const char *query;
} T_END;
}
const char *key)
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
T_BEGIN {
const char *query;
} T_END;
}
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
T_BEGIN {
const char *query;
} T_END;
}
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
T_BEGIN {
const char *query;
} T_END;
}
struct dict dict_driver_client = {
.name = "proxy",
{
}
};