Allow building WITHOUT_STATS
[freeradius.git] / src / main / client.c
index 0ea7961..35045df 100644 (file)
@@ -49,16 +49,51 @@ struct radclient_list {
 
 
 #ifdef WITH_STATS
-static rbtree_t                *tree_num;      /* client numbers 0..N */
-static int             tree_num_max;
+static rbtree_t                *tree_num = NULL;     /* client numbers 0..N */
+static int             tree_num_max = 0;
+#endif
+static RADCLIENT_LIST  *root_clients = NULL;
+
+#ifdef WITH_DYNAMIC_CLIENTS
+static fr_fifo_t       *deleted_clients = NULL;
 #endif
-static RADCLIENT_LIST  *root_clients;
 
 /*
  *     Callback for freeing a client.
  */
 void client_free(RADCLIENT *client)
 {
+#ifdef WITH_DYNAMIC_CLIENTS
+       if (client->dynamic == 2) {
+               time_t now;
+
+               if (!deleted_clients) {
+                       deleted_clients = fr_fifo_create(1024,
+                                                        (void *) client_free);
+                       if (!deleted_clients) return; /* MEMLEAK */
+               }
+
+               /*
+                *      Mark it as in the fifo, and remember when we
+                *      pushed it.
+                */
+               client->dynamic = 3;
+               client->created = now = time(NULL); /* re-set it */
+               fr_fifo_push(deleted_clients, client);
+
+               /*
+                *      Peek at the head of the fifo.  If it might
+                *      still be in use, return.  Otherwise, pop it
+                *      from the queue and delete it.
+                */
+               client = fr_fifo_peek(deleted_clients);
+               if ((client->created + 120) >= now) return;
+
+               client = fr_fifo_pop(deleted_clients);
+               rad_assert(client != NULL);
+       }
+#endif
+
        free(client->longname);
        free(client->secret);
        free(client->shortname);
@@ -88,8 +123,23 @@ static int client_ipaddr_cmp(const void *one, const void *two)
 {
        const RADCLIENT *a = one;
        const RADCLIENT *b = two;
+#ifndef WITH_TCP
 
        return fr_ipaddr_cmp(&a->ipaddr, &b->ipaddr);
+#else
+       int rcode;
+
+       rcode = fr_ipaddr_cmp(&a->ipaddr, &b->ipaddr);
+       if (rcode != 0) return rcode;
+
+       /*
+        *      Wildcard match
+        */
+       if ((a->proto == IPPROTO_IP) ||
+           (b->proto == IPPROTO_IP)) return 0;
+
+       return (a->proto - b->proto);
+#endif
 }
 
 #ifdef WITH_STATS
@@ -125,6 +175,12 @@ void clients_free(RADCLIENT_LIST *clients)
                root_clients = NULL;
        }
 
+#ifdef WITH_DYNAMIC_CLIENTS
+       /*
+        *      FIXME: No fr_fifo_delete()
+        */
+#endif
+
        free(clients);
 }
 
@@ -177,15 +233,34 @@ static int client_sane(RADCLIENT *client)
                               sizeof(client->ipaddr.ipaddr.ip6addr));
 
                } else if (client->prefix < 128) {
-                       int i;
                        uint32_t mask, *addr;
 
                        addr = (uint32_t *) &client->ipaddr.ipaddr.ip6addr;
 
-                       for (i = client->prefix; i < 128; i += 32) {
-                               mask = ~0;
-                               mask <<= ((128 - i) & 0x1f);
-                               addr[i / 32] &= mask;
+                       if ((client->prefix & 0x1f) == 0) {
+                               mask = 0;
+                       } else {
+                               mask = ~ ((uint32_t) 0);
+                               mask <<= (32 - (client->prefix & 0x1f));
+                               mask = htonl(mask);
+                       }
+
+                       switch (client->prefix >> 5) {
+                       case 0:
+                               addr[0] &= mask;
+                               mask = 0;
+                               /* FALL-THROUGH */
+                       case 1:
+                               addr[1] &= mask;
+                               mask = 0;
+                               /* FALL-THROUGH */
+                       case 2:
+                               addr[2] &= mask;
+                               mask = 0;
+                               /* FALL-THROUGH */
+                       case 3:
+                               addr[3] &= mask;
+                         break;
                        }
                }
                break;
@@ -203,6 +278,8 @@ static int client_sane(RADCLIENT *client)
  */
 int client_add(RADCLIENT_LIST *clients, RADCLIENT *client)
 {
+       RADCLIENT *old;
+
        if (!client) {
                return 0;
        }
@@ -238,14 +315,42 @@ int client_add(RADCLIENT_LIST *clients, RADCLIENT *client)
                }
        }
 
