dict-client.c revision 02c335c23bf5fa225a467c19f2c063fb0dc7b8c3
/* Copyright (c) 2005-2016 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 <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 iter_replies_skip;
unsigned int in_iteration:1;
unsigned int handshaked:1;
};
struct client_dict_iterate_context {
struct dict_iterate_context ctx;
bool failed;
bool finished;
};
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;
}
static int
{
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 -1;
}
i_error("dict-client: Invalid ID");
return -1;
}
return 0;
}
if (dict->iter_replies_skip > 0) {
/* called aborted the iteration before finishing it.
skip over the iteration reply */
if (*line == DICT_PROTOCOL_REPLY_OK)
return 0;
i_error("dict-client: Invalid iteration reply line: %s",
line);
return -1;
}
return 0;
}
return 1;
}
{
int ret;
return ret;
}
{
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++;
dict->iter_replies_skip = 0;
/* 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[0] == ':') {
/* default path */
} else if (uri[0] == '/') {
/* absolute path */
} else {
/* relative path to base_dir */
}
return 0;
}
{
}
{
char *line;
int ret;
if (!dict->handshaked)
return -1;
while (dict->async_commits > 0) {
return -1;
if (ret > 0) {
return -1;
}
}
return 0;
}
{
const char *line;
int ret;
T_BEGIN {
const char *query;
} T_END;
if (ret < 0)
return -1;
/* read reply */
return -1;
switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
return 1;
return 0;
case DICT_PROTOCOL_REPLY_FAIL:
return -1;
default:
return -1;
}
}
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)
else if (ret > 0) {
}
}
static int
bool async,
void *context)
{
struct client_dict_transaction_context *ctx =
(struct client_dict_transaction_context *)_ctx;
unsigned int id;
ret = -1;
else if (async) {
if (dict->async_commits++ == 0) {
}
} else {
/* sync commit, read reply */
ret = -1;
else switch (*line) {
case DICT_PROTOCOL_REPLY_OK:
ret = 1;
break;
ret = 0;
break;
case DICT_PROTOCOL_REPLY_FAIL:
ret = -1;
break;
default:
ret = -1;
break;
}
i_error("dict-client: Invalid commit reply, "
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",
{
}
};