return line == NULL ? NULL : t_strsplit_tabescaped(line);
}
+static bool
+connect_limit_key_parse(const char *ident, struct connect_limit_key *key_r)
+{
+ const char *p, *p2, *ip_str;
+
+ /* imap, pop3: service/ip/username
+ lmtp: service/username */
+ p = strchr(ident, '/');
+ if (p == NULL)
+ return FALSE;
+
+ i_zero(key_r);
+ key_r->service = t_strdup_until(ident, p++);
+
+ p2 = strchr(p, '/');
+ if (p2 == NULL)
+ key_r->username = p;
+ else {
+ ip_str = t_strdup_until(p, p2++);
+ key_r->username = p2;
+ if (ip_str[0] != '\0' && net_addr2ip(ip_str, &key_r->ip) < 0)
+ return FALSE;
+ }
+ return TRUE;
+}
+
static int
anvil_connection_request(struct anvil_connection *conn,
const char *const *args, const char **error_r)
{
const char *cmd = args[0];
+ struct connect_limit_key key;
unsigned int value, checksum;
time_t stamp;
pid_t pid;
*error_r = "CONNECT: Invalid pid";
return -1;
}
- connect_limit_connect(connect_limit, pid, args[1]);
+ if (!connect_limit_key_parse(args[1], &key)) {
+ *error_r = "CONNECT: Invalid ident string";
+ return -1;
+ }
+ connect_limit_connect(connect_limit, pid, &key);
} else if (strcmp(cmd, "DISCONNECT") == 0) {
if (args[0] == NULL || args[1] == NULL) {
*error_r = "DISCONNECT: Not enough parameters";
*error_r = "DISCONNECT: Invalid pid";
return -1;
}
- connect_limit_disconnect(connect_limit, pid, args[1]);
+ if (!connect_limit_key_parse(args[1], &key)) {
+ *error_r = "DISCONNECT: Invalid ident string";
+ return -1;
+ }
+ connect_limit_disconnect(connect_limit, pid, &key);
} else if (strcmp(cmd, "CONNECT-DUMP") == 0) {
connect_limit_dump(connect_limit, conn->output);
} else if (strcmp(cmd, "KILL") == 0) {
*error_r = "LOOKUP: Not enough parameters";
return -1;
}
+ if (!connect_limit_key_parse(args[0], &key)) {
+ *error_r = "LOOKUP: Invalid ident string";
+ return -1;
+ }
if (conn->output == NULL) {
*error_r = "LOOKUP on a FIFO, can't send reply";
return -1;
}
- value = connect_limit_lookup(connect_limit, args[0]);
+ value = connect_limit_lookup(connect_limit, &key);
o_stream_nsend_str(conn->output,
t_strdup_printf("%u\n", value));
} else if (strcmp(cmd, "PENALTY-GET") == 0) {
#include "ostream.h"
#include "connect-limit.h"
+struct userip {
+ char *username;
+ char *service;
+ struct ip_addr ip;
+};
+
struct ident_pid {
- /* ident string points to ident_hash keys */
- const char *ident;
+ /* points to userip_hash keys */
+ struct userip *userip;
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 */
+ /* userip => unsigned int refcount */
+ HASH_TABLE(struct userip *, void *) userip_hash;
+ /* (userip, pid) => struct ident_pid */
HASH_TABLE(struct ident_pid *, struct ident_pid *) ident_pid_hash;
};
+static unsigned int userip_hash(const struct userip *userip)
+{
+ return str_hash(userip->username) ^ str_hash(userip->service) ^
+ net_ip_hash(&userip->ip);
+}
+
+static int userip_cmp(const struct userip *userip1,
+ const struct userip *userip2)
+{
+ int ret = strcmp(userip1->username, userip2->username);
+ if (ret != 0)
+ return ret;
+ ret = net_ip_cmp(&userip1->ip, &userip2->ip);
+ if (ret != 0)
+ return ret;
+ return strcmp(userip1->service, userip2->service);
+}
+
static unsigned int ident_pid_hash(const struct ident_pid *i)
{
- return str_hash(i->ident) ^ i->pid;
+ return userip_hash(i->userip) ^ i->pid;
}
static int ident_pid_cmp(const struct ident_pid *i1, const struct ident_pid *i2)
else if (i1->pid > i2->pid)
return 1;
else
- return strcmp(i1->ident, i2->ident);
+ return userip_cmp(i1->userip, i2->userip);
}
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->userip_hash, default_pool, 0,
+ userip_hash, userip_cmp);
hash_table_create(&limit->ident_pid_hash, default_pool, 0,
ident_pid_hash, ident_pid_cmp);
return limit;
struct connect_limit *limit = *_limit;
*_limit = NULL;
- hash_table_destroy(&limit->ident_hash);
+ hash_table_destroy(&limit->userip_hash);
hash_table_destroy(&limit->ident_pid_hash);
i_free(limit);
}
unsigned int connect_limit_lookup(struct connect_limit *limit,
- const char *ident)
+ const struct connect_limit_key *key)
{
+ struct userip userip_lookup = {
+ .username = (char *)key->username,
+ .service = (char *)key->service,
+ .ip = key->ip,
+ };
void *value;
- value = hash_table_lookup(limit->ident_hash, ident);
+ value = hash_table_lookup(limit->userip_hash, &userip_lookup);
return POINTER_CAST_TO(value, unsigned int);
}
void connect_limit_connect(struct connect_limit *limit, pid_t pid,
- const char *ident)
+ const struct connect_limit_key *key)
{
struct ident_pid *i, lookup_i;
- char *key;
+ struct userip *userip;
void *value;
- if (!hash_table_lookup_full(limit->ident_hash, ident,
- &key, &value)) {
- key = i_strdup(ident);
+ struct userip userip_lookup = {
+ .username = (char *)key->username,
+ .service = (char *)key->service,
+ .ip = key->ip,
+ };
+ if (!hash_table_lookup_full(limit->userip_hash, &userip_lookup,
+ &userip, &value)) {
+ userip = i_new(struct userip, 1);
+ userip->username = i_strdup(key->username);
+ userip->service = i_strdup(key->service);
+ userip->ip = key->ip;
value = POINTER_CAST(1);
- hash_table_insert(limit->ident_hash, key, value);
+ hash_table_insert(limit->userip_hash, userip, value);
} else {
value = POINTER_CAST(POINTER_CAST_TO(value, unsigned int) + 1);
- hash_table_update(limit->ident_hash, key, value);
+ hash_table_update(limit->userip_hash, userip, value);
}
- lookup_i.ident = ident;
+ lookup_i.userip = userip;
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->userip = userip;
i->pid = pid;
i->refcount = 1;
hash_table_insert(limit->ident_pid_hash, i, i);
}
static void
-connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident)
+userip_hash_unref(struct connect_limit *limit,
+ const struct userip *userip_lookup)
{
- char *key;
+ struct userip *userip;
void *value;
unsigned int new_refcount;
- if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value))
+ if (!hash_table_lookup_full(limit->userip_hash,
+ userip_lookup, &userip, &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);
+ hash_table_update(limit->userip_hash, userip, value);
} else {
- hash_table_remove(limit->ident_hash, key);
- i_free(key);
+ hash_table_remove(limit->userip_hash, userip);
+ i_free(userip->username);
+ i_free(userip->service);
+ i_free(userip);
}
}
void connect_limit_disconnect(struct connect_limit *limit, pid_t pid,
- const char *ident)
+ const struct connect_limit_key *key)
{
struct ident_pid *i, lookup_i;
+ struct userip userip_lookup = {
+ .username = (char *)key->username,
+ .service = (char *)key->service,
+ .ip = key->ip,
+ };
- lookup_i.ident = ident;
+ lookup_i.userip = &userip_lookup;
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);
+ "(pid=%s, user=%s, service=%s, ip=%s)",
+ dec2str(pid), key->username, key->service,
+ net_ip2addr(&key->ip));
return;
}
i_free(i);
}
- connect_limit_ident_hash_unref(limit, ident);
+ userip_hash_unref(limit, &userip_lookup);
}
void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid)
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);
+ userip_hash_unref(limit, i->userip);
i_free(i);
}
}
{
struct hash_iterate_context *iter;
struct ident_pid *i, *value;
- string_t *str = t_str_new(256);
+ string_t *str = str_new(default_pool, 256);
+ ssize_t ret = 0;
iter = hash_table_iterate_init(limit->ident_pid_hash);
- while (hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) {
+ while (ret >= 0 &&
+ hash_table_iterate(iter, limit->ident_pid_hash, &i, &value)) T_BEGIN {
str_truncate(str, 0);
- str_append_tabescaped(str, i->ident);
+ str_append_tabescaped(str, i->userip->service);
+ if (i->userip->ip.family != 0) {
+ str_append_c(str, '/');
+ str_append(str, net_ip2addr(&i->userip->ip));
+ }
+ str_append_c(str, '/');
+ str_append_tabescaped(str, i->userip->username);
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;
- }
+ ret = o_stream_send(output, str_data(str), str_len(str));
+ } T_END;
hash_table_iterate_deinit(&iter);
o_stream_nsend(output, "\n", 1);
+ str_free(&str);
}