Browse Source

radius: initial host reservation implementation

Baptiste Jonglez 7 years ago
parent
commit
ecc96ee9e0
1 changed files with 178 additions and 16 deletions
  1. 178 16
      src/lib/dhcpsrv/radius_host_data_source.cc

+ 178 - 16
src/lib/dhcpsrv/radius_host_data_source.cc

@@ -75,6 +75,14 @@ const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_T
 namespace isc {
 namespace dhcp {
 
+static std::string getParameter(const DatabaseConnection::ParameterMap& parameters, const std::string& name) {
+    DatabaseConnection::ParameterMap::const_iterator param = parameters.find(name);
+    if (param == parameters.end()) {
+        isc_throw(BadValue, "Parameter " << name << " not found");
+    }
+    return (param->second);
+}
+
 RadiusHostDataSource::
 RadiusHostDataSource(const DatabaseConnection::ParameterMap& parameters) {
     int res;
@@ -82,13 +90,91 @@ RadiusHostDataSource(const DatabaseConnection::ParameterMap& parameters) {
     if (rh == NULL) {
          isc_throw(isc::Exception, "Failed to initialize Radius client");
     }
-    res = rc_add_config(rh, "authserver", "127.0.0.1", NULL, 0);
-    if (res != 0) {
+    rh = rc_config_init(rh);
+    if (rh == NULL) {
          isc_throw(isc::Exception, "Failed to initialize Radius client");
     }
+    res = rc_add_config(rh, "auth_order", "radius", NULL, 0);
+    if (res != 0) {
+         isc_throw(isc::Exception, "Failed to configure Radius auth_order");
+    }
+    /* TODO: just define manually the few attributes we need */
+    res = rc_add_config(rh, "dictionary", "/usr/share/radcli/dictionary", NULL, 0);
+    if (res != 0) {
+         isc_throw(isc::Exception, "Failed to configure Radius dictionary");
+    }
     res = rc_add_config(rh, "radius_timeout", "1", NULL, 0);
-    res = rc_add_config(rh, "radius_retries", "2", NULL, 0);
-    res = rc_add_config(rh, "serv-type", "tcp", NULL, 0);
+    if (res != 0) {
+         isc_throw(isc::Exception, "Failed to configure Radius timeout");
+    }
+    res = rc_add_config(rh, "radius_retries", "1", NULL, 0);
+    if (res != 0) {
+         isc_throw(isc::Exception, "Failed to configure Radius retries");
+    }
+
+    const char* host = "localhost";
+    string shost;
+    try {
+        shost = getParameter(parameters, "host");
+        host = shost.c_str();
+    } catch (...) {
+        // No host.  Fine, we'll use "localhost"
+    }
+
+    unsigned int port = 0;
+    string sport;
+    try {
+        sport = getParameter(parameters, "port");
+    } catch (...) {
+        // No port parameter, we are going to use the default port.
+        sport = "";
+    }
+
+    if (sport.size() > 0) {
+        // Port was given, so try to convert it to an integer.
+
+        try {
+            port = boost::lexical_cast<unsigned int>(sport);
+        } catch (...) {
+            // Port given but could not be converted to an unsigned int.
+            // Just fall back to the default value.
+            port = 0;
+        }
+
+        // The port is only valid when it is in the 0..65535 range.
+        // Again fall back to the default when the given value is invalid.
+        if (port > numeric_limits<uint16_t>::max()) {
+            port = 0;
+        }
+    }
+
+    const char* password = NULL;
+    string spassword;
+    try {
+        spassword = getParameter(parameters, "password");
+        password = spassword.c_str();
+    } catch (...) {
+        // No secret.  Throw an exception
+        isc_throw(isc::Exception, "must specify a secret (password) for Radius connection");
+    }
+
+    char authserver[512];
+    snprintf(authserver, sizeof(authserver), "%s:%u:%s", host, port, password);
+    res = rc_add_config(rh, "authserver", authserver, NULL, 0);
+    if (res != 0) {
+         isc_throw(isc::Exception, "Failed to configure Radius authserver");
+    }
+    // Test and apply config (this also setups the necessary structures to
+    // send requests to the radius server)
+    res = rc_test_config(rh, "kea");
+    if (res != 0) {
+         isc_throw(isc::Exception, "Failed to apply radcli configuration");
+    }
+    // Load dictionary
+    res = rc_read_dictionary(rh, rc_conf_str(rh, "dictionary"));
+    if (res != 0) {
+         isc_throw(isc::Exception, "Failed to read Radius dictionary");
+    }
 }
 
 RadiusHostDataSource::~RadiusHostDataSource() {
@@ -128,7 +214,16 @@ RadiusHostDataSource::del6(const SubnetID& subnet_id,
 ConstHostCollection
 RadiusHostDataSource::getAll(const HWAddrPtr& hwaddr,
                             const DuidPtr& duid) const {
-    // TODO: libradcli call
+    if (duid){
+        return (getAll(Host::IDENT_DUID, &duid->getDuid()[0],
+                       duid->getDuid().size()));
+
+    } else if (hwaddr) {
+        return (getAll(Host::IDENT_HWADDR,
+                       &hwaddr->hwaddr_[0],
+                       hwaddr->hwaddr_.size()));
+    }
+
     return (ConstHostCollection());
 }
 
@@ -136,8 +231,56 @@ ConstHostCollection
 RadiusHostDataSource::getAll(const Host::IdentifierType& identifier_type,
                             const uint8_t* identifier_begin,
                             const size_t identifier_len) const {
-    // TODO: libradcli call
-    return (ConstHostCollection());
+    ConstHostCollection result;
+    HostPtr host;
+    int res;
+    VALUE_PAIR 	*send = NULL, *received;
+    // Convert binary identifier (DUID or MAC address) to an hexadecimal
+    // string, with each byte separated by a colon.
+    std::stringstream tmp;
+    tmp << std::hex;
+    bool delim = false;
+    for (int i = 0; i < identifier_len; ++i) {
+        if (delim) {
+            tmp << ":";
+        }
+        tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(identifier_begin[i]);
+        delim = true;
+    }
+    // Add realm
+    tmp << "@radio.rezine.org";
+    // Necessary because of variable lifetime, see https://stackoverflow.com/a/1374485/4113356
+    const std::string tmp2 = tmp.str();
+    const char* identifier_hex = tmp2.c_str();
+    // Build radius request
+    if (rc_avpair_add(rh, &send, PW_USER_NAME, identifier_hex, -1, 0) == NULL)
+        isc_throw(isc::Exception, "Failed to set username");
+
+    res = rc_auth(rh, 0, send, &received, NULL);
+    if (res == OK_RC) {
+        VALUE_PAIR *vp = received;
+        char name[128];
+        char value[128];
+
+        fprintf(stderr, "\"%s\" RADIUS Authentication OK\n", identifier_hex);
+
+        /* parse the known attributes in the reply */
+        while(vp != NULL) {
+            if (rc_avpair_tostr(rh, vp, name, sizeof(name), value, sizeof(value)) == 0) {
+                if (std::string(name) == "Framed-IP-Address") {
+                    host.reset(new Host(identifier_begin, identifier_len,
+                                        identifier_type, SubnetID(),
+                                        SubnetID(), asiolink::IOAddress(value)));
+                    result.push_back(host);
+                }
+            }
+            vp = vp->next;
+        }
+    } else {
+        fprintf(stderr, "\"%s\" RADIUS Authentication failure (RC=%i)\n", identifier_hex, res);
+    }
+
+    return (result);
 }
 
 ConstHostCollection
@@ -147,10 +290,27 @@ RadiusHostDataSource::getAll4(const asiolink::IOAddress& address) const {
 
 ConstHostPtr
 RadiusHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
-                          const DuidPtr& duid) const {
-    // TODO: libradcli call
-    ConstHostPtr result = NULL;
-    return (result);
+                           const DuidPtr& duid) const {
+    if (hwaddr && duid) {
+        isc_throw(BadValue, "Radius host data source get4() called with both"
+                  " hwaddr and duid, only one of them is allowed");
+    }
+    if (!hwaddr && !duid) {
+        isc_throw(BadValue, "Radius host data source get4() called with "
+                  "neither hwaddr or duid specified, one of them is required");
+    }
+
+    // Choosing one of the identifiers
+    if (hwaddr) {
+        return (get4(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
+                     hwaddr->hwaddr_.size()));
+
+    } else if (duid) {
+        return (get4(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
+                     duid->getDuid().size()));
+    }
+
+    return (ConstHostPtr());
 }
 
 ConstHostPtr
@@ -158,17 +318,19 @@ RadiusHostDataSource::get4(const SubnetID& subnet_id,
                           const Host::IdentifierType& identifier_type,
                           const uint8_t* identifier_begin,
                           const size_t identifier_len) const {
-    // TODO: libradcli call
-    ConstHostPtr result = NULL;
+    ConstHostCollection collection = getAll(identifier_type, identifier_begin, identifier_len);
+    ConstHostPtr result;
+    if (!collection.empty())
+        result = *collection.begin();
     return (result);
 }
 
 ConstHostPtr
 RadiusHostDataSource::get4(const SubnetID& subnet_id,
                           const asiolink::IOAddress& address) const {
-    // TODO: libradcli call
-    ConstHostPtr result = NULL;
-    return (result);
+    // We always assume that there is no conflict between reserved
+    // addresses and dynamic addresses, so just return nothing here.
+    return (ConstHostPtr());
 }
 
 ConstHostPtr