// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-
// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.
#ident "$XORP: xorp/rip/auth.cc,v 1.36 2007/02/16 22:47:13 pavlin Exp $"
#include "rip_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/eventloop.hh"
#include <functional>
#include <openssl/md5.h>
#include "constants.hh"
#include "auth.hh"
// ----------------------------------------------------------------------------
// AuthHandlerBase implementation
AuthHandlerBase::~AuthHandlerBase()
{
}
const string&
AuthHandlerBase::error() const
{
return _err;
}
inline void
AuthHandlerBase::reset_error()
{
if (_err.empty() == false)
_err.erase();
}
inline void
AuthHandlerBase::set_error(const string& err)
{
_err = err;
}
// ----------------------------------------------------------------------------
// NullAuthHandler implementation
const char*
NullAuthHandler::effective_name() const
{
return auth_type_name();
}
const char*
NullAuthHandler::auth_type_name()
{
return "none";
}
void
NullAuthHandler::reset()
{
}
uint32_t
NullAuthHandler::head_entries() const
{
return 0;
}
uint32_t
NullAuthHandler::max_routing_entries() const
{
return RIPv2_ROUTES_PER_PACKET;
}
bool
NullAuthHandler::authenticate_inbound(const uint8_t* packet,
size_t packet_bytes,
const uint8_t*& entries_ptr,
uint32_t& n_entries,
const IPv4&,
bool)
{
entries_ptr = NULL;
n_entries = 0;
if (packet_bytes > RIPv2_MAX_PACKET_BYTES) {
set_error(c_format("packet too large (%u bytes)",
XORP_UINT_CAST(packet_bytes)));
return false;
} else if (packet_bytes < RIPv2_MIN_PACKET_BYTES) {
set_error(c_format("packet too small (%u bytes)",
XORP_UINT_CAST(packet_bytes)));
return false;
}
size_t entry_bytes = packet_bytes - RipPacketHeader::size();
if (entry_bytes % PacketRouteEntry<IPv4>::size()) {
set_error(c_format("non-integral route entries (%u bytes)",
XORP_UINT_CAST(entry_bytes)));
return false;
}
n_entries = entry_bytes / PacketRouteEntry<IPv4>::size();
if (n_entries == 0) {
return true;
}
entries_ptr = packet + RipPacketHeader::size();
const PacketRouteEntry<IPv4> entry(entries_ptr);
// Reject packet if first entry is authentication data
if (entry.is_auth_entry()) {
set_error(c_format("unexpected authentication data (type %d)",
entry.tag()));
entries_ptr = NULL;
n_entries = 0;
return false;
}
reset_error();
return true;
}
bool
NullAuthHandler::authenticate_outbound(RipPacket<IPv4>& packet,
list<RipPacket<IPv4> *>& auth_packets,
size_t& n_routes)
{
// XXX: nothing to do so just create a single copy
RipPacket<IPv4>* copy_packet = new RipPacket<IPv4>(packet);
auth_packets.push_back(copy_packet);
reset_error();
n_routes = packet.data_bytes() - RipPacketHeader::size();
n_routes /= PacketRouteEntry<IPv4>::size();
return (true);
}
// ----------------------------------------------------------------------------
// Plaintext handler implementation
const char*
PlaintextAuthHandler::effective_name() const
{
return auth_type_name();
}
const char*
PlaintextAuthHandler::auth_type_name()
{
return "simple";
}
void
PlaintextAuthHandler::reset()
{
}
uint32_t
PlaintextAuthHandler::head_entries() const
{
return 1;
}
uint32_t
PlaintextAuthHandler::max_routing_entries() const
{
return RIPv2_ROUTES_PER_PACKET - 1;
}
bool
PlaintextAuthHandler::authenticate_inbound(const uint8_t* packet,
size_t packet_bytes,
const uint8_t*& entries_ptr,
uint32_t& n_entries,
const IPv4&,
bool)
{
entries_ptr = NULL;
n_entries = 0;
if (packet_bytes > RIPv2_MAX_PACKET_BYTES) {
set_error(c_format("packet too large (%u bytes)",
XORP_UINT_CAST(packet_bytes)));
return false;
}
if (packet_bytes < RIPv2_MIN_AUTH_PACKET_BYTES) {
set_error(c_format("packet too small (%u bytes)",
XORP_UINT_CAST(packet_bytes)));
return false;
}
size_t entry_bytes = packet_bytes - RipPacketHeader::size();
if (entry_bytes % PacketRouteEntry<IPv4>::size()) {
set_error(c_format("non-integral route entries (%u bytes)",
XORP_UINT_CAST(entry_bytes)));
return false;
}
const PlaintextPacketRouteEntry4 ppr(packet + RipPacketHeader::size());
if (ppr.addr_family() != PlaintextPacketRouteEntry4::ADDR_FAMILY) {
set_error("not an authenticated packet");
return false;
} else if (ppr.auth_type() != PlaintextPacketRouteEntry4::AUTH_TYPE) {
set_error("not a plaintext authenticated packet");
return false;
}
string passwd = ppr.password();
if (passwd != key()) {
set_error(c_format("wrong password \"%s\"", passwd.c_str()));
return false;
}
reset_error();
n_entries = entry_bytes / PacketRouteEntry<IPv4>::size() - 1;
if (n_entries) {
entries_ptr = (packet + RipPacketHeader::size()
+ PlaintextPacketRouteEntry4::size());
}
return true;
}
bool
PlaintextAuthHandler::authenticate_outbound(RipPacket<IPv4>& packet,
list<RipPacket<IPv4> *>& auth_packets,
size_t& n_routes)
{
uint8_t* first_entry_ptr = NULL;
if (head_entries() > 0)
first_entry_ptr = packet.route_entry_ptr(0);
static_assert(PacketRouteEntry<IPv4>::SIZE == 20);
static_assert(PlaintextPacketRouteEntry4::SIZE == 20);
XLOG_ASSERT(packet.data_ptr() + RipPacketHeader::size()
== first_entry_ptr);
PlaintextPacketRouteEntry4Writer ppr(first_entry_ptr);
ppr.initialize(key());
// XXX: just create a single copy
RipPacket<IPv4>* copy_packet = new RipPacket<IPv4>(packet);
auth_packets.push_back(copy_packet);
reset_error();
n_routes = packet.data_bytes() - RipPacketHeader::size();
n_routes /= PacketRouteEntry<IPv4>::size();
n_routes--; // XXX: exclude the first (authentication) entry
return (true);
}
const string&
PlaintextAuthHandler::key() const
{
return _key;
}
void
PlaintextAuthHandler::set_key(const string& plaintext_key)
{
_key = string(plaintext_key, 0, 16);
}
// ----------------------------------------------------------------------------
// MD5AuthHandler::MD5Key implementation
MD5AuthHandler::MD5Key::MD5Key(uint8_t key_id,
const string& key,
const TimeVal& start_timeval,
const TimeVal& end_timeval,
XorpTimer start_timer,
XorpTimer stop_timer)
: _id(key_id),
_start_timeval(start_timeval),
_end_timeval(end_timeval),
_is_persistent(false),
_o_seqno(0),
_start_timer(start_timer),
_stop_timer(stop_timer)
{
string::size_type n = key.copy(_key_data, 16);
if (n < KEY_BYTES) {
memset(_key_data + n, 0, KEY_BYTES - n);
}
}
string
MD5AuthHandler::MD5Key::key() const
{
return string(_key_data, 0, 16);
}
bool
MD5AuthHandler::MD5Key::valid_at(const TimeVal& when) const
{
if (is_persistent())
return true;
return ((_start_timeval <= when) && (when <= _end_timeval));
}
void
MD5AuthHandler::MD5Key::reset()
{
//
// Reset the seqno
//
_lr_seqno.clear();
//
// Reset the flag that a packet has been received
//
_pkts_recv.clear();
}
void
MD5AuthHandler::MD5Key::reset(const IPv4& src_addr)
{
map<IPv4, uint32_t>::iterator seqno_iter;
map<IPv4, bool>::iterator recv_iter;
//
// Reset the seqno
//
seqno_iter = _lr_seqno.find(src_addr);
if (seqno_iter != _lr_seqno.end())
_lr_seqno.erase(seqno_iter);
//
// Reset the flag that a packet has been received
//
recv_iter = _pkts_recv.find(src_addr);
if (recv_iter != _pkts_recv.end())
_pkts_recv.erase(recv_iter);
}
bool
MD5AuthHandler::MD5Key::packets_received(const IPv4& src_addr) const
{
map<IPv4, bool>::const_iterator iter;
iter = _pkts_recv.find(src_addr);
if (iter == _pkts_recv.end())
return (false);
return (iter->second);
}
uint32_t
MD5AuthHandler::MD5Key::last_seqno_recv(const IPv4& src_addr) const
{
map<IPv4, uint32_t>::const_iterator iter;
iter = _lr_seqno.find(src_addr);
if (iter == _lr_seqno.end())
return (0);
return (iter->second);
}
void
MD5AuthHandler::MD5Key::set_last_seqno_recv(const IPv4& src_addr,
uint32_t seqno)
{
map<IPv4, uint32_t>::iterator seqno_iter;
map<IPv4, bool>::iterator recv_iter;
//
// Set the seqno
//
seqno_iter = _lr_seqno.find(src_addr);
if (seqno_iter == _lr_seqno.end())
_lr_seqno.insert(make_pair(src_addr, seqno));
else
seqno_iter->second = seqno;
//
// Set the flag that a packet has been received
//
recv_iter = _pkts_recv.find(src_addr);
if (recv_iter == _pkts_recv.end())
_pkts_recv.insert(make_pair(src_addr, true));
else
recv_iter->second = true;
}
// ----------------------------------------------------------------------------
// MD5AuthHandler implementation
MD5AuthHandler::MD5AuthHandler(EventLoop& eventloop)
: _eventloop(eventloop)
{
}
const char*
MD5AuthHandler::effective_name() const
{
//
// XXX: if no valid keys, then don't use any authentication
//
if (_valid_key_chain.empty()) {
return (_null_handler.effective_name());
}
return auth_type_name();
}
const char*
MD5AuthHandler::auth_type_name()
{
return "md5";
}
void
MD5AuthHandler::reset()
{
//
// XXX: if no valid keys, then don't use any authentication
//
if (_valid_key_chain.empty()) {
_null_handler.reset();
return;
}
reset_keys();
}
uint32_t
MD5AuthHandler::head_entries() const
{
//
// XXX: if no valid keys, then don't use any authentication
//
if (_valid_key_chain.empty()) {
return (_null_handler.head_entries());
}
return 1;
}
uint32_t
MD5AuthHandler::max_routing_entries() const
{
//
// XXX: if no valid keys, then don't use any authentication
//
if (_valid_key_chain.empty()) {
return (_null_handler.max_routing_entries());
}
return RIPv2_ROUTES_PER_PACKET - 1;
}
bool
MD5AuthHandler::authenticate_inbound(const uint8_t* packet,
size_t packet_bytes,
const uint8_t*& entries_ptr,
uint32_t& n_entries,
const IPv4& src_addr,
bool new_peer)
{
static_assert(MD5PacketTrailer::SIZE == 20);
//
// XXX: if no valid keys, then don't use any authentication
//
if (_valid_key_chain.empty()) {
if (_null_handler.authenticate_inbound(packet, packet_bytes,
entries_ptr, n_entries,
src_addr, new_peer)
!= true) {
set_error(_null_handler.error());
return (false);
}
reset_error();
return (true);
}
entries_ptr = NULL;
n_entries = 0;
if (packet_bytes > RIPv2_MAX_PACKET_BYTES + MD5PacketTrailer::size()) {
set_error(c_format("packet too large (%u bytes)",
XORP_UINT_CAST(packet_bytes)));
return false;
}
if (packet_bytes < RIPv2_MIN_AUTH_PACKET_BYTES) {
set_error(c_format("packet too small (%u bytes)",
XORP_UINT_CAST(packet_bytes)));
return false;
}
const MD5PacketRouteEntry4 mpr(packet + RipPacketHeader::size());
if (mpr.addr_family() != MD5PacketRouteEntry4::ADDR_FAMILY) {
set_error("not an authenticated packet");
return false;
}
if (mpr.auth_type() != MD5PacketRouteEntry4::AUTH_TYPE) {
set_error("not an MD5 authenticated packet");
return false;
}
if (mpr.auth_bytes() != MD5PacketTrailer::size()) {
set_error(c_format("wrong number of auth bytes (%d != %u)",
mpr.auth_bytes(),
XORP_UINT_CAST(MD5PacketTrailer::size())));
return false;
}
if (uint32_t(mpr.auth_off() + mpr.auth_bytes()) != packet_bytes) {
set_error(c_format("Size of packet does not correspond with "
"authentication data offset and size "
"(%d + %d != %u).", mpr.auth_off(),
mpr.auth_bytes(),
XORP_UINT_CAST(packet_bytes)));
return false;
}
KeyChain::iterator k = find_if(_valid_key_chain.begin(),
_valid_key_chain.end(),
bind2nd(mem_fun_ref(&MD5Key::id_matches),
mpr.key_id()));
if (k == _valid_key_chain.end()) {
set_error(c_format("packet with key ID %d for which no key is "
"configured", mpr.key_id()));
return false;
}
MD5Key* key = &(*k);
if (new_peer)
key->reset(src_addr);
uint32_t last_seqno_recv = key->last_seqno_recv(src_addr);
if (key->packets_received(src_addr) && !(new_peer && mpr.seqno() == 0) &&
(mpr.seqno() - last_seqno_recv >= 0x7fffffff)) {
set_error(c_format("bad sequence number 0x%08x < 0x%08x",
XORP_UINT_CAST(mpr.seqno()),
XORP_UINT_CAST(last_seqno_recv)));
return false;
}
const MD5PacketTrailer mpt(packet + mpr.auth_off());
if (mpt.valid() == false) {
set_error("invalid authentication trailer");
return false;
}
MD5_CTX ctx;
uint8_t digest[16];
MD5_Init(&ctx);
MD5_Update(&ctx, packet, mpr.auth_off() + mpt.auth_data_offset());
MD5_Update(&ctx, key->key_data(), key->key_data_bytes());
MD5_Final(digest, &ctx);
if (memcmp(digest, mpt.auth_data(), mpt.auth_data_bytes()) != 0) {
set_error(c_format("authentication digest doesn't match local key "
"(key ID = %d)", key->id()));
// #define DUMP_BAD_MD5
#ifdef DUMP_BAD_MD5
const char badmd5[] = "/tmp/rip_badmd5";
// If the file already exists don't dump anything. The file
// should contain and only one packet.
if (-1 == access(badmd5, R_OK)) {
XLOG_INFO("Dumping bad MD5 to %s", badmd5);
FILE *fp = fopen(badmd5, "w");
fwrite(packet, packet_bytes, 1 , fp);
fclose(fp);
}
#endif
return false;
}
// Update sequence number only after packet has passed digest check
key->set_last_seqno_recv(src_addr, mpr.seqno());
reset_error();
n_entries = (mpr.auth_off() - RipPacketHeader::size()) /
PacketRouteEntry<IPv4>::size() - 1;
if (n_entries > 0) {
entries_ptr = (packet + RipPacketHeader::size()
+ MD5PacketRouteEntry4::size());
}
return true;
}
bool
MD5AuthHandler::authenticate_outbound(RipPacket<IPv4>& packet,
list<RipPacket<IPv4> *>& auth_packets,
size_t& n_routes)
{
RipPacket<IPv4> first_packet(packet);
vector<uint8_t> first_trailer;
KeyChain::iterator iter;
static_assert(MD5PacketTrailer::SIZE == 20);
//
// XXX: if no valid keys, then don't use any authentication
//
if (_valid_key_chain.empty()) {
if (_null_handler.authenticate_outbound(packet, auth_packets,
n_routes)
!= true) {
set_error(_null_handler.error());
return (false);
}
reset_error();
return (true);
}
//
// Create an authenticated copy of the packet for each valid key
//
for (iter = _valid_key_chain.begin();
iter != _valid_key_chain.end();
++iter) {
MD5Key& key = *iter;
RipPacket<IPv4>* copy_packet = new RipPacket<IPv4>(packet);
auth_packets.push_back(copy_packet);
uint8_t* first_entry_ptr = NULL;
if (head_entries() > 0)
first_entry_ptr = copy_packet->route_entry_ptr(0);
MD5PacketRouteEntry4Writer mpr(first_entry_ptr);
mpr.initialize(copy_packet->data_bytes(), key.id(),
MD5PacketTrailer::size(),
key.next_seqno_out());
vector<uint8_t> trailer;
trailer.resize(MD5PacketTrailer::size());
MD5PacketTrailerWriter mpt(&trailer[0]);
mpt.initialize();
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, copy_packet->data_ptr(), mpr.auth_off());
MD5_Update(&ctx, &trailer[0], mpt.auth_data_offset());
MD5_Update(&ctx, key.key_data(), key.key_data_bytes());
MD5_Final(mpt.auth_data(), &ctx);
//
// XXX: create a copy of the first packet without the trailer
// and of the trailer itself.
//
if (iter == _valid_key_chain.begin()) {
first_packet = *copy_packet;
first_trailer = trailer;
}
copy_packet->append_data(trailer);
}
packet = first_packet;
n_routes = packet.data_bytes() / MD5PacketRouteEntry4::size() - 1;
packet.append_data(first_trailer);
reset_error();
return (true);
}
bool
MD5AuthHandler::add_key(uint8_t key_id,
const string& key,
const TimeVal& start_timeval,
const TimeVal& end_timeval,
string& error_msg)
{
TimeVal now;
XorpTimer start_timer, end_timer;
string dummy_error_msg;
_eventloop.current_time(now);
if (start_timeval > end_timeval) {
error_msg = c_format("Start time is later than the end time");
return false;
}
if (end_timeval < now) {
error_msg = c_format("End time is in the past");
return false;
}
if (start_timeval > now) {
start_timer = _eventloop.new_oneoff_at(
start_timeval,
callback(this, &MD5AuthHandler::key_start_cb, key_id));
}
if (end_timeval != TimeVal::MAXIMUM()) {
end_timer = _eventloop.new_oneoff_at(
end_timeval,
callback(this, &MD5AuthHandler::key_stop_cb, key_id));
}
//
// XXX: If we are using the last authentication key that has expired,
// move it to the list of invalid keys.
//
if (_valid_key_chain.size() == 1) {
MD5Key& key = _valid_key_chain.front();
if (key.is_persistent()) {
key.set_persistent(false);
_invalid_key_chain.push_back(key);
_valid_key_chain.pop_front();
}
}
// XXX: for simplicity just try to remove the key even if it doesn't exist
remove_key(key_id, dummy_error_msg);
// Add the new key to the appropriate chain
MD5Key new_key = MD5Key(key_id, key, start_timeval, end_timeval,
start_timer, end_timer);
if (start_timer.scheduled())
_invalid_key_chain.push_back(new_key);
else
_valid_key_chain.push_back(new_key);
return true;
}
bool
MD5AuthHandler::remove_key(uint8_t key_id, string& error_msg)
{
KeyChain::iterator i;
// Check among all valid keys
i = find_if(_valid_key_chain.begin(), _valid_key_chain.end(),
bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id));
if (i != _valid_key_chain.end()) {
_valid_key_chain.erase(i);
return true;
}
// Check among all invalid keys
i = find_if(_invalid_key_chain.begin(), _invalid_key_chain.end(),
bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id));
if (i != _invalid_key_chain.end()) {
_invalid_key_chain.erase(i);
return true;
}
error_msg = c_format("No such key");
return false;
}
void
MD5AuthHandler::key_start_cb(uint8_t key_id)
{
KeyChain::iterator i;
// Find the key among all invalid keys and move it to the valid keys
i = find_if(_invalid_key_chain.begin(), _invalid_key_chain.end(),
bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id));
if (i != _invalid_key_chain.end()) {
MD5Key& key = *i;
_valid_key_chain.push_back(key);
_invalid_key_chain.erase(i);
}
}
void
MD5AuthHandler::key_stop_cb(uint8_t key_id)
{
KeyChain::iterator i;
// Find the key among all valid keys and move it to the invalid keys
i = find_if(_valid_key_chain.begin(), _valid_key_chain.end(),
bind2nd(mem_fun_ref(&MD5Key::id_matches), key_id));
if (i != _valid_key_chain.end()) {
MD5Key& key = *i;
//
// XXX: If the last key expires then keep using it as per
// RFC 2082 Section 4.3 until the lifetime is extended, the key
// is deleted by network management, or a new key is configured.
//
if (_valid_key_chain.size() == 1) {
XLOG_WARNING("Last authentication key (key ID = %u) has expired. "
"Will keep using it until its lifetime is extended, "
"the key is deleted, or a new key is configured.",
key_id);
key.set_persistent(true);
return;
}
_invalid_key_chain.push_back(key);
_valid_key_chain.erase(i);
}
}
void
MD5AuthHandler::reset_keys()
{
KeyChain::iterator iter;
for (iter = _valid_key_chain.begin();
iter != _valid_key_chain.end();
++iter) {
iter->reset();
}
}
bool
MD5AuthHandler::empty() const
{
return (_valid_key_chain.empty() && _invalid_key_chain.empty());
}
syntax highlighted by Code2HTML, v. 0.9.1