--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ostream.h"
+#include "str.h"
+#include "dict-client.h"
+#include "dict-settings.h"
+#include "dict-connection.h"
+#include "dict-commands.h"
+
+#include <stdlib.h>
+
+#define DICT_OUTPUT_OPTIMAL_SIZE 1024
+
+struct dict_client_cmd {
+ int cmd;
+ int (*func)(struct dict_connection *conn, const char *line);
+};
+
+static int cmd_lookup(struct dict_connection *conn, const char *line)
+{
+ const char *reply;
+ const char *value;
+ int ret;
+
+ if (conn->iter_ctx != NULL) {
+ i_error("dict client: LOOKUP: Can't lookup while iterating");
+ return -1;
+ }
+
+ /* <key> */
+ ret = dict_lookup(conn->dict, pool_datastack_create(), line, &value);
+ if (ret > 0) {
+ reply = t_strdup_printf("%c%s\n",
+ DICT_PROTOCOL_REPLY_OK, value);
+ o_stream_send_str(conn->output, reply);
+ } else {
+ reply = t_strdup_printf("%c\n", ret == 0 ?
+ DICT_PROTOCOL_REPLY_NOTFOUND :
+ DICT_PROTOCOL_REPLY_FAIL);
+ o_stream_send_str(conn->output, reply);
+ }
+ return 0;
+}
+
+static int cmd_iterate_flush(struct dict_connection *conn)
+{
+ string_t *str;
+ const char *key, *value;
+ int ret;
+
+ str = t_str_new(256);
+ o_stream_cork(conn->output);
+ while ((ret = dict_iterate(conn->iter_ctx, &key, &value)) > 0) {
+ str_truncate(str, 0);
+ str_printfa(str, "%s\t%s\n", key, value);
+ o_stream_send(conn->output, str_data(str), str_len(str));
+
+ if (o_stream_get_buffer_used_size(conn->output) >
+ DICT_OUTPUT_OPTIMAL_SIZE) {
+ if (o_stream_flush(conn->output) <= 0)
+ break;
+ /* flushed everything, continue */
+ }
+ }
+
+ if (ret <= 0) {
+ /* finished iterating */
+ o_stream_unset_flush_callback(conn->output);
+ dict_iterate_deinit(&conn->iter_ctx);
+ o_stream_send(conn->output, "\n", 1);
+ }
+ o_stream_uncork(conn->output);
+ return ret <= 0 ? 1 : 0;
+}
+
+static int cmd_iterate(struct dict_connection *conn, const char *line)
+{
+ const char *const *args;
+
+ if (conn->iter_ctx != NULL) {
+ i_error("dict client: ITERATE: Already iterating");
+ return -1;
+ }
+
+ args = t_strsplit(line, "\t");
+ if (str_array_length(args) != 2) {
+ i_error("dict client: ITERATE: broken input");
+ return -1;
+ }
+
+ /* <flags> <path> */
+ conn->iter_ctx = dict_iterate_init(conn->dict, args[1], atoi(args[0]));
+
+ o_stream_set_flush_callback(conn->output, cmd_iterate_flush, conn);
+ cmd_iterate_flush(conn);
+ return 0;
+}
+
+static struct dict_connection_transaction *
+dict_connection_transaction_lookup(struct dict_connection *conn,
+ unsigned int id)
+{
+ struct dict_connection_transaction *transactions;
+ unsigned int i, count;
+
+ if (!array_is_created(&conn->transactions))
+ return NULL;
+
+ transactions = array_get_modifiable(&conn->transactions, &count);
+ for (i = 0; i < count; i++) {
+ if (transactions[i].id == id)
+ return &transactions[i];
+ }
+ return NULL;
+}
+
+static void
+dict_connection_transaction_array_remove(struct dict_connection *conn,
+ struct dict_connection_transaction *trans)
+{
+ const struct dict_connection_transaction *transactions;
+ unsigned int i, count;
+
+ transactions = array_get(&conn->transactions, &count);
+ for (i = 0; i < count; i++) {
+ if (&transactions[i] == trans) {
+ array_delete(&conn->transactions, i, 1);
+ break;
+ }
+ }
+}
+
+static int cmd_begin(struct dict_connection *conn, const char *line)
+{
+ struct dict_connection_transaction *trans;
+ unsigned int id;
+
+ if (!is_numeric(line, '\0')) {
+ i_error("dict client: Invalid transaction ID %s", line);
+ return -1;
+ }
+
+ id = (unsigned int)strtoul(line, NULL, 10);
+ if (dict_connection_transaction_lookup(conn, id) != NULL) {
+ i_error("dict client: Transaction ID %u already exists", id);
+ return -1;
+ }
+
+ if (!array_is_created(&conn->transactions))
+ i_array_init(&conn->transactions, 4);
+
+ /* <id> */
+ trans = array_append_space(&conn->transactions);
+ trans->id = id;
+ trans->ctx = dict_transaction_begin(conn->dict);
+ return 0;
+}
+
+static int
+dict_connection_transaction_lookup_parse(struct dict_connection *conn,
+ const char *line,
+ struct dict_connection_transaction **trans_r)
+{
+ unsigned int id;
+
+ if (!is_numeric(line, '\0')) {
+ i_error("dict client: Invalid transaction ID %s", line);
+ return -1;
+ }
+
+ id = (unsigned int)strtoul(line, NULL, 10);
+ *trans_r = dict_connection_transaction_lookup(conn, id);
+ if (*trans_r == NULL) {
+ i_error("dict client: Transaction ID %u doesn't exist", id);
+ return -1;
+ }
+ return 0;
+}
+
+static int cmd_commit(struct dict_connection *conn, const char *line)
+{
+ struct dict_connection_transaction *trans;
+ const char *reply;
+ int ret;
+
+ if (conn->iter_ctx != NULL) {
+ i_error("dict client: COMMIT: Can't commit while iterating");
+ return -1;
+ }
+
+ if (dict_connection_transaction_lookup_parse(conn, line, &trans) < 0)
+ return -1;
+
+ ret = dict_transaction_commit(&trans->ctx);
+ reply = t_strdup_printf("%c\n", ret == 0 ? DICT_PROTOCOL_REPLY_OK :
+ DICT_PROTOCOL_REPLY_FAIL);
+ o_stream_send_str(conn->output, reply);
+ dict_connection_transaction_array_remove(conn, trans);
+ return 0;
+}
+
+static int
+cmd_commit_async(struct dict_connection *conn, const char *line)
+{
+ struct dict_connection_transaction *trans;
+
+ if (conn->iter_ctx != NULL) {
+ i_error("dict client: COMMIT: Can't commit while iterating");
+ return -1;
+ }
+
+ if (dict_connection_transaction_lookup_parse(conn, line, &trans) < 0)
+ return -1;
+
+ dict_transaction_commit_async(&trans->ctx);
+ dict_connection_transaction_array_remove(conn, trans);
+ return 0;
+}
+
+static int cmd_rollback(struct dict_connection *conn, const char *line)
+{
+ struct dict_connection_transaction *trans;
+
+ if (dict_connection_transaction_lookup_parse(conn, line, &trans) < 0)
+ return -1;
+
+ dict_transaction_rollback(&trans->ctx);
+ dict_connection_transaction_array_remove(conn, trans);
+ return 0;
+}
+
+static int cmd_set(struct dict_connection *conn, const char *line)
+{
+ struct dict_connection_transaction *trans;
+ const char *const *args;
+
+ /* <id> <key> <value> */
+ args = t_strsplit(line, "\t");
+ if (str_array_length(args) != 3) {
+ i_error("dict client: SET: broken input");
+ return -1;
+ }
+
+ if (dict_connection_transaction_lookup_parse(conn, args[0], &trans) < 0)
+ return -1;
+
+ dict_set(trans->ctx, args[1], args[2]);
+ return 0;
+}
+
+static int cmd_unset(struct dict_connection *conn, const char *line)
+{
+ struct dict_connection_transaction *trans;
+ const char *const *args;
+
+ /* <id> <key> */
+ args = t_strsplit(line, "\t");
+ if (str_array_length(args) != 2) {
+ i_error("dict client: UNSET: broken input");
+ return -1;
+ }
+
+ if (dict_connection_transaction_lookup_parse(conn, args[0], &trans) < 0)
+ return -1;
+
+ dict_unset(trans->ctx, args[1]);
+ return 0;
+}
+
+static int cmd_atomic_inc(struct dict_connection *conn, const char *line)
+{
+ struct dict_connection_transaction *trans;
+ const char *const *args;
+ long long arg;
+
+ /* <id> <key> <diff> */
+ args = t_strsplit(line, "\t");
+ if (str_array_length(args) != 3) {
+ i_error("dict client: ATOMIC_INC: broken input");
+ return -1;
+ }
+
+ if (dict_connection_transaction_lookup_parse(conn, args[0], &trans) < 0)
+ return -1;
+
+ if (*args[2] != '-')
+ arg = (long long)strtoull(args[2], NULL, 10);
+ else
+ arg = -(long long)strtoull(args[2]+1, NULL, 10);
+ dict_atomic_inc(trans->ctx, args[1], arg);
+ return 0;
+}
+
+static struct dict_client_cmd cmds[] = {
+ { DICT_PROTOCOL_CMD_LOOKUP, cmd_lookup },
+ { DICT_PROTOCOL_CMD_ITERATE, cmd_iterate },
+ { DICT_PROTOCOL_CMD_BEGIN, cmd_begin },
+ { DICT_PROTOCOL_CMD_COMMIT, cmd_commit },
+ { DICT_PROTOCOL_CMD_COMMIT_ASYNC, cmd_commit_async },
+ { DICT_PROTOCOL_CMD_ROLLBACK, cmd_rollback },
+ { DICT_PROTOCOL_CMD_SET, cmd_set },
+ { DICT_PROTOCOL_CMD_UNSET, cmd_unset },
+ { DICT_PROTOCOL_CMD_ATOMIC_INC, cmd_atomic_inc },
+
+ { 0, NULL }
+};
+
+static struct dict_client_cmd *dict_command_find(char cmd)
+{
+ unsigned int i;
+
+ for (i = 0; cmds[i].cmd != '\0'; i++) {
+ if (cmds[i].cmd == cmd)
+ return &cmds[i];
+ }
+ return NULL;
+}
+
+int dict_command_input(struct dict_connection *conn, const char *line)
+{
+ struct dict_client_cmd *cmd;
+
+ cmd = dict_command_find(*line);
+ if (cmd == NULL) {
+ i_error("dict client: Unknown command %c", *line);
+ return -1;
+ }
+
+ return cmd->func(conn, line + 1);
+}
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "llist.h"
+#include "dict-client.h"
+#include "dict-settings.h"
+#include "dict-commands.h"
+#include "dict-connection.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+static struct dict_connection *dict_connections;
+
+static int dict_connection_parse_handshake(struct dict_connection *conn,
+ const char *line)
+{
+ const char *username, *name, *value_type;
+
+ if (*line++ != DICT_PROTOCOL_CMD_HELLO)
+ return -1;
+
+ /* check major version */
+ if (*line++ - '0' != DICT_CLIENT_PROTOCOL_MAJOR_VERSION ||
+ *line++ != '\t')
+ return -1;
+
+ /* skip minor version */
+ while (*line != '\t' && *line != '\0') line++;
+
+ if (*line++ != '\t')
+ return -1;
+
+ /* get value type */
+ value_type = line;
+ while (*line != '\t' && *line != '\0') line++;
+
+ if (*line++ != '\t')
+ return -1;
+ conn->value_type = atoi(t_strdup_until(value_type, line - 1));
+
+ /* get username */
+ username = line;
+ while (*line != '\t' && *line != '\0') line++;
+
+ if (*line++ != '\t')
+ return -1;
+ conn->username = i_strdup_until(username, line - 1);
+
+ /* the rest is dict name. since we're looking it with getenv(),
+ disallow all funny characters that might confuse it, just in case. */
+ name = line;
+ while (*line > ' ' && *line != '=') line++;
+
+ if (*line != '\0')
+ return -1;
+
+ conn->name = i_strdup(name);
+ return 0;
+}
+
+static int dict_connection_dict_init(struct dict_connection *conn)
+{
+ const char *const *strlist;
+ unsigned int i, count;
+ const char *uri;
+
+ strlist = array_get(&dict_settings->dicts, &count);
+ for (i = 0; i < count; i += 2) {
+ if (strcmp(strlist[i], conn->name) == 0)
+ break;
+ }
+
+ if (i == count) {
+ i_error("dict client: Unconfigured dictionary name '%s'",
+ conn->name);
+ return -1;
+ }
+ uri = strlist[i+1];
+
+ conn->dict = dict_init(uri, conn->value_type, conn->username);
+ if (conn->dict == NULL) {
+ /* dictionary initialization failed */
+ i_error("Failed to initialize dictionary '%s'", conn->name);
+ return -1;
+ }
+ return 0;
+}
+
+static void dict_connection_input(struct dict_connection *conn)
+{
+ const char *line;
+ int ret;
+
+ switch (i_stream_read(conn->input)) {
+ case 0:
+ return;
+ case -1:
+ /* disconnected */
+ dict_connection_destroy(conn);
+ return;
+ case -2:
+ /* buffer full */
+ i_error("dict client: Sent us more than %d bytes",
+ (int)DICT_CLIENT_MAX_LINE_LENGTH);
+ dict_connection_destroy(conn);
+ return;
+ }
+
+ if (conn->username == NULL) {
+ /* handshake not received yet */
+ if ((line = i_stream_next_line(conn->input)) == NULL)
+ return;
+
+ if (dict_connection_parse_handshake(conn, line) < 0) {
+ i_error("dict client: Broken handshake");
+ dict_connection_destroy(conn);
+ return;
+ }
+ if (dict_connection_dict_init(conn)) {
+ dict_connection_destroy(conn);
+ return;
+ }
+ }
+
+ while ((line = i_stream_next_line(conn->input)) != NULL) {
+ T_BEGIN {
+ ret = dict_command_input(conn, line);
+ } T_END;
+ if (ret < 0) {
+ dict_connection_destroy(conn);
+ break;
+ }
+ }
+}
+
+struct dict_connection *dict_connection_create(int fd)
+{
+ struct dict_connection *conn;
+
+ conn = i_new(struct dict_connection, 1);
+ conn->fd = fd;
+ conn->input = i_stream_create_fd(fd, DICT_CLIENT_MAX_LINE_LENGTH,
+ FALSE);
+ conn->output = o_stream_create_fd(fd, 128*1024, FALSE);
+ conn->io = io_add(fd, IO_READ, dict_connection_input, conn);
+ DLLIST_PREPEND(&dict_connections, conn);
+ return conn;
+}
+
+void dict_connection_destroy(struct dict_connection *conn)
+{
+ struct dict_connection_transaction *transactions;
+ unsigned int i, count;
+
+ DLLIST_REMOVE(&dict_connections, conn);
+
+ if (array_is_created(&conn->transactions)) {
+ transactions = array_get_modifiable(&conn->transactions, &count);
+ for (i = 0; i < count; i++)
+ dict_transaction_rollback(&transactions[i].ctx);
+ array_free(&conn->transactions);
+ }
+
+ if (conn->iter_ctx != NULL)
+ dict_iterate_deinit(&conn->iter_ctx);
+
+ io_remove(&conn->io);
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ if (close(conn->fd) < 0)
+ i_error("close(dict client) failed: %m");
+
+ if (conn->dict != NULL)
+ dict_deinit(&conn->dict);
+ i_free(conn->name);
+ i_free(conn->username);
+ i_free(conn);
+}
+
+void dict_connections_destroy_all(void)
+{
+ while (dict_connections != NULL)
+ dict_connection_destroy(dict_connections);
+}