summaryrefslogtreecommitdiffstats
path: root/src/anvil/connect-limit.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/anvil/connect-limit.c192
1 files changed, 192 insertions, 0 deletions
diff --git a/src/anvil/connect-limit.c b/src/anvil/connect-limit.c
new file mode 100644
index 0000000..0d27368
--- /dev/null
+++ b/src/anvil/connect-limit.c
@@ -0,0 +1,192 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "ostream.h"
+#include "connect-limit.h"
+
+struct ident_pid {
+ /* ident string points to ident_hash keys */
+ const char *ident;
+ pid_t pid;
+ unsigned int refcount;
+};
+
+struct connect_limit {
+ /* ident => unsigned int refcount */
+ HASH_TABLE(char *, void *) ident_hash;
+ /* struct ident_pid => struct ident_pid */
+ HASH_TABLE(struct ident_pid *, struct ident_pid *) ident_pid_hash;
+};
+
+static void
+connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident);
+
+static unsigned int ident_pid_hash(const struct ident_pid *i)
+{
+ return str_hash(i->ident) ^ i->pid;
+}
+
+static int ident_pid_cmp(const struct ident_pid *i1, const struct ident_pid *i2)
+{
+ if (i1->pid < i2->pid)
+ return -1;
+ else if (i1->pid > i2->pid)
+ return 1;
+ else
+ return strcmp(i1->ident, i2->ident);
+}
+
+struct connect_limit *connect_limit_init(void)
+{
+ struct connect_limit *limit;
+
+ limit = i_new(struct connect_limit, 1);
+ hash_table_create(&limit->ident_hash, default_pool, 0, str_hash, strcmp);
+ hash_table_create(&limit->ident_pid_hash, default_pool, 0,
+ ident_pid_hash, ident_pid_cmp);
+ return limit;
+}
+
+void connect_limit_deinit(struct connect_limit **_limit)
+{
+ struct connect_limit *limit = *_limit;
+ struct hash_iterate_context *iter;
+ struct ident_pid *i, *value;
+
+ iter = hash_table_iterate_init(limit->ident_pid_hash);
+ while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) {
+ hash_table_remove(limit->ident_pid_hash, i);
+ for (; i->refcount > 0; i->refcount--)
+ connect_limit_ident_hash_unref(limit, i->ident);
+ i_free(i);
+ }
+ hash_table_iterate_deinit(&iter);
+
+ *_limit = NULL;
+ hash_table_destroy(&limit->ident_hash);
+ hash_table_destroy(&limit->ident_pid_hash);
+ i_free(limit);
+}
+
+unsigned int connect_limit_lookup(struct connect_limit *limit,
+ const char *ident)
+{
+ void *value;
+
+ value = hash_table_lookup(limit->ident_hash, ident);
+ return POINTER_CAST_TO(value, unsigned int);
+}
+
+void connect_limit_connect(struct connect_limit *limit, pid_t pid,
+ const char *ident)
+{
+ struct ident_pid *i, lookup_i;
+ char *key;
+ void *value;
+
+ if (!hash_table_lookup_full(limit->ident_hash, ident,
+ &key, &value)) {
+ key = i_strdup(ident);
+ value = POINTER_CAST(1);
+ hash_table_insert(limit->ident_hash, key, value);
+ } else {
+ value = POINTER_CAST(POINTER_CAST_TO(value, unsigned int) + 1);
+ hash_table_update(limit->ident_hash, key, value);
+ }
+
+ lookup_i.ident = ident;
+ lookup_i.pid = pid;
+ i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
+ if (i == NULL) {
+ i = i_new(struct ident_pid, 1);
+ i->ident = key;
+ i->pid = pid;
+ i->refcount = 1;
+ hash_table_insert(limit->ident_pid_hash, i, i);
+ } else {
+ i->refcount++;
+ }
+}
+
+static void
+connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident)
+{
+ char *key;
+ void *value;
+ unsigned int new_refcount;
+
+ if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value))
+ i_panic("connect limit hash tables are inconsistent");
+
+ new_refcount = POINTER_CAST_TO(value, unsigned int) - 1;
+ if (new_refcount > 0) {
+ value = POINTER_CAST(new_refcount);
+ hash_table_update(limit->ident_hash, key, value);
+ } else {
+ hash_table_remove(limit->ident_hash, key);
+ i_free(key);
+ }
+}
+
+void connect_limit_disconnect(struct connect_limit *limit, pid_t pid,
+ const char *ident)
+{
+ struct ident_pid *i, lookup_i;
+
+ lookup_i.ident = ident;
+ lookup_i.pid = pid;
+
+ i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
+ if (i == NULL) {
+ i_error("connect limit: disconnection for unknown "
+ "pid %s + ident %s", dec2str(pid), ident);
+ return;
+ }
+
+ if (--i->refcount == 0) {
+ hash_table_remove(limit->ident_pid_hash, i);
+ i_free(i);
+ }
+
+ connect_limit_ident_hash_unref(limit, ident);
+}
+
+void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid)
+{
+ struct hash_iterate_context *iter;
+ struct ident_pid *i, *value;
+
+ /* this should happen rarely (or never), so this slow implementation
+ should be fine. */
+ iter = hash_table_iterate_init(limit->ident_pid_hash);
+ while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) {
+ if (i->pid == pid) {
+ hash_table_remove(limit->ident_pid_hash, i);
+ for (; i->refcount > 0; i->refcount--)
+ connect_limit_ident_hash_unref(limit, i->ident);
+ i_free(i);
+ }
+ }
+ hash_table_iterate_deinit(&iter);
+}
+
+void connect_limit_dump(struct connect_limit *limit, struct ostream *output)
+{
+ struct hash_iterate_context *iter;
+ struct ident_pid *i, *value;
+ string_t *str = t_str_new(256);
+
+ iter = hash_table_iterate_init(limit->ident_pid_hash);
+ while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) {
+ str_truncate(str, 0);
+ str_append_tabescaped(str, i->ident);
+ str_printfa(str, "\t%ld\t%u\n", (long)i->pid, i->refcount);
+ if (o_stream_send(output, str_data(str), str_len(str)) < 0)
+ break;
+ }
+ hash_table_iterate_deinit(&iter);
+ o_stream_nsend(output, "\n", 1);
+}