// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace isc::dhcp; using namespace std; void TokenString::evaluate(Pkt& /*pkt*/, ValueStack& values) { // Literals only push, nothing to pop values.push(value_); // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_STRING) .arg('\'' + value_ + '\''); } TokenHexString::TokenHexString(const string& str) : value_("") { // Check string starts "0x" or "0x" and has at least one additional character. if ((str.size() < 3) || (str[0] != '0') || ((str[1] != 'x') && (str[1] != 'X'))) { return; } string digits = str.substr(2); // Transform string of hexadecimal digits into binary format vector binary; try { // The decodeHex function expects that the string contains an // even number of digits. If we don't meet this requirement, // we have to insert a leading 0. if ((digits.length() % 2) != 0) { digits = digits.insert(0, "0"); } util::encode::decodeHex(digits, binary); } catch (...) { return; } // Convert to a string (note that binary.size() cannot be 0) value_.resize(binary.size()); memmove(&value_[0], &binary[0], binary.size()); } void TokenHexString::evaluate(Pkt& /*pkt*/, ValueStack& values) { // Literals only push, nothing to pop values.push(value_); // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_HEXSTRING) .arg("0x" + util::encode::encodeHex(std::vector(value_.begin(), value_.end()))); } TokenIpAddress::TokenIpAddress(const string& addr) : value_("") { // Transform IP address into binary format vector binary; try { asiolink::IOAddress ip(addr); binary = ip.toBytes(); } catch (...) { return; } // Convert to a string (note that binary.size() is 4 or 16, so not 0) value_.resize(binary.size()); memmove(&value_[0], &binary[0], binary.size()); } void TokenIpAddress::evaluate(Pkt& /*pkt*/, ValueStack& values) { // Literals only push, nothing to pop values.push(value_); // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IPADDRESS) .arg("0x" + util::encode::encodeHex(std::vector(value_.begin(), value_.end()))); } OptionPtr TokenOption::getOption(Pkt& pkt) { return (pkt.getOption(option_code_)); } void TokenOption::evaluate(Pkt& pkt, ValueStack& values) { OptionPtr opt = getOption(pkt); std::string opt_str; if (opt) { if (representation_type_ == TEXTUAL) { opt_str = opt->toString(); } else if (representation_type_ == HEXADECIMAL) { std::vector binary = opt->toBinary(); opt_str.resize(binary.size()); if (!binary.empty()) { memmove(&opt_str[0], &binary[0], binary.size()); } } else { opt_str = "true"; } } else if (representation_type_ == EXISTS) { opt_str = "false"; } // Push value of the option or empty string if there was no such option // in the packet. values.push(opt_str); // Log what we pushed, both exists and textual are simple text // and can be output directly. We also include the code number // of the requested option. if (representation_type_ == HEXADECIMAL) { LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OPTION) .arg(option_code_) .arg("0x" + util::encode::encodeHex(std::vector(opt_str.begin(), opt_str.end()))); } else { LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OPTION) .arg(option_code_) .arg('\'' + opt_str + '\''); } } std::string TokenOption::pushFailure(ValueStack& values) { std::string txt; if (representation_type_ == EXISTS) { txt = "false"; } values.push(txt); return (txt); } TokenRelay4Option::TokenRelay4Option(const uint16_t option_code, const RepresentationType& rep_type) :TokenOption(option_code, rep_type) { } OptionPtr TokenRelay4Option::getOption(Pkt& pkt) { // Check if there is Relay Agent Option. OptionPtr rai = pkt.getOption(DHO_DHCP_AGENT_OPTIONS); if (!rai) { return (OptionPtr()); } // If there is, try to return its suboption return (rai->getOption(option_code_)); } void TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) { vector binary; string type_str; try { // Check if it's a Pkt4. If it's not, the dynamic_cast will throw // std::bad_cast (failed dynamic_cast returns NULL for pointers and // throws for references). const Pkt4& pkt4 = dynamic_cast(pkt); switch (type_) { case CHADDR: { HWAddrPtr hwaddr = pkt4.getHWAddr(); if (!hwaddr) { // This should never happen. Every Pkt4 should always have // a hardware address. isc_throw(EvalTypeError, "Packet does not have hardware address"); } binary = hwaddr->hwaddr_; type_str = "mac"; break; } case GIADDR: binary = pkt4.getGiaddr().toBytes(); type_str = "giaddr"; break; case CIADDR: binary = pkt4.getCiaddr().toBytes(); type_str = "ciaddr"; break; case YIADDR: binary = pkt4.getYiaddr().toBytes(); type_str = "yiaddr"; break; case SIADDR: binary = pkt4.getSiaddr().toBytes(); type_str = "siaddr"; break; case HLEN: // Pad the uint8_t field to 4 bytes. binary.push_back(0); binary.push_back(0); binary.push_back(0); binary.push_back(pkt4.getHlen()); type_str = "hlen"; break; case HTYPE: // Pad the uint8_t field to 4 bytes. binary.push_back(0); binary.push_back(0); binary.push_back(0); binary.push_back(pkt4.getHtype()); type_str = "htype"; break; default: isc_throw(EvalTypeError, "Bad field specified: " << static_cast(type_) ); } } catch (const std::bad_cast&) { isc_throw(EvalTypeError, "Specified packet is not a Pkt4"); } string value; value.resize(binary.size()); if (!binary.empty()) { memmove(&value[0], &binary[0], binary.size()); } values.push(value); // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT4) .arg(type_str) .arg("0x" + util::encode::encodeHex(std::vector(value.begin(), value.end()))); } void TokenEqual::evaluate(Pkt& /*pkt*/, ValueStack& values) { if (values.size() < 2) { isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " "2 values for == operator, got " << values.size()); } string op1 = values.top(); values.pop(); string op2 = values.top(); values.pop(); // Dammit, std::stack interface is awkward. if (op1 == op2) values.push("true"); else values.push("false"); // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_EQUAL) .arg("0x" + util::encode::encodeHex(std::vector(op1.begin(), op1.end()))) .arg("0x" + util::encode::encodeHex(std::vector(op2.begin(), op2.end()))) .arg('\'' + values.top() + '\''); } void TokenSubstring::evaluate(Pkt& /*pkt*/, ValueStack& values) { if (values.size() < 3) { isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " "3 values for substring operator, got " << values.size()); } string len_str = values.top(); values.pop(); string start_str = values.top(); values.pop(); string string_str = values.top(); values.pop(); // If we have no string to start with we push an empty string and leave if (string_str.empty()) { values.push(""); // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING_EMPTY) .arg(len_str) .arg(start_str) .arg("0x") .arg("0x"); return; } // Convert the starting position and length from strings to numbers // the length may also be "all" in which case simply make it the // length of the string. // If we have a problem push an empty string and leave int start_pos; int length; try { start_pos = boost::lexical_cast(start_str); } catch (const boost::bad_lexical_cast&) { isc_throw(EvalTypeError, "the parameter '" << start_str << "' for the starting postion of the substring " << "couldn't be converted to an integer."); } try { if (len_str == "all") { length = string_str.length(); } else { length = boost::lexical_cast(len_str); } } catch (const boost::bad_lexical_cast&) { isc_throw(EvalTypeError, "the parameter '" << len_str << "' for the length of the substring " << "couldn't be converted to an integer."); } const int string_length = string_str.length(); // If the starting postion is outside of the string push an // empty string and leave if ((start_pos < -string_length) || (start_pos >= string_length)) { values.push(""); // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING_RANGE) .arg(len_str) .arg(start_str) .arg("0x" + util::encode::encodeHex(std::vector(string_str.begin(), string_str.end()))) .arg("0x"); return; } // Adjust the values to be something for substr. We first figure out // the starting postion, then update it and the length to get the // characters before or after it depending on the sign of length if (start_pos < 0) { start_pos = string_length + start_pos; } if (length < 0) { length = -length; if (length <= start_pos){ start_pos -= length; } else { length = start_pos; start_pos = 0; } } // and finally get the substring values.push(string_str.substr(start_pos, length)); // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING) .arg(len_str) .arg(start_str) .arg("0x" + util::encode::encodeHex(std::vector(string_str.begin(), string_str.end()))) .arg("0x" + util::encode::encodeHex(std::vector(values.top().begin(), values.top().end()))); } void TokenConcat::evaluate(Pkt& /*pkt*/, ValueStack& values) { if (values.size() < 2) { isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " "2 values for concat, got " << values.size()); } string op1 = values.top(); values.pop(); string op2 = values.top(); values.pop(); // Dammit, std::stack interface is awkward. // The top of the stack was evaluated last so this is the right order values.push(op2 + op1); // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_CONCAT) .arg("0x" + util::encode::encodeHex(std::vector(op1.begin(), op1.end()))) .arg("0x" + util::encode::encodeHex(std::vector(op2.begin(), op2.end()))) .arg("0x" + util::encode::encodeHex(std::vector(values.top().begin(), values.top().end()))); } void TokenNot::evaluate(Pkt& /*pkt*/, ValueStack& values) { if (values.size() == 0) { isc_throw(EvalBadStack, "Incorrect empty stack."); } string op = values.top(); values.pop(); bool val = toBool(op); if (!val) { values.push("true"); } else { values.push("false"); } // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_NOT) .arg('\'' + op + '\'') .arg('\'' + values.top() + '\''); } void TokenAnd::evaluate(Pkt& /*pkt*/, ValueStack& values) { if (values.size() < 2) { isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " "2 values for and operator, got " << values.size()); } string op1 = values.top(); values.pop(); bool val1 = toBool(op1); string op2 = values.top(); values.pop(); // Dammit, std::stack interface is awkward. bool val2 = toBool(op2); if (val1 && val2) { values.push("true"); } else { values.push("false"); } // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_AND) .arg('\'' + op1 + '\'') .arg('\'' + op2 + '\'') .arg('\'' + values.top() + '\''); } void TokenOr::evaluate(Pkt& /*pkt*/, ValueStack& values) { if (values.size() < 2) { isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " "2 values for or operator, got " << values.size()); } string op1 = values.top(); values.pop(); bool val1 = toBool(op1); string op2 = values.top(); values.pop(); // Dammit, std::stack interface is awkward. bool val2 = toBool(op2); if (val1 || val2) { values.push("true"); } else { values.push("false"); } // Log what we popped and pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OR) .arg('\'' + op1 + '\'') .arg('\'' + op2 + '\'') .arg('\'' + values.top() + '\''); } OptionPtr TokenRelay6Option::getOption(Pkt& pkt) { try { // Check if it's a Pkt6. If it's not the dynamic_cast will // throw std::bad_cast. Pkt6& pkt6 = dynamic_cast(pkt); try { // Now that we have the right type of packet we can // get the option and return it. return(pkt6.getRelayOption(option_code_, nest_level_)); } catch (const isc::OutOfRange&) { // The only exception we expect is OutOfRange if the nest // level is out of range of the encapsulations, for example // if nest_level_ is 4 and there are only 2 encapsulations. // We return a NULL in that case. return (OptionPtr()); } } catch (const std::bad_cast&) { isc_throw(EvalTypeError, "Specified packet is not Pkt6"); } } void TokenRelay6Field::evaluate(Pkt& pkt, ValueStack& values) { vector binary; string type_str; try { // Check if it's a Pkt6. If it's not the dynamic_cast will // throw std::bad_cast. const Pkt6& pkt6 = dynamic_cast(pkt); try { switch (type_) { // Now that we have the right type of packet we can // get the option and return it. case LINKADDR: type_str = "linkaddr"; binary = pkt6.getRelay6LinkAddress(nest_level_).toBytes(); break; case PEERADDR: type_str = "peeraddr"; binary = pkt6.getRelay6PeerAddress(nest_level_).toBytes(); break; } } catch (const isc::OutOfRange&) { // The only exception we expect is OutOfRange if the nest // level is invalid. We push "" in that case. values.push(""); // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6_RANGE) .arg(type_str) .arg(unsigned(nest_level_)) .arg("0x"); return; } } catch (const std::bad_cast&) { isc_throw(EvalTypeError, "Specified packet is not Pkt6"); } string value; value.resize(binary.size()); if (!binary.empty()) { memmove(&value[0], &binary[0], binary.size()); } values.push(value); // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6) .arg(type_str) .arg(unsigned(nest_level_)) .arg("0x" + util::encode::encodeHex(std::vector(value.begin(), value.end()))); } void TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) { vector binary; string type_str; try { // Check if it's a Pkt6. If it's not the dynamic_cast will throw // std::bad_cast (failed dynamic_cast returns NULL for pointers and // throws for references). const Pkt6& pkt6 = dynamic_cast(pkt); switch (type_) { case MSGTYPE: { // msg type is an uint8_t integer. We want a 4 byte string so 0 pad. binary.push_back(0); binary.push_back(0); binary.push_back(0); binary.push_back(pkt6.getType()); type_str = "msgtype"; break; } case TRANSID: { // transaction id is an uint32_t integer. We want a 4 byte string so copy uint32_t transid = pkt6.getTransid(); binary.push_back(transid >> 24); binary.push_back((transid >> 16) & 0xFF); binary.push_back((transid >> 8) & 0xFF); binary.push_back(transid & 0xFF); type_str = "transid"; break; } default: isc_throw(EvalTypeError, "Bad field specified: " << static_cast(type_) ); } } catch (const std::bad_cast&) { isc_throw(EvalTypeError, "Specified packet is not Pkt6"); } string value; value.resize(binary.size()); memmove(&value[0], &binary[0], binary.size()); values.push(value); // Log what we pushed LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT6) .arg(type_str) .arg("0x" + util::encode::encodeHex(std::vector(value.begin(), value.end()))); } TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr, uint16_t option_code) :TokenOption(option_code, repr), universe_(u), vendor_id_(vendor_id), field_(option_code ? SUBOPTION : EXISTS) { } TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field) :TokenOption(0, TokenOption::HEXADECIMAL), universe_(u), vendor_id_(vendor_id), field_(field) { if (field_ == EXISTS) { representation_type_ = TokenOption::EXISTS; } } uint32_t TokenVendor::getVendorId() const { return (vendor_id_); } TokenVendor::FieldType TokenVendor::getField() const { return (field_); } void TokenVendor::evaluate(Pkt& pkt, ValueStack& values) { // Get the option first. uint16_t code = 0; switch (universe_) { case Option::V4: code = DHO_VIVSO_SUBOPTIONS; break; case Option::V6: code = D6O_VENDOR_OPTS; break; } OptionPtr opt = pkt.getOption(code); OptionVendorPtr vendor = boost::dynamic_pointer_cast(opt); if (!vendor) { // There's no vendor option, give up. std::string txt = pushFailure(values); LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_NO_OPTION) .arg(code) .arg(txt); return; } if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) { // There is vendor option, but it has other vendor-id value // than we're looking for. (0 means accept any vendor-id) std::string txt = pushFailure(values); LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH) .arg(vendor_id_) .arg(vendor->getVendorId()) .arg(txt); return; } switch (field_) { case ENTERPRISE_ID: { // Extract enterprise-id string txt(sizeof(uint32_t), 0); uint32_t value = htonl(vendor->getVendorId()); memcpy(&txt[0], &value, sizeof(uint32_t)); values.push(txt); LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_ENTERPRISE_ID) .arg(vendor_id_) .arg(util::encode::encodeHex(std::vector(txt.begin(), txt.end()))); return; } case SUBOPTION: /// This is vendor[X].option[Y].exists, let's try to /// extract the option TokenOption::evaluate(pkt, values); return; case EXISTS: // We already passed all the checks: the option is there and has specified // enterprise-id. LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_EXISTS) .arg("true"); values.push("true"); return; case DATA: // This is for vendor-class option, we can skip it here. isc_throw(EvalTypeError, "Field None is not valid for vendor-class"); return; } } OptionPtr TokenVendor::getOption(Pkt& pkt) { uint16_t code = 0; switch (universe_) { case Option::V4: code = DHO_VIVSO_SUBOPTIONS; break; case Option::V6: code = D6O_VENDOR_OPTS; break; } OptionPtr opt = pkt.getOption(code); if (!opt) { // If vendor option is not found, return NULL return (opt); } // If vendor option is found, try to return its // encapsulated option. return (opt->getOption(option_code_)); } TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id, RepresentationType repr) :TokenVendor(u, vendor_id, repr, 0), index_(0) { } TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id, FieldType field, uint16_t index) :TokenVendor(u, vendor_id, TokenOption::HEXADECIMAL, 0), index_(index) { field_ = field; } uint16_t TokenVendorClass::getDataIndex() const { return (index_); } void TokenVendorClass::evaluate(Pkt& pkt, ValueStack& values) { // Get the option first. uint16_t code = 0; switch (universe_) { case Option::V4: code = DHO_VIVCO_SUBOPTIONS; break; case Option::V6: code = D6O_VENDOR_CLASS; break; } OptionPtr opt = pkt.getOption(code); OptionVendorClassPtr vendor = boost::dynamic_pointer_cast(opt); if (!vendor) { // There's no vendor class option, give up. std::string txt = pushFailure(values); LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_NO_OPTION) .arg(code) .arg(txt); return; } if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) { // There is vendor option, but it has other vendor-id value // than we're looking for. (0 means accept any vendor-id) std::string txt = pushFailure(values); LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH) .arg(vendor_id_) .arg(vendor->getVendorId()) .arg(txt); return; } switch (field_) { case ENTERPRISE_ID: { // Extract enterprise-id string txt(sizeof(uint32_t), 0); uint32_t value = htonl(vendor->getVendorId()); memcpy(&txt[0], &value, sizeof(uint32_t)); values.push(txt); LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID) .arg(vendor_id_) .arg(util::encode::encodeHex(std::vector(txt.begin(), txt.end()))); return; } case SUBOPTION: // Extract sub-options isc_throw(EvalTypeError, "Field None is not valid for vendor-class"); return; case EXISTS: // We already passed all the checks: the option is there and has specified // enterprise-id. LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_EXISTS) .arg("true"); values.push("true"); return; case DATA: { size_t max = vendor->getTuplesNum(); if (index_ + 1 > max) { // The index specified if out of bound, e.g. there are only // 2 tuples and index specified is 5. values.push(""); return; } OpaqueDataTuple tuple = vendor->getTuple(index_); OpaqueDataTuple::Buffer buf = tuple.getData(); string txt(buf.begin(), buf.end()); LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_DATA) .arg(index_) .arg(max) .arg(util::encode::encodeHex(buf)); values.push(txt); return; } default: isc_throw(EvalTypeError, "Invalid field specified." << field_); } }