ncr_msg.cc 17 KB

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