radius_host_data_source.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. // Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. #include <config.h>
  7. #include <dhcp/libdhcp++.h>
  8. #include <dhcp/option.h>
  9. #include <dhcp/option_definition.h>
  10. #include <dhcp/option_space.h>
  11. #include <dhcpsrv/cfg_option.h>
  12. #include <dhcpsrv/db_exceptions.h>
  13. #include <dhcpsrv/dhcpsrv_log.h>
  14. #include <dhcpsrv/radius_host_data_source.h>
  15. #include <dhcpsrv/db_exceptions.h>
  16. #include <util/buffer.h>
  17. #include <util/optional_value.h>
  18. #include <boost/algorithm/string/split.hpp>
  19. #include <boost/algorithm/string/classification.hpp>
  20. #include <boost/array.hpp>
  21. #include <boost/pointer_cast.hpp>
  22. #include <boost/static_assert.hpp>
  23. #include <radcli/radcli.h>
  24. #include <stdint.h>
  25. #include <string>
  26. using namespace isc;
  27. using namespace isc::asiolink;
  28. using namespace isc::dhcp;
  29. using namespace isc::util;
  30. using namespace std;
  31. /// @brief Maximum size of an IPv6 address represented as a text string.
  32. ///
  33. /// This is 32 hexadecimal characters written in 8 groups of four, plus seven
  34. /// colon separators.
  35. const size_t ADDRESS6_TEXT_MAX_LEN = 39;
  36. /// @brief Maximum length of classes stored in a dhcp4/6_client_classes
  37. /// columns.
  38. const size_t CLIENT_CLASSES_MAX_LEN = 255;
  39. /// @brief Maximum length of the hostname stored in DNS.
  40. ///
  41. /// This length is restricted by the length of the domain-name carried
  42. /// in the Client FQDN %Option (see RFC4702 and RFC4704).
  43. const size_t HOSTNAME_MAX_LEN = 255;
  44. /// @brief Maximum length of option value.
  45. const size_t OPTION_VALUE_MAX_LEN = 4096;
  46. /// @brief Maximum length of option value specified in textual format.
  47. const size_t OPTION_FORMATTED_VALUE_MAX_LEN = 8192;
  48. /// @brief Maximum length of option space name.
  49. const size_t OPTION_SPACE_MAX_LEN = 128;
  50. /// @brief Maximum length of the server hostname.
  51. const size_t SERVER_HOSTNAME_MAX_LEN = 64;
  52. /// @brief Maximum length of the boot file name.
  53. const size_t BOOT_FILE_NAME_MAX_LEN = 128;
  54. /// @brief Numeric value representing last supported identifier.
  55. ///
  56. /// This value is used to validate whether the identifier type stored in
  57. /// a database is within bounds. of supported identifiers.
  58. const uint8_t MAX_IDENTIFIER_TYPE = static_cast<uint8_t>(Host::LAST_IDENTIFIER_TYPE);
  59. namespace isc {
  60. namespace dhcp {
  61. static std::string getParameter(const DatabaseConnection::ParameterMap& parameters, const std::string& name) {
  62. DatabaseConnection::ParameterMap::const_iterator param = parameters.find(name);
  63. if (param == parameters.end()) {
  64. isc_throw(BadValue, "Parameter " << name << " not found");
  65. }
  66. return (param->second);
  67. }
  68. RadiusHostDataSource::
  69. RadiusHostDataSource(const DatabaseConnection::ParameterMap& parameters) {
  70. int res;
  71. rh = rc_new();
  72. if (rh == NULL) {
  73. isc_throw(DbOpenError, "Failed to initialize Radius client");
  74. }
  75. rh = rc_config_init(rh);
  76. if (rh == NULL) {
  77. isc_throw(DbOpenError, "Failed to initialize Radius client");
  78. }
  79. res = rc_add_config(rh, "auth_order", "radius", NULL, 0);
  80. if (res != 0) {
  81. isc_throw(DbOpenError, "Failed to configure Radius auth_order");
  82. }
  83. /* TODO: just define manually the few attributes we need */
  84. res = rc_add_config(rh, "dictionary", "/usr/share/radcli/dictionary", NULL, 0);
  85. if (res != 0) {
  86. isc_throw(DbOpenError, "Failed to configure Radius dictionary");
  87. }
  88. res = rc_add_config(rh, "radius_timeout", "1", NULL, 0);
  89. if (res != 0) {
  90. isc_throw(DbOpenError, "Failed to configure Radius timeout");
  91. }
  92. res = rc_add_config(rh, "radius_retries", "1", NULL, 0);
  93. if (res != 0) {
  94. isc_throw(DbOpenError, "Failed to configure Radius retries");
  95. }
  96. try {
  97. realm_ = getParameter(parameters, "name");
  98. } catch (...) {
  99. // No realm. Throw an exception.
  100. isc_throw(NoDatabaseName, "must specify a database name (realm) for Radius connection");
  101. }
  102. const char* host = "localhost";
  103. string shost;
  104. try {
  105. shost = getParameter(parameters, "host");
  106. host = shost.c_str();
  107. } catch (...) {
  108. // No host. Fine, we'll use "localhost"
  109. }
  110. unsigned int port = 0;
  111. string sport;
  112. try {
  113. sport = getParameter(parameters, "port");
  114. } catch (...) {
  115. // No port parameter, we are going to use the default port.
  116. sport = "";
  117. }
  118. if (sport.size() > 0) {
  119. // Port was given, so try to convert it to an integer.
  120. try {
  121. port = boost::lexical_cast<unsigned int>(sport);
  122. } catch (...) {
  123. // Port given but could not be converted to an unsigned int.
  124. // Just fall back to the default value.
  125. port = 0;
  126. }
  127. // The port is only valid when it is in the 0..65535 range.
  128. // Again fall back to the default when the given value is invalid.
  129. if (port > numeric_limits<uint16_t>::max()) {
  130. port = 0;
  131. }
  132. }
  133. const char* password = NULL;
  134. string spassword;
  135. try {
  136. spassword = getParameter(parameters, "password");
  137. password = spassword.c_str();
  138. } catch (...) {
  139. // No secret. Throw an exception
  140. isc_throw(NoPassword, "must specify a secret (password) for Radius connection");
  141. }
  142. char authserver[512];
  143. snprintf(authserver, sizeof(authserver), "%s:%u:%s", host, port, password);
  144. res = rc_add_config(rh, "authserver", authserver, NULL, 0);
  145. if (res != 0) {
  146. isc_throw(DbOpenError, "Failed to configure Radius authserver");
  147. }
  148. // Test and apply config (this also setups the necessary structures to
  149. // send requests to the radius server)
  150. res = rc_test_config(rh, "kea");
  151. if (res != 0) {
  152. isc_throw(DbOpenError, "Failed to apply radcli configuration");
  153. }
  154. // Load dictionary
  155. res = rc_read_dictionary(rh, rc_conf_str(rh, "dictionary"));
  156. if (res != 0) {
  157. isc_throw(DbOpenError, "Failed to read Radius dictionary");
  158. }
  159. }
  160. RadiusHostDataSource::~RadiusHostDataSource() {
  161. }
  162. void
  163. RadiusHostDataSource::add(const HostPtr& host) {
  164. // cannot add a host with radius
  165. isc_throw(NotImplemented, "RadiusHostDataSource::add not implemented");
  166. }
  167. bool
  168. RadiusHostDataSource::del(const SubnetID& subnet_id, const asiolink::IOAddress& addr) {
  169. // cannot delete hosts with radius
  170. isc_throw(NotImplemented, "RadiusHostDataSource::del not implemented");
  171. return false;
  172. }
  173. bool
  174. RadiusHostDataSource::del4(const SubnetID& subnet_id,
  175. const Host::IdentifierType& identifier_type,
  176. const uint8_t* identifier_begin, const size_t identifier_len) {
  177. // cannot delete hosts with radius
  178. isc_throw(NotImplemented, "RadiusHostDataSource::del4 not implemented");
  179. return false;
  180. }
  181. bool
  182. RadiusHostDataSource::del6(const SubnetID& subnet_id,
  183. const Host::IdentifierType& identifier_type,
  184. const uint8_t* identifier_begin, const size_t identifier_len) {
  185. // cannot delete hosts with radius
  186. isc_throw(NotImplemented, "RadiusHostDataSource::del6 not implemented");
  187. return false;
  188. }
  189. ConstHostCollection
  190. RadiusHostDataSource::getAll(const HWAddrPtr& hwaddr,
  191. const DuidPtr& duid) const {
  192. if (duid){
  193. return (getAll(Host::IDENT_DUID, &duid->getDuid()[0],
  194. duid->getDuid().size()));
  195. } else if (hwaddr) {
  196. return (getAll(Host::IDENT_HWADDR,
  197. &hwaddr->hwaddr_[0],
  198. hwaddr->hwaddr_.size()));
  199. }
  200. return (ConstHostCollection());
  201. }
  202. ConstHostCollection
  203. RadiusHostDataSource::getAll(const Host::IdentifierType& identifier_type,
  204. const uint8_t* identifier_begin,
  205. const size_t identifier_len) const {
  206. ConstHostCollection result;
  207. HostPtr host;
  208. int res;
  209. VALUE_PAIR *send = NULL, *received;
  210. // Convert binary identifier (DUID or MAC address) to an hexadecimal
  211. // string, with each byte separated by a colon.
  212. std::stringstream tmp;
  213. tmp << std::hex;
  214. bool delim = false;
  215. for (int i = 0; i < identifier_len; ++i) {
  216. if (delim) {
  217. tmp << ":";
  218. }
  219. tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(identifier_begin[i]);
  220. delim = true;
  221. }
  222. // Add realm
  223. tmp << "@" << realm_;
  224. // Necessary because of variable lifetime, see https://stackoverflow.com/a/1374485/4113356
  225. const std::string tmp2 = tmp.str();
  226. const char* identifier_hex = tmp2.c_str();
  227. // Build radius request
  228. if (rc_avpair_add(rh, &send, PW_USER_NAME, identifier_hex, -1, 0) == NULL)
  229. isc_throw(DbOperationError, "Failed to set username");
  230. res = rc_auth(rh, 0, send, &received, NULL);
  231. if (res == OK_RC) {
  232. VALUE_PAIR *vp = received;
  233. char name[128];
  234. char value[128];
  235. HostPtr host(new Host(identifier_begin, identifier_len,
  236. identifier_type, SubnetID(),
  237. SubnetID(), asiolink::IOAddress::IPV4_ZERO_ADDRESS()));
  238. fprintf(stderr, "\"%s\" RADIUS Authentication OK\n", identifier_hex);
  239. /* parse the known attributes in the reply */
  240. while(vp != NULL) {
  241. if (rc_avpair_tostr(rh, vp, name, sizeof(name), value, sizeof(value)) == 0) {
  242. if (std::string(name) == "Framed-IP-Address") {
  243. host->setIPv4Reservation(asiolink::IOAddress(value));
  244. }
  245. if (std::string(name) == "Framed-IPv6-Address") {
  246. IPv6Resrv ipv6_addr(IPv6Resrv::TYPE_NA, asiolink::IOAddress(value), 128);
  247. host->addReservation(ipv6_addr);
  248. }
  249. if (std::string(name) == "Delegated-IPv6-Prefix") {
  250. /* Split "prefix/prefixlen" appropriately */
  251. std::string value_str(value);
  252. size_t pos = value_str.find('/');
  253. asiolink::IOAddress prefix(value_str.substr(0, pos));
  254. uint8_t prefixlen = std::stoi(value_str.substr(pos + 1));
  255. IPv6Resrv ipv6_prefix(IPv6Resrv::TYPE_PD, prefix, prefixlen);
  256. host->addReservation(ipv6_prefix);
  257. }
  258. }
  259. vp = vp->next;
  260. }
  261. result.push_back(host);
  262. } else {
  263. fprintf(stderr, "\"%s\" RADIUS Authentication failure (RC=%i)\n", identifier_hex, res);
  264. }
  265. return (result);
  266. }
  267. ConstHostCollection
  268. RadiusHostDataSource::getAll4(const asiolink::IOAddress& address) const {
  269. return (ConstHostCollection());
  270. }
  271. ConstHostPtr
  272. RadiusHostDataSource::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
  273. const DuidPtr& duid) const {
  274. if (hwaddr && duid) {
  275. isc_throw(BadValue, "Radius host data source get4() called with both"
  276. " hwaddr and duid, only one of them is allowed");
  277. }
  278. if (!hwaddr && !duid) {
  279. isc_throw(BadValue, "Radius host data source get4() called with "
  280. "neither hwaddr or duid specified, one of them is required");
  281. }
  282. // Choosing one of the identifiers
  283. if (hwaddr) {
  284. return (get4(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  285. hwaddr->hwaddr_.size()));
  286. } else if (duid) {
  287. return (get4(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  288. duid->getDuid().size()));
  289. }
  290. return (ConstHostPtr());
  291. }
  292. ConstHostPtr
  293. RadiusHostDataSource::get4(const SubnetID& subnet_id,
  294. const Host::IdentifierType& identifier_type,
  295. const uint8_t* identifier_begin,
  296. const size_t identifier_len) const {
  297. ConstHostCollection collection = getAll(identifier_type, identifier_begin, identifier_len);
  298. ConstHostPtr result;
  299. if (!collection.empty())
  300. result = *collection.begin();
  301. return (result);
  302. }
  303. ConstHostPtr
  304. RadiusHostDataSource::get4(const SubnetID& subnet_id,
  305. const asiolink::IOAddress& address) const {
  306. // We always assume that there is no conflict between reserved
  307. // addresses and dynamic addresses, so just return nothing here.
  308. return (ConstHostPtr());
  309. }
  310. ConstHostPtr
  311. RadiusHostDataSource::get6(const SubnetID& subnet_id, const DuidPtr& duid,
  312. const HWAddrPtr& hwaddr) const {
  313. if (hwaddr && duid) {
  314. isc_throw(BadValue, "Radius host data source get6() called with both"
  315. " hwaddr and duid, only one of them is allowed");
  316. }
  317. if (!hwaddr && !duid) {
  318. isc_throw(BadValue, "Radius host data source get6() called with "
  319. "neither hwaddr or duid specified, one of them is required");
  320. }
  321. // Choosing one of the identifiers
  322. if (hwaddr) {
  323. return (get6(subnet_id, Host::IDENT_HWADDR, &hwaddr->hwaddr_[0],
  324. hwaddr->hwaddr_.size()));
  325. } else if (duid) {
  326. return (get6(subnet_id, Host::IDENT_DUID, &duid->getDuid()[0],
  327. duid->getDuid().size()));
  328. }
  329. return (ConstHostPtr());
  330. }
  331. ConstHostPtr
  332. RadiusHostDataSource::get6(const SubnetID& subnet_id,
  333. const Host::IdentifierType& identifier_type,
  334. const uint8_t* identifier_begin,
  335. const size_t identifier_len) const {
  336. ConstHostCollection collection = getAll(identifier_type, identifier_begin, identifier_len);
  337. ConstHostPtr result;
  338. if (!collection.empty())
  339. result = *collection.begin();
  340. return (result);
  341. }
  342. ConstHostPtr
  343. RadiusHostDataSource::get6(const asiolink::IOAddress& prefix,
  344. const uint8_t prefix_len) const {
  345. // We always assume that there is no conflict between reserved
  346. // prefixes and dynamic prefixes, so just return nothing here.
  347. return (ConstHostPtr());
  348. }
  349. ConstHostPtr
  350. RadiusHostDataSource::get6(const SubnetID& subnet_id,
  351. const asiolink::IOAddress& address) const {
  352. // We always assume that there is no conflict between reserved
  353. // addresses and dynamic addresses, so just return nothing here.
  354. return (ConstHostPtr());
  355. }
  356. // Miscellaneous database methods.
  357. std::string RadiusHostDataSource::getName() const {
  358. std::string name = "";
  359. return (name);
  360. }
  361. std::string RadiusHostDataSource::getDescription() const {
  362. return (std::string("Host data source that retrieves host information"
  363. "in radius server"));
  364. }
  365. std::pair<uint32_t, uint32_t> RadiusHostDataSource::getVersion() const {
  366. // TODO: Not relevant for libradcli ?
  367. return std::make_pair(0,0);
  368. }
  369. void
  370. RadiusHostDataSource::commit() {
  371. // Not relevant for radius.
  372. }
  373. void
  374. RadiusHostDataSource::rollback() {
  375. // Not relevant for radius.
  376. }
  377. }; // end of isc::dhcp namespace
  378. }; // end of isc namespace