ncr_msg.cc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  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. D2Dhcid::D2Dhcid() {
  25. }
  26. D2Dhcid::D2Dhcid(const std::string& data) {
  27. fromStr(data);
  28. }
  29. D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
  30. const std::vector<uint8_t>& wire_fqdn) {
  31. fromDUID(duid, wire_fqdn);
  32. }
  33. void
  34. D2Dhcid::fromStr(const std::string& data) {
  35. bytes_.clear();
  36. try {
  37. isc::util::encode::decodeHex(data, bytes_);
  38. } catch (const isc::Exception& ex) {
  39. isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
  40. }
  41. }
  42. std::string
  43. D2Dhcid::toStr() const {
  44. return (isc::util::encode::encodeHex(bytes_));
  45. }
  46. void
  47. D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
  48. const std::vector<uint8_t>& wire_fqdn) {
  49. // DHCID created from DUID starts with two bytes representing
  50. // a type of the identifier. The value of 0x0002 indicates that
  51. // DHCID has been created from DUID. The 3rd byte is equal to 1
  52. // which indicates that the SHA-256 algorithm is used to create
  53. // a DHCID digest. This value is called digest-type.
  54. static uint8_t dhcid_header[] = { 0x00, 0x02, 0x01 };
  55. // We get FQDN in the wire format, so we don't know if it is
  56. // valid. It is caller's responsibility to make sure it is in
  57. // the valid format. Here we just make sure it is not empty.
  58. if (wire_fqdn.empty()) {
  59. isc_throw(isc::dhcp_ddns::NcrMessageError,
  60. "empty FQDN used to create DHCID");
  61. }
  62. // Get the wire representation of the DUID.
  63. std::vector<uint8_t> data = duid.getDuid();
  64. // It should be DUID class responsibility to validate the DUID
  65. // but let's be on the safe side here and make sure that empty
  66. // DUID is not returned.
  67. if (data.empty()) {
  68. isc_throw(isc::dhcp_ddns::NcrMessageError,
  69. "empty DUID used to create DHCID");
  70. }
  71. // Append FQDN in the wire format.
  72. data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
  73. // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
  74. // The getCryptoLink is a common function to initialize the Botan library.
  75. cryptolink::CryptoLink::getCryptoLink();
  76. // @todo The code below, which calculates the SHA-256 may need to be moved
  77. // to the cryptolink library.
  78. Botan::SecureVector<Botan::byte> secure;
  79. try {
  80. Botan::SHA_256 sha;
  81. // We have checked already that the DUID and FQDN aren't empty
  82. // so it is safe to assume that the data buffer is not empty.
  83. secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
  84. data.size());
  85. } catch (const std::exception& ex) {
  86. isc_throw(isc::dhcp_ddns::NcrMessageError,
  87. "error while generating DHCID from DUID: "
  88. << ex.what());
  89. }
  90. // The exception unsafe part is finished, so we can finally replace
  91. // the contents of bytes_.
  92. bytes_.assign(dhcid_header, dhcid_header + sizeof(dhcid_header));
  93. bytes_.insert(bytes_.end(), secure.begin(), secure.end());
  94. }
  95. std::ostream&
  96. operator<<(std::ostream& os, const D2Dhcid& dhcid) {
  97. os << dhcid.toStr();
  98. return (os);
  99. }
  100. /**************************** NameChangeRequest ******************************/
  101. NameChangeRequest::NameChangeRequest()
  102. : change_type_(CHG_ADD), forward_change_(false),
  103. reverse_change_(false), fqdn_(""), ip_address_(""),
  104. dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
  105. }
  106. NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
  107. const bool forward_change, const bool reverse_change,
  108. const std::string& fqdn, const std::string& ip_address,
  109. const D2Dhcid& dhcid,
  110. const uint64_t lease_expires_on,
  111. const uint32_t lease_length)
  112. : change_type_(change_type), forward_change_(forward_change),
  113. reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
  114. dhcid_(dhcid), lease_expires_on_(lease_expires_on),
  115. lease_length_(lease_length), status_(ST_NEW) {
  116. // Validate the contents. This will throw a NcrMessageError if anything
  117. // is invalid.
  118. validateContent();
  119. }
  120. NameChangeRequestPtr
  121. NameChangeRequest::fromFormat(const NameChangeFormat format,
  122. isc::util::InputBuffer& buffer) {
  123. // Based on the format requested, pull the marshalled request from
  124. // InputBuffer and pass it into the appropriate format-specific factory.
  125. NameChangeRequestPtr ncr;
  126. switch (format) {
  127. case FMT_JSON: {
  128. try {
  129. // Get the length of the JSON text.
  130. size_t len = buffer.readUint16();
  131. // Read the text from the buffer into a vector.
  132. std::vector<uint8_t> vec;
  133. buffer.readVector(vec, len);
  134. // Turn the vector into a string.
  135. std::string string_data(vec.begin(), vec.end());
  136. // Pass the string of JSON text into JSON factory to create the
  137. // NameChangeRequest instance. Note the factory may throw
  138. // NcrMessageError.
  139. ncr = NameChangeRequest::fromJSON(string_data);
  140. } catch (isc::util::InvalidBufferPosition& ex) {
  141. // Read error accessing data in InputBuffer.
  142. isc_throw(NcrMessageError, "fromFormat: buffer read error: "
  143. << ex.what());
  144. }
  145. break;
  146. }
  147. default:
  148. // Programmatic error, shouldn't happen.
  149. isc_throw(NcrMessageError, "fromFormat - invalid format");
  150. break;
  151. }
  152. return (ncr);
  153. }
  154. void
  155. NameChangeRequest::toFormat(const NameChangeFormat format,
  156. isc::util::OutputBuffer& buffer) const {
  157. // Based on the format requested, invoke the appropriate format handler
  158. // which will marshal this request's contents into the OutputBuffer.
  159. switch (format) {
  160. case FMT_JSON: {
  161. // Invoke toJSON to create a JSON text of this request's contents.
  162. std::string json = toJSON();
  163. uint16_t length = json.size();
  164. // Write the length of the JSON text to the OutputBuffer first, then
  165. // write the JSON text itself.
  166. buffer.writeUint16(length);
  167. buffer.writeData(json.c_str(), length);
  168. break;
  169. }
  170. default:
  171. // Programmatic error, shouldn't happen.
  172. isc_throw(NcrMessageError, "toFormat - invalid format");
  173. break;
  174. }
  175. }
  176. NameChangeRequestPtr
  177. NameChangeRequest::fromJSON(const std::string& json) {
  178. // This method leverages the existing JSON parsing provided by isc::data
  179. // library. Should this prove to be a performance issue, it may be that
  180. // lighter weight solution would be appropriate.
  181. // Turn the string of JSON text into an Element set.
  182. isc::data::ElementPtr elements;
  183. try {
  184. elements = isc::data::Element::fromJSON(json);
  185. } catch (isc::data::JSONError& ex) {
  186. isc_throw(NcrMessageError,
  187. "Malformed NameChangeRequest JSON: " << ex.what());
  188. }
  189. // Get a map of the Elements, keyed by element name.
  190. ElementMap element_map = elements->mapValue();
  191. isc::data::ConstElementPtr element;
  192. // Use default constructor to create a "blank" NameChangeRequest.
  193. NameChangeRequestPtr ncr(new NameChangeRequest());
  194. // For each member of NameChangeRequest, find its element in the map and
  195. // call the appropriate Element-based setter. These setters may throw
  196. // NcrMessageError if the given Element is the wrong type or its data
  197. // content is lexically invalid. If the element is NOT found in the
  198. // map, getElement will throw NcrMessageError indicating the missing
  199. // member. Currently there are no optional values.
  200. element = ncr->getElement("change_type", element_map);
  201. ncr->setChangeType(element);
  202. element = ncr->getElement("forward_change", element_map);
  203. ncr->setForwardChange(element);
  204. element = ncr->getElement("reverse_change", element_map);
  205. ncr->setReverseChange(element);
  206. element = ncr->getElement("fqdn", element_map);
  207. ncr->setFqdn(element);
  208. element = ncr->getElement("ip_address", element_map);
  209. ncr->setIpAddress(element);
  210. element = ncr->getElement("dhcid", element_map);
  211. ncr->setDhcid(element);
  212. element = ncr->getElement("lease_expires_on", element_map);
  213. ncr->setLeaseExpiresOn(element);
  214. element = ncr->getElement("lease_length", element_map);
  215. ncr->setLeaseLength(element);
  216. // All members were in the Element set and were correct lexically. Now
  217. // validate the overall content semantically. This will throw an
  218. // NcrMessageError if anything is amiss.
  219. ncr->validateContent();
  220. // Everything is valid, return the new instance.
  221. return (ncr);
  222. }
  223. std::string
  224. NameChangeRequest::toJSON() const {
  225. // Create a JSON string of this request's contents. Note that this method
  226. // does NOT use the isc::data library as generating the output is straight
  227. // forward.
  228. std::ostringstream stream;
  229. stream << "{\"change_type\":" << getChangeType() << ","
  230. << "\"forward_change\":"
  231. << (isForwardChange() ? "true" : "false") << ","
  232. << "\"reverse_change\":"
  233. << (isReverseChange() ? "true" : "false") << ","
  234. << "\"fqdn\":\"" << getFqdn() << "\","
  235. << "\"ip_address\":\"" << getIpAddress() << "\","
  236. << "\"dhcid\":\"" << getDhcid().toStr() << "\","
  237. << "\"lease_expires_on\":\"" << getLeaseExpiresOnStr() << "\","
  238. << "\"lease_length\":" << getLeaseLength() << "}";
  239. return (stream.str());
  240. }
  241. void
  242. NameChangeRequest::validateContent() {
  243. //@todo This is an initial implementation which provides a minimal amount
  244. // of validation. FQDN, DHCID, and IP Address members are all currently
  245. // strings, these may be replaced with richer classes.
  246. if (fqdn_ == "") {
  247. isc_throw(NcrMessageError, "FQDN cannot be blank");
  248. }
  249. // Validate IP Address.
  250. try {
  251. isc::asiolink::IOAddress io_addr(ip_address_);
  252. } catch (const isc::asiolink::IOError& ex) {
  253. isc_throw(NcrMessageError,
  254. "Invalid ip address string for ip_address: " << ip_address_);
  255. }
  256. // Validate the DHCID.
  257. if (dhcid_.getBytes().size() == 0) {
  258. isc_throw(NcrMessageError, "DHCID cannot be blank");
  259. }
  260. // Ensure the request specifies at least one direction to update.
  261. if (!forward_change_ && !reverse_change_) {
  262. isc_throw(NcrMessageError,
  263. "Invalid Request, forward and reverse flags are both false");
  264. }
  265. }
  266. isc::data::ConstElementPtr
  267. NameChangeRequest::getElement(const std::string& name,
  268. const ElementMap& element_map) const {
  269. // Look for "name" in the element map.
  270. ElementMap::const_iterator it = element_map.find(name);
  271. if (it == element_map.end()) {
  272. // Didn't find the element, so throw.
  273. isc_throw(NcrMessageError,
  274. "NameChangeRequest value missing for: " << name );
  275. }
  276. // Found the element, return it.
  277. return (it->second);
  278. }
  279. void
  280. NameChangeRequest::setChangeType(const NameChangeType value) {
  281. change_type_ = value;
  282. }
  283. void
  284. NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
  285. long raw_value = -1;
  286. try {
  287. // Get the element's integer value.
  288. raw_value = element->intValue();
  289. } catch (isc::data::TypeError& ex) {
  290. // We expect a integer Element type, don't have one.
  291. isc_throw(NcrMessageError,
  292. "Wrong data type for change_type: " << ex.what());
  293. }
  294. if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
  295. // Value is not a valid change type.
  296. isc_throw(NcrMessageError,
  297. "Invalid data value for change_type: " << raw_value);
  298. }
  299. // Good to go, make the assignment.
  300. setChangeType(static_cast<NameChangeType>(raw_value));
  301. }
  302. void
  303. NameChangeRequest::setForwardChange(const bool value) {
  304. forward_change_ = value;
  305. }
  306. void
  307. NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
  308. bool value;
  309. try {
  310. // Get the element's boolean value.
  311. value = element->boolValue();
  312. } catch (isc::data::TypeError& ex) {
  313. // We expect a boolean Element type, don't have one.
  314. isc_throw(NcrMessageError,
  315. "Wrong data type for forward_change :" << ex.what());
  316. }
  317. // Good to go, make the assignment.
  318. setForwardChange(value);
  319. }
  320. void
  321. NameChangeRequest::setReverseChange(const bool value) {
  322. reverse_change_ = value;
  323. }
  324. void
  325. NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
  326. bool value;
  327. try {
  328. // Get the element's boolean value.
  329. value = element->boolValue();
  330. } catch (isc::data::TypeError& ex) {
  331. // We expect a boolean Element type, don't have one.
  332. isc_throw(NcrMessageError,
  333. "Wrong data type for reverse_change :" << ex.what());
  334. }
  335. // Good to go, make the assignment.
  336. setReverseChange(value);
  337. }
  338. void
  339. NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
  340. setFqdn(element->stringValue());
  341. }
  342. void
  343. NameChangeRequest::setFqdn(const std::string& value) {
  344. fqdn_ = value;
  345. }
  346. void
  347. NameChangeRequest::setIpAddress(const std::string& value) {
  348. ip_address_ = value;
  349. }
  350. void
  351. NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
  352. setIpAddress(element->stringValue());
  353. }
  354. void
  355. NameChangeRequest::setDhcid(const std::string& value) {
  356. dhcid_.fromStr(value);
  357. }
  358. void
  359. NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
  360. setDhcid(element->stringValue());
  361. }
  362. std::string
  363. NameChangeRequest::getLeaseExpiresOnStr() const {
  364. return (isc::util::timeToText64(lease_expires_on_));
  365. }
  366. void
  367. NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
  368. try {
  369. lease_expires_on_ = isc::util::timeFromText64(value);
  370. } catch(...) {
  371. // We were given an invalid string, so throw.
  372. isc_throw(NcrMessageError,
  373. "Invalid date-time string: [" << value << "]");
  374. }
  375. }
  376. void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
  377. // Pull out the string value and pass it into the string setter.
  378. setLeaseExpiresOn(element->stringValue());
  379. }
  380. void
  381. NameChangeRequest::setLeaseLength(const uint32_t value) {
  382. lease_length_ = value;
  383. }
  384. void
  385. NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
  386. long value = -1;
  387. try {
  388. // Get the element's integer value.
  389. value = element->intValue();
  390. } catch (isc::data::TypeError& ex) {
  391. // We expect a integer Element type, don't have one.
  392. isc_throw(NcrMessageError,
  393. "Wrong data type for lease_length: " << ex.what());
  394. }
  395. // Make sure we the range is correct and value is positive.
  396. if (value > std::numeric_limits<uint32_t>::max()) {
  397. isc_throw(NcrMessageError, "lease_length value " << value <<
  398. "is too large for unsigned 32-bit integer.");
  399. }
  400. if (value < 0) {
  401. isc_throw(NcrMessageError, "lease_length value " << value <<
  402. "is negative. It must greater than or equal to zero ");
  403. }
  404. // Good to go, make the assignment.
  405. setLeaseLength(static_cast<uint32_t>(value));
  406. }
  407. void
  408. NameChangeRequest::setStatus(const NameChangeStatus value) {
  409. status_ = value;
  410. }
  411. std::string
  412. NameChangeRequest::toText() const {
  413. std::ostringstream stream;
  414. stream << "Type: " << static_cast<int>(change_type_) << " (";
  415. switch (change_type_) {
  416. case CHG_ADD:
  417. stream << "CHG_ADD)\n";
  418. break;
  419. case CHG_REMOVE:
  420. stream << "CHG_REMOVE)\n";
  421. break;
  422. default:
  423. // Shouldn't be possible.
  424. stream << "Invalid Value\n";
  425. }
  426. stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
  427. << std::endl
  428. << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
  429. << std::endl
  430. << "FQDN: [" << fqdn_ << "]" << std::endl
  431. << "IP Address: [" << ip_address_ << "]" << std::endl
  432. << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
  433. << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
  434. << "Lease Length: " << lease_length_ << std::endl;
  435. return (stream.str());
  436. }
  437. bool
  438. NameChangeRequest::operator == (const NameChangeRequest& other) {
  439. return ((change_type_ == other.change_type_) &&
  440. (forward_change_ == other.forward_change_) &&
  441. (reverse_change_ == other.reverse_change_) &&
  442. (fqdn_ == other.fqdn_) &&
  443. (ip_address_ == other.ip_address_) &&
  444. (dhcid_ == other.dhcid_) &&
  445. (lease_expires_on_ == other.lease_expires_on_) &&
  446. (lease_length_ == other.lease_length_));
  447. }
  448. bool
  449. NameChangeRequest::operator != (const NameChangeRequest& other) {
  450. return (!(*this == other));
  451. }
  452. }; // end of isc::dhcp namespace
  453. }; // end of isc namespace