]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
anvil: Change connect-limit API to use struct rather than ident string
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Sun, 12 Dec 2021 17:36:37 +0000 (19:36 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 8 Feb 2022 09:48:24 +0000 (10:48 +0100)
src/anvil/anvil-connection.c
src/anvil/connect-limit.c
src/anvil/connect-limit.h

index 20e859b5c3365c0ade0b7d3d45770b55d77500fa..3bb9073b04e8e2a2bc902a75510d76bbe8b0fbd6 100644 (file)
@@ -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) {
index 0548a0b7898fc008bff2bccefd9b7da966ce2e64..73ae91ce187db1462e3c85dd2f7184eab352964f 100644 (file)
@@ -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);
 }
index 2d3c61101f654bd16f939620fadaf9b9b833c2d6..0d24782d0e13826fd6de257f1acc83d2ce4cea3f 100644 (file)
@@ -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);