ncr_msg.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. // Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
  2. //
  3. // Permission to use, copy, modify, and/or distribute this software for any
  4. // purpose with or without fee is hereby granted, provided that the above
  5. // copyright notice and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  9. // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
  12. // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. #include <dhcp_ddns/ncr_msg.h>
  15. #include <asiolink/io_address.h>
  16. #include <asiolink/io_error.h>
  17. #include <cryptolink/cryptolink.h>
  18. #include <botan/sha2_32.h>
  19. #include <sstream>
  20. #include <limits>
  21. namespace isc {
  22. namespace dhcp_ddns {
  23. /********************************* D2Dhcid ************************************/
  24. namespace {
  25. ///
  26. /// @name Constants which define DHCID identifier-type
  27. //@{
  28. /// DHCID created from client's HW address.
  29. const uint8_t DHCID_ID_HWADDR = 0x0;
  30. /// DHCID created from client identifier.
  31. const uint8_t DHCID_ID_CLIENTID = 0x1;
  32. /// DHCID created from DUID.
  33. const uint8_t DHCID_ID_DUID = 0x2;
  34. }
  35. D2Dhcid::D2Dhcid() {
  36. }
  37. D2Dhcid::D2Dhcid(const std::string& data) {
  38. fromStr(data);
  39. }
  40. D2Dhcid::D2Dhcid(const isc::dhcp::HWAddrPtr& hwaddr,
  41. const std::vector<uint8_t>& wire_fqdn) {
  42. fromHWAddr(hwaddr, wire_fqdn);
  43. }
  44. D2Dhcid::D2Dhcid(const std::vector<uint8_t>& clientid_data,
  45. const std::vector<uint8_t>& wire_fqdn) {
  46. fromClientId(clientid_data, wire_fqdn);
  47. }
  48. D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
  49. const std::vector<uint8_t>& wire_fqdn) {
  50. fromDUID(duid, wire_fqdn);
  51. }
  52. void
  53. D2Dhcid::fromStr(const std::string& data) {
  54. bytes_.clear();
  55. try {
  56. isc::util::encode::decodeHex(data, bytes_);
  57. } catch (const isc::Exception& ex) {
  58. isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
  59. }
  60. }
  61. std::string
  62. D2Dhcid::toStr() const {
  63. return (isc::util::encode::encodeHex(bytes_));
  64. }
  65. void
  66. D2Dhcid::fromClientId(const std::vector<uint8_t>& clientid_data,
  67. const std::vector<uint8_t>& wire_fqdn) {
  68. createDigest(DHCID_ID_CLIENTID, clientid_data, wire_fqdn);
  69. }
  70. void
  71. D2Dhcid::fromHWAddr(const isc::dhcp::HWAddrPtr& hwaddr,
  72. const std::vector<uint8_t>& wire_fqdn) {
  73. if (!hwaddr) {
  74. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  75. "unable to compute DHCID from the HW address, "
  76. "NULL pointer has been specified");
  77. } else if (hwaddr->hwaddr_.empty()) {
  78. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  79. "unable to compute DHCID from the HW address, "
  80. "HW address is empty");
  81. }
  82. std::vector<uint8_t> hwaddr_data;
  83. hwaddr_data.push_back(hwaddr->htype_);
  84. hwaddr_data.insert(hwaddr_data.end(), hwaddr->hwaddr_.begin(),
  85. hwaddr->hwaddr_.end());
  86. createDigest(DHCID_ID_HWADDR, hwaddr_data, wire_fqdn);
  87. }
  88. void
  89. D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
  90. const std::vector<uint8_t>& wire_fqdn) {
  91. createDigest(DHCID_ID_DUID, duid.getDuid(), wire_fqdn);
  92. }
  93. void
  94. D2Dhcid::createDigest(const uint8_t identifier_type,
  95. const std::vector<uint8_t>& identifier_data,
  96. const std::vector<uint8_t>& wire_fqdn) {
  97. // We get FQDN in the wire format, so we don't know if it is
  98. // valid. It is caller's responsibility to make sure it is in
  99. // the valid format. Here we just make sure it is not empty.
  100. if (wire_fqdn.empty()) {
  101. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  102. "empty FQDN used to create DHCID");
  103. }
  104. // It is a responsibility of the classes which encapsulate client
  105. // identifiers, e.g. DUID, to validate the client identifier data.
  106. // But let's be on the safe side and at least check that it is not
  107. // empty.
  108. if (identifier_data.empty()) {
  109. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  110. "empty DUID used to create DHCID");
  111. }
  112. // A data buffer will be used to compute the digest.
  113. std::vector<uint8_t> data = identifier_data;
  114. // Append FQDN in the wire format.
  115. data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
  116. // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
  117. // The getCryptoLink is a common function to initialize the Botan library.
  118. cryptolink::CryptoLink::getCryptoLink();
  119. // @todo The code below, which calculates the SHA-256 may need to be moved
  120. // to the cryptolink library.
  121. Botan::SecureVector<Botan::byte> secure;
  122. try {
  123. Botan::SHA_256 sha;
  124. // We have checked already that the DUID and FQDN aren't empty
  125. // so it is safe to assume that the data buffer is not empty.
  126. secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
  127. data.size());
  128. } catch (const std::exception& ex) {
  129. isc_throw(isc::dhcp_ddns::DhcidRdataComputeError,
  130. "error while generating DHCID from DUID: "
  131. << ex.what());
  132. }
  133. // The DHCID RDATA has the following structure:
  134. //
  135. // < identifier-type > < digest-type > < digest >
  136. //
  137. // where identifier type
  138. // Let's allocate the space for the identifier-type (2 bytes) and
  139. // digest-type (1 byte). This is 3 bytes all together.
  140. bytes_.resize(3);
  141. // Leave first byte 0 and set the second byte. Those two bytes
  142. // form the identifier-type.
  143. bytes_[1] = identifier_type;
  144. // Third byte is always equal to 1, which specifies SHA-256 digest type.
  145. bytes_[2] = 1;
  146. // Now let's append the digest.
  147. bytes_.insert(bytes_.end(), secure.begin(), secure.end());
  148. }
  149. /**************************** NameChangeRequest ******************************/
  150. NameChangeRequest::NameChangeRequest()
  151. : change_type_(CHG_ADD), forward_change_(false),
  152. reverse_change_(false), fqdn_(""), ip_address_(""),
  153. dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
  154. }
  155. NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
  156. const bool forward_change, const bool reverse_change,
  157. const std::string& fqdn, const std::string& ip_address,
  158. const D2Dhcid& dhcid,
  159. const uint64_t lease_expires_on,
  160. const uint32_t lease_length)
  161. : change_type_(change_type), forward_change_(forward_change),
  162. reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
  163. dhcid_(dhcid), lease_expires_on_(lease_expires_on),
  164. lease_length_(lease_length), status_(ST_NEW) {
  165. // Validate the contents. This will throw a NcrMessageError if anything
  166. // is invalid.
  167. validateContent();
  168. }
  169. NameChangeRequestPtr
  170. NameChangeRequest::fromFormat(const NameChangeFormat format,
  171. isc::util::InputBuffer& buffer) {
  172. // Based on the format requested, pull the marshalled request from
  173. // InputBuffer and pass it into the appropriate format-specific factory.
  174. NameChangeRequestPtr ncr;
  175. switch (format) {
  176. case FMT_JSON: {
  177. try {
  178. // Get the length of the JSON text.
  179. size_t len = buffer.readUint16();
  180. // Read the text from the buffer into a vector.
  181. std::vector<uint8_t> vec;
  182. buffer.readVector(vec, len);
  183. // Turn the vector into a string.
  184. std::string string_data(vec.begin(), vec.end());
  185. // Pass the string of JSON text into JSON factory to create the
  186. // NameChangeRequest instance. Note the factory may throw
  187. // NcrMessageError.
  188. ncr = NameChangeRequest::fromJSON(string_data);
  189. } catch (isc::util::InvalidBufferPosition& ex) {
  190. // Read error accessing data in InputBuffer.
  191. isc_throw(NcrMessageError, "fromFormat: buffer read error: "
  192. << ex.what());
  193. }
  194. break;
  195. }
  196. default:
  197. // Programmatic error, shouldn't happen.
  198. isc_throw(NcrMessageError, "fromFormat - invalid format");
  199. break;
  200. }
  201. return (ncr);
  202. }
  203. void
  204. NameChangeRequest::toFormat(const NameChangeFormat format,
  205. isc::util::OutputBuffer& buffer) const {
  206. // Based on the format requested, invoke the appropriate format handler
  207. // which will marshal this request's contents into the OutputBuffer.
  208. switch (format) {
  209. case FMT_JSON: {
  210. // Invoke toJSON to create a JSON text of this request's contents.
  211. std::string json = toJSON();
  212. uint16_t length = json.size();
  213. // Write the length of the JSON text to the OutputBuffer first, then
  214. // write the JSON text itself.
  215. buffer.writeUint16(length);
  216. buffer.writeData(json.c_str(), length);
  217. break;
  218. }
  219. default:
  220. // Programmatic error, shouldn't happen.
  221. isc_throw(NcrMessageError, "toFormat - invalid format");
  222. break;
  223. }
  224. }
  225. NameChangeRequestPtr
  226. NameChangeRequest::fromJSON(const std::string& json) {
  227. // This method leverages the existing JSON parsing provided by isc::data
  228. // library. Should this prove to be a performance issue, it may be that
  229. // lighter weight solution would be appropriate.
  230. // Turn the string of JSON text into an Element set.
  231. isc::data::ElementPtr elements;
  232. try {
  233. elements = isc::data::Element::fromJSON(json);
  234. } catch (isc::data::JSONError& ex) {
  235. isc_throw(NcrMessageError,
  236. "Malformed NameChangeRequest JSON: " << ex.what());
  237. }
  238. // Get a map of the Elements, keyed by element name.
  239. ElementMap element_map = elements->mapValue();
  240. isc::data::ConstElementPtr element;
  241. // Use default constructor to create a "blank" NameChangeRequest.
  242. NameChangeRequestPtr ncr(new NameChangeRequest());
  243. // For each member of NameChangeRequest, find its element in the map and
  244. // call the appropriate Element-based setter. These setters may throw
  245. // NcrMessageError if the given Element is the wrong type or its data
  246. // content is lexically invalid. If the element is NOT found in the
  247. // map, getElement will throw NcrMessageError indicating the missing
  248. // member. Currently there are no optional values.
  249. element = ncr->getElement("change_type", element_map);
  250. ncr->setChangeType(element);
  251. element = ncr->getElement("forward_change", element_map);
  252. ncr->setForwardChange(element);
  253. element = ncr->getElement("reverse_change", element_map);
  254. ncr->setReverseChange(element);
  255. element = ncr->getElement("fqdn", element_map);
  256. ncr->setFqdn(element);
  257. element = ncr->getElement("ip_address", element_map);
  258. ncr->setIpAddress(element);
  259. element = ncr->getElement("dhcid", element_map);
  260. ncr->setDhcid(element);
  261. element = ncr->getElement("lease_expires_on", element_map);
  262. ncr->setLeaseExpiresOn(element);
  263. element = ncr->getElement("lease_length", element_map);
  264. ncr->setLeaseLength(element);
  265. // All members were in the Element set and were correct lexically. Now
  266. // validate the overall content semantically. This will throw an
  267. // NcrMessageError if anything is amiss.
  268. ncr->validateContent();
  269. // Everything is valid, return the new instance.
  270. return (ncr);
  271. }
  272. std::string
  273. NameChangeRequest::toJSON() const {
  274. // Create a JSON string of this request's contents. Note that this method
  275. // does NOT use the isc::data library as generating the output is straight
  276. // forward.
  277. std::ostringstream stream;
  278. stream << "{\"change_type\":" << getChangeType() << ","
  279. << "\"forward_change\":"
  280. << (isForwardChange() ? "true" : "false") << ","
  281. << "\"reverse_change\":"
  282. << (isReverseChange() ? "true" : "false") << ","
  283. << "\"fqdn\":\"" << getFqdn() << "\","
  284. << "\"ip_address\":\"" << getIpAddress() << "\","
  285. << "\"dhcid\":\"" << getDhcid().toStr() << "\","
  286. << "\"lease_expires_on\":\"" << getLeaseExpiresOnStr() << "\","
  287. << "\"lease_length\":" << getLeaseLength() << "}";
  288. return (stream.str());
  289. }
  290. void
  291. NameChangeRequest::validateContent() {
  292. //@todo This is an initial implementation which provides a minimal amount
  293. // of validation. FQDN, DHCID, and IP Address members are all currently
  294. // strings, these may be replaced with richer classes.
  295. if (fqdn_ == "") {
  296. isc_throw(NcrMessageError, "FQDN cannot be blank");
  297. }
  298. // Validate IP Address.
  299. try {
  300. isc::asiolink::IOAddress io_addr(ip_address_);
  301. } catch (const isc::asiolink::IOError& ex) {
  302. isc_throw(NcrMessageError,
  303. "Invalid ip address string for ip_address: " << ip_address_);
  304. }
  305. // Validate the DHCID.
  306. if (dhcid_.getBytes().size() == 0) {
  307. isc_throw(NcrMessageError, "DHCID cannot be blank");
  308. }
  309. // Ensure the request specifies at least one direction to update.
  310. if (!forward_change_ && !reverse_change_) {
  311. isc_throw(NcrMessageError,
  312. "Invalid Request, forward and reverse flags are both false");
  313. }
  314. }
  315. isc::data::ConstElementPtr
  316. NameChangeRequest::getElement(const std::string& name,
  317. const ElementMap& element_map) const {
  318. // Look for "name" in the element map.
  319. ElementMap::const_iterator it = element_map.find(name);
  320. if (it == element_map.end()) {
  321. // Didn't find the element, so throw.
  322. isc_throw(NcrMessageError,
  323. "NameChangeRequest value missing for: " << name );
  324. }
  325. // Found the element, return it.
  326. return (it->second);
  327. }
  328. void
  329. NameChangeRequest::setChangeType(const NameChangeType value) {
  330. change_type_ = value;
  331. }
  332. void
  333. NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
  334. long raw_value = -1;
  335. try {
  336. // Get the element's integer value.
  337. raw_value = element->intValue();
  338. } catch (isc::data::TypeError& ex) {
  339. // We expect a integer Element type, don't have one.
  340. isc_throw(NcrMessageError,
  341. "Wrong data type for change_type: " << ex.what());
  342. }
  343. if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
  344. // Value is not a valid change type.
  345. isc_throw(NcrMessageError,
  346. "Invalid data value for change_type: " << raw_value);
  347. }
  348. // Good to go, make the assignment.
  349. setChangeType(static_cast<NameChangeType>(raw_value));
  350. }
  351. void
  352. NameChangeRequest::setForwardChange(const bool value) {
  353. forward_change_ = value;
  354. }
  355. void
  356. NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
  357. bool value;
  358. try {
  359. // Get the element's boolean value.
  360. value = element->boolValue();
  361. } catch (isc::data::TypeError& ex) {
  362. // We expect a boolean Element type, don't have one.
  363. isc_throw(NcrMessageError,
  364. "Wrong data type for forward_change :" << ex.what());
  365. }
  366. // Good to go, make the assignment.
  367. setForwardChange(value);
  368. }
  369. void
  370. NameChangeRequest::setReverseChange(const bool value) {
  371. reverse_change_ = value;
  372. }
  373. void
  374. NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
  375. bool value;
  376. try {
  377. // Get the element's boolean value.
  378. value = element->boolValue();
  379. } catch (isc::data::TypeError& ex) {
  380. // We expect a boolean Element type, don't have one.
  381. isc_throw(NcrMessageError,
  382. "Wrong data type for reverse_change :" << ex.what());
  383. }
  384. // Good to go, make the assignment.
  385. setReverseChange(value);
  386. }
  387. void
  388. NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
  389. setFqdn(element->stringValue());
  390. }
  391. void
  392. NameChangeRequest::setFqdn(const std::string& value) {
  393. fqdn_ = value;
  394. }
  395. void
  396. NameChangeRequest::setIpAddress(const std::string& value) {
  397. ip_address_ = value;
  398. }
  399. void
  400. NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
  401. setIpAddress(element->stringValue());
  402. }
  403. void
  404. NameChangeRequest::setDhcid(const std::string& value) {
  405. dhcid_.fromStr(value);
  406. }
  407. void
  408. NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
  409. setDhcid(element->stringValue());
  410. }
  411. std::string
  412. NameChangeRequest::getLeaseExpiresOnStr() const {
  413. return (isc::util::timeToText64(lease_expires_on_));
  414. }
  415. void
  416. NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
  417. try {
  418. lease_expires_on_ = isc::util::timeFromText64(value);
  419. } catch(...) {
  420. // We were given an invalid string, so throw.
  421. isc_throw(NcrMessageError,
  422. "Invalid date-time string: [" << value << "]");
  423. }
  424. }
  425. void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
  426. // Pull out the string value and pass it into the string setter.
  427. setLeaseExpiresOn(element->stringValue());
  428. }
  429. void
  430. NameChangeRequest::setLeaseLength(const uint32_t value) {
  431. lease_length_ = value;
  432. }
  433. void
  434. NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
  435. long value = -1;
  436. try {
  437. // Get the element's integer value.
  438. value = element->intValue();
  439. } catch (isc::data::TypeError& ex) {
  440. // We expect a integer Element type, don't have one.
  441. isc_throw(NcrMessageError,
  442. "Wrong data type for lease_length: " << ex.what());
  443. }
  444. // Make sure we the range is correct and value is positive.
  445. if (value > std::numeric_limits<uint32_t>::max()) {
  446. isc_throw(NcrMessageError, "lease_length value " << value <<
  447. "is too large for unsigned 32-bit integer.");
  448. }
  449. if (value < 0) {
  450. isc_throw(NcrMessageError, "lease_length value " << value <<
  451. "is negative. It must greater than or equal to zero ");
  452. }
  453. // Good to go, make the assignment.
  454. setLeaseLength(static_cast<uint32_t>(value));
  455. }
  456. void
  457. NameChangeRequest::setStatus(const NameChangeStatus value) {
  458. status_ = value;
  459. }
  460. std::string
  461. NameChangeRequest::toText() const {
  462. std::ostringstream stream;
  463. stream << "Type: " << static_cast<int>(change_type_) << " (";
  464. switch (change_type_) {
  465. case CHG_ADD:
  466. stream << "CHG_ADD)\n";
  467. break;
  468. case CHG_REMOVE:
  469. stream << "CHG_REMOVE)\n";
  470. break;
  471. default:
  472. // Shouldn't be possible.
  473. stream << "Invalid Value\n";
  474. }
  475. stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
  476. << std::endl
  477. << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
  478. << std::endl
  479. << "FQDN: [" << fqdn_ << "]" << std::endl
  480. << "IP Address: [" << ip_address_ << "]" << std::endl
  481. << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
  482. << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
  483. << "Lease Length: " << lease_length_ << std::endl;
  484. return (stream.str());
  485. }
  486. bool
  487. NameChangeRequest::operator == (const NameChangeRequest& other) {
  488. return ((change_type_ == other.change_type_) &&
  489. (forward_change_ == other.forward_change_) &&
  490. (reverse_change_ == other.reverse_change_) &&
  491. (fqdn_ == other.fqdn_) &&
  492. (ip_address_ == other.ip_address_) &&
  493. (dhcid_ == other.dhcid_) &&
  494. (lease_expires_on_ == other.lease_expires_on_) &&
  495. (lease_length_ == other.lease_length_));
  496. }
  497. bool
  498. NameChangeRequest::operator != (const NameChangeRequest& other) {
  499. return (!(*this == other));
  500. }
  501. }; // end of isc::dhcp namespace
  502. }; // end of isc namespace