1 // Copyright (C) 2011-2021 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 
7 #include <config.h>
8 #include <dhcp/dhcp4.h>
9 #include <dhcp/libdhcp++.h>
10 #include <dhcp/option.h>
11 #include <dhcp/option_space.h>
12 #include <exceptions/exceptions.h>
13 #include <util/encode/hex.h>
14 #include <util/io_utilities.h>
15 
16 #include <boost/make_shared.hpp>
17 
18 #include <iomanip>
19 #include <list>
20 #include <sstream>
21 
22 #include <arpa/inet.h>
23 #include <stdint.h>
24 #include <string.h>
25 
26 using namespace std;
27 using namespace isc::util;
28 
29 namespace isc {
30 namespace dhcp {
31 
32 OptionPtr
factory(Option::Universe u,uint16_t type,const OptionBuffer & buf)33 Option::factory(Option::Universe u,
34         uint16_t type,
35         const OptionBuffer& buf) {
36     return(LibDHCP::optionFactory(u, type, buf));
37 }
38 
39 
Option(Universe u,uint16_t type)40 Option::Option(Universe u, uint16_t type)
41     :universe_(u), type_(type) {
42     check();
43 }
44 
Option(Universe u,uint16_t type,const OptionBuffer & data)45 Option::Option(Universe u, uint16_t type, const OptionBuffer& data)
46     :universe_(u), type_(type), data_(data) {
47     check();
48 }
49 
Option(Universe u,uint16_t type,OptionBufferConstIter first,OptionBufferConstIter last)50 Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
51                OptionBufferConstIter last)
52     :universe_(u), type_(type), data_(first, last) {
53     check();
54 }
55 
Option(const Option & option)56 Option::Option(const Option& option)
57     : universe_(option.universe_), type_(option.type_),
58       data_(option.data_), options_(),
59       encapsulated_space_(option.encapsulated_space_) {
60     option.getOptionsCopy(options_);
61 }
62 
63 OptionPtr
create(Universe u,uint16_t type)64 Option::create(Universe u, uint16_t type) {
65     return (boost::make_shared<Option>(u, type));
66 }
67 
68 OptionPtr
create(Universe u,uint16_t type,const OptionBuffer & data)69 Option::create(Universe u, uint16_t type, const OptionBuffer& data) {
70     return (boost::make_shared<Option>(u, type, data));
71 }
72 
73 Option&
operator =(const Option & rhs)74 Option::operator=(const Option& rhs) {
75     if (&rhs != this) {
76         universe_ = rhs.universe_;
77         type_ = rhs.type_;
78         data_ = rhs.data_;
79         rhs.getOptionsCopy(options_);
80         encapsulated_space_ = rhs.encapsulated_space_;
81     }
82     return (*this);
83 }
84 
85 OptionPtr
clone() const86 Option::clone() const {
87     return (cloneInternal<Option>());
88 }
89 
90 void
check() const91 Option::check() const {
92     if ( (universe_ != V4) && (universe_ != V6) ) {
93         isc_throw(BadValue, "Invalid universe type specified. "
94                   << "Only V4 and V6 are allowed.");
95     }
96 
97     if (universe_ == V4) {
98 
99         if (type_ > 255) {
100             isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. "
101                       << "For DHCPv4 allowed type range is 0..255");
102         } else if (data_.size() > 255) {
103             isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big.");
104             /// TODO Larger options can be stored as separate instances
105             /// of DHCPv4 options. Clients MUST concatenate them.
106             /// Fortunately, there are no such large options used today.
107         }
108     }
109 
110     // no need to check anything for DHCPv6. It allows full range (0-64k) of
111     // both types and data size.
112 }
113 
pack(isc::util::OutputBuffer & buf) const114 void Option::pack(isc::util::OutputBuffer& buf) const {
115     // Write a header.
116     packHeader(buf);
117     // Write data.
118     if (!data_.empty()) {
119         buf.writeData(&data_[0], data_.size());
120     }
121     // Write sub-options.
122     packOptions(buf);
123 }
124 
125 void
packHeader(isc::util::OutputBuffer & buf) const126 Option::packHeader(isc::util::OutputBuffer& buf) const {
127     if (universe_ == V4) {
128         if (len() > 255) {
129             isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. "
130                       << "At most 255 bytes are supported.");
131             /// TODO Larger options can be stored as separate instances
132             /// of DHCPv4 options. Clients MUST concatenate them.
133             /// Fortunately, there are no such large options used today.
134         }
135 
136         buf.writeUint8(type_);
137         buf.writeUint8(len() - getHeaderLen());
138 
139     } else {
140         buf.writeUint16(type_);
141         buf.writeUint16(len() - getHeaderLen());
142     }
143 }
144 
145 void
packOptions(isc::util::OutputBuffer & buf) const146 Option::packOptions(isc::util::OutputBuffer& buf) const {
147     switch (universe_) {
148     case V4:
149         LibDHCP::packOptions4(buf, options_);
150         return;
151     case V6:
152         LibDHCP::packOptions6(buf, options_);
153         return;
154     default:
155         isc_throw(isc::BadValue, "Invalid universe type " << universe_);
156     }
157 }
158 
unpack(OptionBufferConstIter begin,OptionBufferConstIter end)159 void Option::unpack(OptionBufferConstIter begin,
160                     OptionBufferConstIter end) {
161     setData(begin, end);
162 }
163 
164 void
unpackOptions(const OptionBuffer & buf)165 Option::unpackOptions(const OptionBuffer& buf) {
166     list<uint16_t> deferred;
167     switch (universe_) {
168     case V4:
169         LibDHCP::unpackOptions4(buf, getEncapsulatedSpace(),
170                                 options_, deferred,
171                                 getType() == DHO_VENDOR_ENCAPSULATED_OPTIONS);
172         return;
173     case V6:
174         LibDHCP::unpackOptions6(buf, getEncapsulatedSpace(), options_);
175         return;
176     default:
177         isc_throw(isc::BadValue, "Invalid universe type " << universe_);
178     }
179 }
180 
len() const181 uint16_t Option::len() const {
182     // Returns length of the complete option (data length + DHCPv4/DHCPv6
183     // option header)
184 
185     // length of the whole option is header and data stored in this option...
186     size_t length = getHeaderLen() + data_.size();
187 
188     // ... and sum of lengths of all suboptions
189     for (OptionCollection::const_iterator it = options_.begin();
190          it != options_.end();
191          ++it) {
192         length += (*it).second->len();
193     }
194 
195     // note that this is not equal to length field. This value denotes
196     // number of bytes required to store this option. length option should
197     // contain (len()-getHeaderLen()) value.
198     return (static_cast<uint16_t>(length));
199 }
200 
201 bool
valid() const202 Option::valid() const {
203     if (universe_ != V4 &&
204         universe_ != V6) {
205         return (false);
206     }
207 
208     return (true);
209 }
210 
getOption(uint16_t opt_type) const211 OptionPtr Option::getOption(uint16_t opt_type) const {
212     isc::dhcp::OptionCollection::const_iterator x =
213         options_.find(opt_type);
214     if ( x != options_.end() ) {
215         return (*x).second;
216     }
217     return OptionPtr(); // NULL
218 }
219 
220 void
getOptionsCopy(OptionCollection & options_copy) const221 Option::getOptionsCopy(OptionCollection& options_copy) const {
222     OptionCollection local_options;
223     for (OptionCollection::const_iterator it = options_.begin();
224          it != options_.end(); ++it) {
225         OptionPtr copy = it->second->clone();
226         local_options.insert(std::make_pair(it->second->getType(),
227                                             copy));
228     }
229     // All options copied successfully, so assign them to the output
230     // parameter.
231     options_copy.swap(local_options);
232 }
233 
delOption(uint16_t opt_type)234 bool Option::delOption(uint16_t opt_type) {
235     isc::dhcp::OptionCollection::iterator x = options_.find(opt_type);
236     if ( x != options_.end() ) {
237         options_.erase(x);
238         return true; // delete successful
239     }
240     return (false); // option not found, can't delete
241 }
242 
243 
toText(int indent) const244 std::string Option::toText(int indent) const {
245     std::stringstream output;
246     output << headerToText(indent) << ": ";
247 
248     for (unsigned int i = 0; i < data_.size(); i++) {
249         if (i) {
250             output << ":";
251         }
252         output << setfill('0') << setw(2) << hex
253             << static_cast<unsigned short>(data_[i]);
254     }
255 
256     // Append suboptions.
257     output << suboptionsToText(indent + 2);
258 
259     return (output.str());
260 }
261 
262 std::string
toString() const263 Option::toString() const {
264     /// @todo: Implement actual conversion in derived classes.
265     return (toText(0));
266 }
267 
268 std::vector<uint8_t>
toBinary(const bool include_header) const269 Option::toBinary(const bool include_header) const {
270     OutputBuffer buf(len());
271     try {
272         // If the option is too long, exception will be thrown. We allow
273         // for this exception to propagate to not mask this error.
274         pack(buf);
275 
276     } catch (const std::exception &ex) {
277         isc_throw(OutOfRange, "unable to obtain hexadecimal representation"
278                   " of option " << getType() << ": " << ex.what());
279     }
280     const uint8_t* option_data = static_cast<const uint8_t*>(buf.getData());
281 
282     // Assign option data to a vector, with or without option header depending
283     // on the value of "include_header" flag.
284     std::vector<uint8_t> option_vec(option_data + (include_header ? 0 : getHeaderLen()),
285                                     option_data + buf.getLength());
286     return (option_vec);
287 }
288 
289 std::string
toHexString(const bool include_header) const290 Option::toHexString(const bool include_header) const {
291     // Prepare binary version of the option.
292     std::vector<uint8_t> option_vec = toBinary(include_header);
293 
294     // Return hexadecimal representation prepended with 0x or empty string
295     // if option has no payload and the header fields are excluded.
296     std::ostringstream s;
297     if (!option_vec.empty()) {
298         s << "0x" << encode::encodeHex(option_vec);
299     }
300     return (s.str());
301 }
302 
303 std::string
headerToText(const int indent,const std::string & type_name) const304 Option::headerToText(const int indent, const std::string& type_name) const {
305     std::stringstream output;
306     for (int i = 0; i < indent; i++)
307         output << " ";
308 
309     int field_len = (getUniverse() == V4 ? 3 : 5);
310     output << "type=" << std::setw(field_len) << std::setfill('0')
311            << type_;
312 
313     if (!type_name.empty()) {
314         output << "(" << type_name << ")";
315     }
316 
317     output << ", len=" << std::setw(field_len) << std::setfill('0')
318            << len()-getHeaderLen();
319     return (output.str());
320 }
321 
322 std::string
suboptionsToText(const int indent) const323 Option::suboptionsToText(const int indent) const {
324     std::stringstream output;
325 
326     if (!options_.empty()) {
327         output << "," << std::endl << "options:";
328         for (OptionCollection::const_iterator opt = options_.begin();
329              opt != options_.end(); ++opt) {
330             output << std::endl << (*opt).second->toText(indent);
331         }
332     }
333 
334     return (output.str());
335 }
336 
337 uint16_t
getHeaderLen() const338 Option::getHeaderLen() const {
339     switch (universe_) {
340     case V4:
341         return OPTION4_HDR_LEN; // header length for v4
342     case V6:
343         return OPTION6_HDR_LEN; // header length for v6
344     }
345     return 0; // should not happen
346 }
347 
addOption(OptionPtr opt)348 void Option::addOption(OptionPtr opt) {
349     if (universe_ == V4) {
350         // check for uniqueness (DHCPv4 options must be unique)
351         if (getOption(opt->getType())) {
352             isc_throw(BadValue, "Option " << opt->getType()
353                       << " already present in this message.");
354         }
355     }
356     options_.insert(make_pair(opt->getType(), opt));
357 }
358 
getUint8() const359 uint8_t Option::getUint8() const {
360     if (data_.size() < sizeof(uint8_t) ) {
361         isc_throw(OutOfRange, "Attempt to read uint8 from option " << type_
362                   << " that has size " << data_.size());
363     }
364     return (data_[0]);
365 }
366 
getUint16() const367 uint16_t Option::getUint16() const {
368     // readUint16() checks and throws OutOfRange if data_ is too small.
369     return (readUint16(&data_[0], data_.size()));
370 }
371 
getUint32() const372 uint32_t Option::getUint32() const {
373     // readUint32() checks and throws OutOfRange if data_ is too small.
374     return (readUint32(&data_[0], data_.size()));
375 }
376 
setUint8(uint8_t value)377 void Option::setUint8(uint8_t value) {
378     data_.resize(sizeof(value));
379     data_[0] = value;
380 }
381 
setUint16(uint16_t value)382 void Option::setUint16(uint16_t value) {
383     data_.resize(sizeof(value));
384     writeUint16(value, &data_[0], data_.size());
385 }
386 
setUint32(uint32_t value)387 void Option::setUint32(uint32_t value) {
388     data_.resize(sizeof(value));
389     writeUint32(value, &data_[0], data_.size());
390 }
391 
equals(const OptionPtr & other) const392 bool Option::equals(const OptionPtr& other) const {
393     return (equals(*other));
394 }
395 
equals(const Option & other) const396 bool Option::equals(const Option& other) const {
397     return ( (getType() == other.getType()) &&
398              (getData() == other.getData()) );
399 }
400 
~Option()401 Option::~Option() {
402 
403 }
404 
405 bool Option::lenient_parsing_;
406 
407 } // end of isc::dhcp namespace
408 } // end of isc namespace
409