From: Timo Sirainen Date: Sun, 12 Dec 2021 17:36:37 +0000 (+0200) Subject: anvil: Change connect-limit API to use struct rather than ident string X-Git-Tag: 2.4.0~4549 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4edfe72f9674190bacffe9b52ff32ef13301eeac;p=thirdparty%2Fdovecot%2Fcore.git anvil: Change connect-limit API to use struct rather than ident string --- diff --git a/src/anvil/anvil-connection.c b/src/anvil/anvil-connection.c index 20e859b5c3..3bb9073b04 100644 --- a/src/anvil/anvil-connection.c +++ b/src/anvil/anvil-connection.c @@ -43,11 +43,38 @@ anvil_connection_next_line(struct anvil_connection *conn) 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; @@ -62,7 +89,11 @@ anvil_connection_request(struct anvil_connection *conn, *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"; @@ -72,7 +103,11 @@ anvil_connection_request(struct anvil_connection *conn, *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) { @@ -94,11 +129,15 @@ anvil_connection_request(struct anvil_connection *conn, *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) { diff --git a/src/anvil/connect-limit.c b/src/anvil/connect-limit.c index 0548a0b789..73ae91ce18 100644 --- a/src/anvil/connect-limit.c +++ b/src/anvil/connect-limit.c @@ -7,23 +7,47 @@ #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) @@ -33,7 +57,7 @@ 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) @@ -41,7 +65,8 @@ 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; @@ -52,43 +77,56 @@ void connect_limit_deinit(struct connect_limit **_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); @@ -98,37 +136,48 @@ void connect_limit_connect(struct connect_limit *limit, pid_t pid, } 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; } @@ -137,7 +186,7 @@ void connect_limit_disconnect(struct connect_limit *limit, pid_t pid, 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) @@ -152,7 +201,7 @@ 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); } } @@ -163,16 +212,24 @@ 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); + 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); } diff --git a/src/anvil/connect-limit.h b/src/anvil/connect-limit.h index 2d3c61101f..0d24782d0e 100644 --- a/src/anvil/connect-limit.h +++ b/src/anvil/connect-limit.h @@ -1,15 +1,27 @@ #ifndef CONNECT_LIMIT_H #define CONNECT_LIMIT_H +#include "net.h" + +struct connect_limit_key { + /* User's primary username */ + const char *username; + /* Service name */ + const char *service; + /* IP address. If family==0, there is no IP. */ + struct ip_addr ip; +}; + struct connect_limit *connect_limit_init(void); void connect_limit_deinit(struct connect_limit **limit); -unsigned int connect_limit_lookup(struct connect_limit *limit, - const char *ident); +unsigned int +connect_limit_lookup(struct connect_limit *limit, + const struct connect_limit_key *key); void connect_limit_connect(struct connect_limit *limit, pid_t pid, - const char *ident); + const struct connect_limit_key *key); void connect_limit_disconnect(struct connect_limit *limit, pid_t pid, - const char *ident); + const struct connect_limit_key *key); void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid); void connect_limit_dump(struct connect_limit *limit, struct ostream *output);