+#define namecmp(a) ((!old->a && !client->a) || (old->a && client->a && (strcmp(old->a, client->a) == 0)))
+
        /*
         *      Cannot insert the same client twice.
         */
-       if (rbtree_find(clients->trees[client->prefix], client)) {
+       old = rbtree_finddata(clients->trees[client->prefix], client);
+       if (old) {
+               /*
+                *      If it's a complete duplicate, then free the new
+                *      one, and return "OK".
+                */
+               if ((fr_ipaddr_cmp(&old->ipaddr, &client->ipaddr) == 0) &&
+                   (old->prefix == client->prefix) &&
+                   namecmp(longname) && namecmp(secret) &&
+                   namecmp(shortname) && namecmp(nastype) &&
+                   namecmp(login) && namecmp(password) && namecmp(server) &&
+#ifdef WITH_DYNAMIC_CLIENTS
+                   (old->lifetime == client->lifetime) &&
+                   namecmp(client_server) &&
+#endif
+#ifdef WITH_COA
+                   namecmp(coa_name) &&
+                   (old->coa_server == client->coa_server) &&
+                   (old->coa_pool == client->coa_pool) &&
+#endif
+                   (old->message_authenticator == client->message_authenticator)) {
+                       DEBUG("WARNING: Ignoring duplicate client %s", client->longname);
+                       client_free(client);
+                       return 1;
+               }
+
                radlog(L_ERR, "Failed to add duplicate client %s",
                       client->shortname);
                return 0;
        }
+#undef namecmp
 
        /*
         *      Other error adding client: likely is fatal.
@@ -290,7 +395,7 @@ int client_add(RADCLIENT_LIST *clients, RADCLIENT *client)
                 *      If there IS an enclosing network,
                 *      inherit the lifetime from it.
                 */
-               network = client_find(clients, &client->ipaddr);
+               network = client_find(clients, &client->ipaddr, client->proto);
                if (network) {
                        client->lifetime = network->lifetime;
                }
@@ -313,10 +418,19 @@ int client_add(RADCLIENT_LIST *clients, RADCLIENT *client)
 #ifdef WITH_DYNAMIC_CLIENTS
 void client_delete(RADCLIENT_LIST *clients, RADCLIENT *client)
 {
-       if (!clients || !client) return;
+       if (!client) return;
+
+       if (!clients) clients = root_clients;
+
+       if (!client->dynamic) return;
 
        rad_assert((client->prefix >= 0) && (client->prefix <= 128));
 
+       client->dynamic = 2;    /* signal to client_free */
+
+#ifdef WITH_STATS
+       rbtree_deletebydata(tree_num, client);
+#endif
        rbtree_deletebydata(clients->trees[client->prefix], client);
 }
 #endif
@@ -355,7 +469,7 @@ RADCLIENT *client_findbynumber(const RADCLIENT_LIST *clients,
  *     Find a client in the RADCLIENTS list.
  */
 RADCLIENT *client_find(const RADCLIENT_LIST *clients,
-                      const fr_ipaddr_t *ipaddr)
+                      const fr_ipaddr_t *ipaddr, int proto)
 {
        int i, max_prefix;
        RADCLIENT myclient;
@@ -382,6 +496,7 @@ RADCLIENT *client_find(const RADCLIENT_LIST *clients,
 
                myclient.prefix = i;
                myclient.ipaddr = *ipaddr;
+               myclient.proto = proto;
                client_sane(&myclient); /* clean up the ipaddress */
 
                if (!clients->trees[i]) continue;
@@ -401,11 +516,14 @@ RADCLIENT *client_find(const RADCLIENT_LIST *clients,
  */
 RADCLIENT *client_find_old(const fr_ipaddr_t *ipaddr)
 {
-       return client_find(root_clients, ipaddr);
+       return client_find(root_clients, ipaddr, IPPROTO_UDP);
 }
 
 static struct in_addr cl_ip4addr;
 static struct in6_addr cl_ip6addr;
+#ifdef WITH_TCP
+static char *hs_proto = NULL;
+#endif
 
 static const CONF_PARSER client_config[] = {
        { "ipaddr",  PW_TYPE_IPADDR,
@@ -433,11 +551,25 @@ static const CONF_PARSER client_config[] = {
        { "server",  PW_TYPE_STRING_PTR, /* compatability with 2.0-pre */
          offsetof(RADCLIENT, server), 0, NULL },
 
+#ifdef WITH_TCP
+       { "proto",  PW_TYPE_STRING_PTR,
+         0, &hs_proto, NULL },
+       { "max_connections",  PW_TYPE_INTEGER,
+         offsetof(RADCLIENT, max_connections), 0, "16" },
+#endif
+
 #ifdef WITH_DYNAMIC_CLIENTS
        { "dynamic_clients",  PW_TYPE_STRING_PTR,
          offsetof(RADCLIENT, client_server), 0, NULL },
        { "lifetime",  PW_TYPE_INTEGER,
          offsetof(RADCLIENT, lifetime), 0, NULL },
+       { "rate_limit",  PW_TYPE_BOOLEAN,
+         offsetof(RADCLIENT, rate_limit), 0, NULL },
+#endif
+
+#ifdef WITH_COA
+       { "coa_server",  PW_TYPE_STRING_PTR,
+         offsetof(RADCLIENT, coa_name), 0, NULL },
 #endif
 
        { NULL, -1, 0, NULL, NULL }
@@ -478,9 +610,15 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
        c->prefix = -1;
 
        if (cf_section_parse(cs, c, client_config) < 0) {
-               client_free(c);
                cf_log_err(cf_sectiontoitem(cs),
                           "Error parsing client section.");
+       error:
+               client_free(c);
+#ifdef WITH_TCP
+               free(hs_proto);
+               hs_proto = NULL;
+#endif
+
                return NULL;
        }
 
@@ -489,10 +627,9 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
         *      per-server clients cannot.
         */
        if (in_server && c->server) {
-               client_free(c);
                cf_log_err(cf_sectiontoitem(cs),
                           "Clients inside of an server section cannot point to a server.");
-               return NULL;
+               goto error;
        }
                
        /*
@@ -511,11 +648,10 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
                if (prefix_ptr) {
                        c->prefix = atoi(prefix_ptr + 1);
                        if ((c->prefix < 0) || (c->prefix > 128)) {
-                               client_free(c);
                                cf_log_err(cf_sectiontoitem(cs),
                                           "Invalid Prefix value '%s' for IP.",
                                           prefix_ptr + 1);
-                               return NULL;
+                               goto error;
                        }
                        /* Replace '/' with '\0' */
                        *prefix_ptr = '\0';
@@ -525,11 +661,10 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
                 *      Always get the numeric representation of IP
                 */
                if (ip_hton(name2, AF_UNSPEC, &c->ipaddr) < 0) {
-                       client_free(c);
                        cf_log_err(cf_sectiontoitem(cs),
                                   "Failed to look up hostname %s: %s",
                                   name2, fr_strerror());
-                       return NULL;
+                       goto error;
                }
 
                if (prefix_ptr) *prefix_ptr = '/';
@@ -548,10 +683,9 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
                        c->ipaddr.ipaddr.ip4addr = cl_ip4addr;
 
                        if ((c->prefix < -1) || (c->prefix > 32)) {
-                               client_free(c);
                                cf_log_err(cf_sectiontoitem(cs),
                                           "Netmask must be between 0 and 32");
-                               return NULL;
+                               goto error;
                        }
                                
                } else if (cf_pair_find(cs, "ipv6addr")) {
@@ -559,16 +693,14 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
                        c->ipaddr.ipaddr.ip6addr = cl_ip6addr;
                                
                        if ((c->prefix < -1) || (c->prefix > 128)) {
-                               client_free(c);
                                cf_log_err(cf_sectiontoitem(cs),
                                           "Netmask must be between 0 and 128");
-                               return NULL;
+                               goto error;
                        }
                } else {
                        cf_log_err(cf_sectiontoitem(cs),
                                   "No IP address defined for the client");
-                       client_free(c);
-                       return NULL;
+                       goto error;
                }
 
                ip_ntoh(&c->ipaddr, buffer, sizeof(buffer));
@@ -578,6 +710,31 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
                 *      Set the short name to the name2
                 */
                if (!c->shortname) c->shortname = strdup(name2);
+
+               c->proto = IPPROTO_UDP;
+#ifdef WITH_TCP
+               if (hs_proto) {
+                       if (strcmp(hs_proto, "udp") == 0) {
+                               free(hs_proto);
+                               hs_proto = NULL;
+                               
+                       } else if (strcmp(hs_proto, "tcp") == 0) {
+                               free(hs_proto);
+                               hs_proto = NULL;
+                               c->proto = IPPROTO_TCP;
+                               
+                       } else if (strcmp(hs_proto, "*") == 0) {
+                               free(hs_proto);
+                               hs_proto = NULL;
+                               c->proto = IPPROTO_IP; /* fake for dual */
+                               
+                       } else {
+                               cf_log_err(cf_sectiontoitem(cs),
+                                          "Unknown proto \"%s\".", hs_proto);
+                               goto error;
+                       }
+               }
+#endif
        }
 
        if (c->prefix < 0) switch (c->ipaddr.af) {
@@ -602,8 +759,7 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
                     (c->prefix == 128))) {
                        cf_log_err(cf_sectiontoitem(cs),
                                   "Dynamic clients MUST be a network, not a single IP address.");
-                       client_free(c);
-                       return NULL;
+                       goto error;
                }
 
                return c;
@@ -623,12 +779,29 @@ static RADCLIENT *client_parse(CONF_SECTION *cs, int in_server)
                if (value && (strcmp(value, "yes") == 0)) return c;
 
 #endif
-               client_free(c);
                cf_log_err(cf_sectiontoitem(cs),
                           "secret must be at least 1 character long");
-               return NULL;
+               goto error;
        }
 
+#ifdef WITH_COA
+       /*
+        *      Point the client to the home server pool, OR to the
+        *      home server.  This gets around the problem of figuring
+        *      out which port to use.
+        */
+       if (c->coa_name) {
+               c->coa_pool = home_pool_byname(c->coa_name, HOME_TYPE_COA);
+               if (!c->coa_pool) {
+                       c->coa_server = home_server_byname(c->coa_name,
+                                                          HOME_TYPE_COA);
+               }
+               if (!c->coa_pool && !c->coa_server) {
+                       cf_log_err(cf_sectiontoitem(cs), "No such home_server or home_server_pool \"%s\"", c->coa_name);
+                       goto error;
+               }
+       }
+#endif
 
        return c;
 }
@@ -900,7 +1073,7 @@ RADCLIENT *client_create(RADCLIENT_LIST *clients, REQUEST *request)
                        return NULL;
                }
 
-               vp = pairfind(request->config_items, da->attr);
+               vp = pairfind(request->config_items, da->attr, da->vendor);
                if (!vp) {
                        /*
                         *      Not required.  Skip it.