Logo Search packages:      
Sourcecode: ike version File versions  Download package

ike.idb.phase1.cpp

/*
 * Copyright (c) 2007
 *      Shrew Soft Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Redistributions in any form must be accompanied by information on
 *    how to obtain complete source code for the software and any
 *    accompanying software that uses the software.  The source code
 *    must either be included in the distribution or be available for no
 *    more than the cost of distribution plus a nominal fee, and must be
 *    freely redistributable under reasonable conditions.  For an
 *    executable file, complete source code means the source code for all
 *    modules it contains.  It does not include source code for modules or
 *    files that typically accompany the major components of the operating
 *    system on which the executable file runs.
 *
 * THIS SOFTWARE IS PROVIDED BY SHREW SOFT INC ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT, ARE DISCLAIMED.  IN NO EVENT SHALL SHREW SOFT INC
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 *
 * AUTHOR : Matthew Grooms
 *          mgrooms@shrew.net
 *
 */

#include "iked.h"

//==============================================================================
// ike phase1 exchange events
//==============================================================================

bool _ITH_EVENT_PH1SOFT::func()
{
      iked.log.txt( LLOG_INFO,
            "ii : phase1 sa is expiring\n"
            "ii : %04x%04x:%04x%04x\n",
            htonl( *( long * ) &ph1->cookies.i[ 0 ] ),
            htonl( *( long * ) &ph1->cookies.i[ 4 ] ),
            htonl( *( long * ) &ph1->cookies.r[ 0 ] ),
            htonl( *( long * ) &ph1->cookies.r[ 4 ] ) );

      //
      // if the tunnel peer definition states
      // that we are to act as a client, replace
      // this phase1 sa and add it to our list
      //

      if( ph1->tunnel->peer->contact == IPSEC_CONTACT_CLIENT )
      {
            IDB_PH1 * addph1 = new IDB_PH1( ph1->tunnel, true, NULL );
            addph1->add( false );
            iked.process_phase1_send( addph1 );
            addph1->dec( false );
      }

      ph1->status( XCH_STATUS_EXPIRING, XCH_NORMAL, 0 );
      ph1->dec( true );

      //
      // FIXME : dpd and natt events should stop for
      //         this sa after the new sa negotiates
      //

      return false;
}

bool _ITH_EVENT_PH1HARD::func()
{
      iked.log.txt( LLOG_INFO,
            "ii : phase1 sa is expired\n"
            "ii : %04x%04x:%04x%04x\n",
            htonl( *( long * ) &ph1->cookies.i[ 0 ] ),
            htonl( *( long * ) &ph1->cookies.i[ 4 ] ),
            htonl( *( long * ) &ph1->cookies.r[ 0 ] ),
            htonl( *( long * ) &ph1->cookies.r[ 4 ] ) );

      ph1->status( XCH_STATUS_EXPIRED, XCH_FAILED_EXPIRED, 0 );
      ph1->dec( true );

      return false;
}

bool _ITH_EVENT_PH1DEAD::func()
{
      iked.log.txt( LLOG_INFO,
            "ii : phase1 sa is dead\n"
            "ii : %04x%04x:%04x%04x\n",
            htonl( *( long * ) &ph1->cookies.i[ 0 ] ),
            htonl( *( long * ) &ph1->cookies.i[ 4 ] ),
            htonl( *( long * ) &ph1->cookies.r[ 0 ] ),
            htonl( *( long * ) &ph1->cookies.r[ 4 ] ) );

      ph1->status( XCH_STATUS_DEAD, XCH_FAILED_EXPIRED, 0 );
      ph1->dec( true );

      return false;
}

//==============================================================================
// ike phase1 exchange handle list
//==============================================================================

IDB_PH1 * _IDB_LIST_PH1::get( int index )
{
      return static_cast<IDB_PH1*>( get_entry( index ) );
}

bool _IDB_LIST_PH1::find( bool lock, IDB_PH1 ** ph1, IDB_TUNNEL * tunnel, XCH_STATUS min, XCH_STATUS max, IKE_COOKIES * cookies )
{
      if( ph1 != NULL )
            *ph1 = NULL;

      if( lock )
            iked.lock_idb.lock();

      //
      // step through our list of sa's
      // and locate a match
      //

      long ph1_count = count();
      long ph1_index = 0;

      for( ; ph1_index < ph1_count; ph1_index++ )
      {
            //
            // get the next sa in our list
            //

            IDB_PH1 * tmp_ph1 = get( ph1_index );

            //
            // match sa minimum status level
            //

            if( min != XCH_STATUS_ANY )
                  if( tmp_ph1->status() < min )
                        continue;

            //
            // match sa maximum status level
            //

            if( max != XCH_STATUS_ANY )
                  if( tmp_ph1->status() > max )
                        continue;

            //
            // match the tunnel id
            //

            if( tunnel != NULL )
                  if( tmp_ph1->tunnel != tunnel )
                        continue;

            //
            // match the cookies
            //

            if( cookies != NULL )
            {
                  //
                  // next match the initiator cookie
                  //

                  if( memcmp( tmp_ph1->cookies.i, cookies->i, ISAKMP_COOKIE_SIZE ) )
                  {
                        //
                        // the initiator cookie should
                        // always match if we are to
                        // return a known sa
                        //

                        continue;
                  }

                  //
                  // next match the responder cookie
                  //

                  if( memcmp( tmp_ph1->cookies.r, cookies->r, ISAKMP_COOKIE_SIZE ) )
                  {
                        //
                        // responder cookie did not match,
                        // if we are the intiator for this
                        // sa, the responder cookie is null
                        // and we are waiting on an sa
                        // payload, it should match
                        //

                        if( tmp_ph1->initiator )
                        {
                              //
                              // check to see if we solicited
                              // a response from this host
                              //

                              if( !( tmp_ph1->xstate & XSTATE_SENT_SA ) ||
                                     ( tmp_ph1->xstate & XSTATE_RECV_SA ) )
                                     continue;

                              //
                              // check the responder cookie
                              // for a null value
                              //

                              for( long x = 0; x < ISAKMP_COOKIE_SIZE; x++ )
                                    if( tmp_ph1->cookies.r[ x ] )
                                          continue;

                              //
                              // store the responders cookie in
                              // our existing sa
                              //

                              memcpy( tmp_ph1->cookies.r, cookies->r, ISAKMP_COOKIE_SIZE );
                        }
                  }
            }

            //
            // looks like we found a match
            //

            iked.log.txt( LLOG_DEBUG, "DB : phase1 found\n" );

            //
            // increase our refrence count
            //

            if( ph1 != NULL )
            {
                  tmp_ph1->inc( false );
                  *ph1 = tmp_ph1;
            }

            if( lock )
                  iked.lock_idb.unlock();

            return true;
      }

      iked.log.txt( LLOG_DEBUG, "DB : phase1 not found\n" );

      if( lock )
            iked.lock_idb.unlock();

      return false;
}

//==============================================================================
// ike phase1 exchange handle list entry
//==============================================================================

_IDB_PH1::_IDB_PH1( IDB_TUNNEL * set_tunnel, bool set_initiator, IKE_COOKIES * set_cookies )
{
      evp_cipher = NULL;
      evp_hash = NULL;
      hash_size = 0;

      memset( &cookies, 0, sizeof( cookies ) );

      vendopts_l.flags = 0;
      vendopts_r.flags = 0;

      natt_version = IPSEC_NATT_NONE;
      natt_pldtype = ISAKMP_PAYLOAD_NONE;

      ctype_l = 0;
      ctype_r = 0;

      hash_size = 0;

      //
      // initialize associated tunnel
      //

      tunnel = set_tunnel;
      tunnel->inc( true );

      //
      // initialize initiator value
      //
      
      initiator = set_initiator;

      //
      // initialize exchange type
      //
      
      exchange = tunnel->peer->exchange;

      //
      // initialize proposal list
      //

      iked.phase1_gen_prop( this );

      //
      // initialize local id
      //

      iked.gen_ph1id_l( this, ph1id_l );

      //
      // initialize cookie
      //

      if( initiator )
            iked.rand_bytes( cookies.i, ISAKMP_COOKIE_SIZE );
      else
      {
            if( set_cookies != NULL )
                  memcpy( cookies.i, set_cookies->i, ISAKMP_COOKIE_SIZE );

            iked.rand_bytes( cookies.r, ISAKMP_COOKIE_SIZE );
      }

      //
      // initialize nonce data
      //

      nonce_l.size( ISAKMP_NONCE_SIZE );
      iked.rand_bytes( nonce_l.buff(), ISAKMP_NONCE_SIZE );

      //
      // always advertise as shrew soft
      //

      vendopts_l.flag.ssoft = true;

      //
      // determine if this is a client tunnel
      //

      if( tunnel->peer->contact == IPSEC_CONTACT_CLIENT )
      {
            vendopts_l.flag.netsc = true;
            vendopts_l.flag.zwall = true;
            vendopts_l.flag.swind = true;
            vendopts_l.flag.unity = true;

            if( tunnel->xconf.opts & IPSEC_OPTS_VEND_CHKPT )
                  vendopts_l.flag.chkpt = true;
      }

      //
      // determine ike fragmentation negotiation
      //

      if( tunnel->peer->frag_ike_mode >= IPSEC_FRAG_ENABLE )
            vendopts_l.flag.frag = true;

      if( tunnel->peer->frag_ike_mode == IPSEC_FRAG_FORCE )
            vendopts_r.flag.frag = true;

      //
      // determine natt negotiation
      //

      if( tunnel->peer->natt_mode >= IPSEC_NATT_ENABLE )
            vendopts_l.flag.natt = true;

      //
      // determine dpd negotiation
      //

      if( tunnel->peer->dpd_mode >= IPSEC_DPD_ENABLE )
            vendopts_l.flag.dpdv1 = true;

      if( tunnel->peer->dpd_mode == IPSEC_DPD_FORCE )
            vendopts_r.flag.dpdv1 = true;

      //
      // locate the first isakmp proposal
      //

      IKE_PROPOSAL * proposal;
      tunnel->peer->proposals.get( &proposal, 0, ISAKMP_PROTO_ISAKMP );

      //
      // if we are the initiator, obtain
      // the authentication type
      //

      if( initiator )
      {
            auth_id = proposal->auth_id;

            //
            // determine xauth negotiation
            //

            if( ( proposal->auth_id == XAUTH_AUTH_INIT_PSK ) ||
                  ( proposal->auth_id == XAUTH_AUTH_INIT_RSA ) ||
                  ( proposal->auth_id == HYBRID_AUTH_INIT_RSA ) )
                  vendopts_l.flag.xauth = true;
      }

      //
      // aggressive mode must include a kex
      // payload in the first packet. make
      // sure we setup the dhgroup in advance
      //

      if( tunnel->peer->exchange == ISAKMP_EXCH_AGGRESSIVE )
            setup_dhgrp( proposal );

      //
      // initialize event info
      //

      event_soft.ph1 = this;
      event_hard.ph1 = this;
      event_dead.ph1 = this;

      //
      // build text strings for logging
      //

      char txtaddr_l[ LIBIKE_MAX_TEXTADDR ];
      char txtaddr_r[ LIBIKE_MAX_TEXTADDR ];

      iked.text_addr( txtaddr_l, &tunnel->saddr_l, true );
      iked.text_addr( txtaddr_r, &tunnel->saddr_r, true );

      //
      // phase 1 created
      //

      iked.log.txt( LLOG_DEBUG,
            "DB : new phase1 ( ISAKMP %s )\n"
            "DB : exchange type is %s\n"
            "DB : %s <-> %s\n"
            "DB : %08x%08x:%08x%08x\n",
            iked.find_name( NAME_INITIATOR, initiator ),
            iked.find_name( NAME_EXCHANGE, exchange ),
            txtaddr_l,
            txtaddr_r,
            htonl( *( long * ) &cookies.i[ 0 ] ),
            htonl( *( long * ) &cookies.i[ 4 ] ),
            htonl( *( long * ) &cookies.r[ 0 ] ),
            htonl( *( long * ) &cookies.r[ 4 ] ) );
}

_IDB_PH1::~_IDB_PH1()
{
      clean();

      //
      // derefrence our tunnel
      //

      tunnel->dec( false );
}

//------------------------------------------------------------------------------
// abstract functions from parent class
//

const char * _IDB_PH1::name()
{
      static const char * xname = "phase1";
      return xname;
}

IDB_RC_LIST * _IDB_PH1::list()
{
      return &iked.idb_list_ph1;
}

void _IDB_PH1::beg()
{
}

void _IDB_PH1::end()
{
      //
      // clear the resend queue
      //

      resend_clear( false, true );

      //
      // remove scheduled events
      //

      if( iked.ith_timer.del( &event_soft ) )
      {
            idb_refcount--;
            iked.log.txt( LLOG_DEBUG,
                  "DB : phase1 soft event canceled ( ref count = %i )\n",
                  idb_refcount );
      }

      if( iked.ith_timer.del( &event_hard ) )
      {
            idb_refcount--;
            iked.log.txt( LLOG_DEBUG,
                  "DB : phase1 hard event canceled ( ref count = %i )\n",
                  idb_refcount );
      }

      if( iked.ith_timer.del( &event_dead ) )
      {
            idb_refcount--;
            iked.log.txt( LLOG_DEBUG,
                  "DB : phase1 dead event canceled ( ref count = %i )\n",
                  idb_refcount );
      }

      //
      // send a delete message if required
      //

      if( ( lstate & LSTATE_HASKEYS ) &&
            ( xch_errorcode != XCH_FAILED_EXPIRED ) &&
            ( xch_errorcode != XCH_FAILED_PEER_DELETE ) )
            iked.inform_new_delete( this, NULL );

      //
      // if this sa has reached maturity,
      // locate any config handles used by
      // this sa and delete them
      //
      // FIXME : There must be a better way
      //

      if( lstate & LSTATE_HASKEYS )
      {
            //
            // FIXME : Use find here
            //

            long cfg_count = iked.idb_list_cfg.count();
            long cfg_index = 0;

            for( ; cfg_index < cfg_count; cfg_index++ )
            {
                  //
                  // get the next config in our list
                  // and attempt to match by pointer
                  // 

                  IDB_CFG * cfg = iked.idb_list_cfg.get( cfg_index );
                  if( cfg->ph1ref == this )
                  {
                        cfg->inc( false );

                        cfg->status( XCH_STATUS_DEAD, XCH_FAILED_PENDING, 0 );

                        if( cfg->dec( false ) )
                        {
                              cfg_index--;
                              cfg_count--;
                        }
                  }
            }
      }

      //
      // if this sa never reached maturity,
      // locate any pending phase2 handles
      // for this tunnel and delete them
      //
      // FIXME : This should be timer driven
      //

      if( !( lstate & LSTATE_HASKEYS ) )
      {
            //
            // FIXME : Use find here
            //

            long ph2_count = iked.idb_list_ph2.count();
            long ph2_index = 0;

            for( ; ph2_index < ph2_count; ph2_index++ )
            {
                  //
                  // get the next phase2 in our list
                  // and attempt to match tunnel ids
                  // 

                  IDB_PH2 * ph2 = iked.idb_list_ph2.get( ph2_index );
                  if( ( ph2->tunnel == tunnel ) && ( ph2->status() == XCH_STATUS_PENDING ) )
                  {
                        ph2->inc( false );

                        ph2->status( XCH_STATUS_DEAD, XCH_FAILED_PENDING, 0 );

                        if( ph2->dec( false ) )
                        {
                              ph2_index--;
                              ph2_count--;
                        }
                  }
            }
      }

      //
      // log deletion
      //

      if( xch_errorcode != XCH_FAILED_EXPIRED )
            iked.log.txt( LLOG_INFO, "ii : phase1 removal before expire time\n" );
      else
            iked.log.txt( LLOG_INFO, "ii : phase1 removal after expire time\n" );

      //
      // if we have negotiated a replacement
      // isakmp sa, change our delete status
      // to expired. some gateways will send
      // a delete message for the old sa and
      // we don't want to treat this as an
      // error condition below.
      //

      if( xch_errorcode == XCH_FAILED_PEER_DELETE )
            if( iked.idb_list_ph1.find(
                        false,
                        NULL,
                        tunnel,
                        XCH_STATUS_MATURE,
                        XCH_STATUS_MATURE,
                        NULL ) )
                  xch_errorcode = XCH_FAILED_EXPIRED;

      //
      // if this is a client tunnel and there
      // was an error negotiating phase1, set
      // a close error message
      //

      if( tunnel->peer->contact == IPSEC_CONTACT_CLIENT )
      {
            if( xch_errorcode != XCH_FAILED_EXPIRED )
            {
                  tunnel->close = xch_errorcode;
                  tunnel->ikei->wakeup();
            }
      }
}

//------------------------------------------------------------------------------
// additional functions
//

bool _IDB_PH1::setup_dhgrp( IKE_PROPOSAL * proposal )
{
      //
      // initialize dh group
      //

      if( !dh_init( proposal->dhgr_id, &dh, &dh_size ) )
      {
            iked.log.txt( LLOG_ERROR, "ii : failed to setup DH group\n" );
            return false;
      }

      xl.size( dh_size );
      long result = BN_bn2bin( dh->pub_key, xl.buff() );

      //
      // fixup public buffer alignment
      //

      if( dh_size > result )
      {
            iked.log.txt( LLOG_DEBUG, "ww : short DH public value\n" );
            xl.size( result );
            xl.ins( 0, dh_size - result );
      }

      return true;
}

bool _IDB_PH1::setup_xform( IKE_PROPOSAL * proposal )
{
      //
      // select cipher and hash crypto objects
      //

      switch( proposal->ciph_id )
      {
            case IKE_CIPHER_DES:

                  evp_cipher = EVP_des_cbc();

                  break;

            case IKE_CIPHER_BLOWFISH:

                  evp_cipher = EVP_bf_cbc();

                  break;

            case IKE_CIPHER_3DES:

                  evp_cipher = EVP_des_ede3_cbc();

                  break;

            case IKE_CIPHER_CAST:

                  evp_cipher = EVP_cast5_cbc();

                  break;

            case IKE_CIPHER_AES:
            {
                  switch( proposal->ciph_kl )
                  {
                        case 128:

                              evp_cipher = EVP_aes_128_cbc();

                              break;

                        case 192:

                              evp_cipher = EVP_aes_192_cbc();

                              break;

                        case 256:

                              evp_cipher = EVP_aes_256_cbc();

                              break;

                        default:

                              return false;
                  }

                  break;
            }

            default:
                  return false;
      }

      switch( proposal->hash_id )
      {
            case IKE_HASH_MD5:

                  evp_hash = EVP_md5();

                  break;

            case IKE_HASH_SHA1:

                  evp_hash = EVP_sha1();

                  break;

            default:
                  return false;
      }

      hash_size = EVP_MD_size( evp_hash );

      return true;
}

void _IDB_PH1::clean()
{
      if( dh )
      {
            DH_free( dh );
            dh = NULL;
      }

      nonce_l.del( true );
      nonce_r.del( true );

      xl.del( true );
      xr.del( true );

      hash_l.del( true );
      hash_r.del( true );

      hda.del( true );;

      idi.del( true );
      idr.del( true );

      natd_hash_l.clean();
      natd_hash_r.clean();

      sign_r.del( true );
}

bool _IDB_PH1::frag_add( unsigned char * data, unsigned long size, long index, bool last )
{
      //
      // create our new ike fragment
      //

      IKE_FRAG * frag = new IKE_FRAG;
      if( frag == NULL )
            return false;

      //
      // set the fragment info
      //

      frag->index = index;
      frag->last = last;
      frag->data.set( data, size );

      //
      // add the fragment to our list
      //

      return frags.add_entry( frag );
}

bool _IDB_PH1::frag_get( PACKET_IKE & packet )
{
      bool frag_done = false;
      int   frag_index = 1;

      //
      // check to see if we have a
      // complete ike packet
      //

      while( !frag_done )
      {
            //
            // step through all fragments
            // and look for the next index
            //

            int count = frags.count();
            int index = 0;

            for( ; index < count; index++ )
            {
                  IKE_FRAG * frag = static_cast<IKE_FRAG*>( frags.get_entry( index ) );

                  //
                  // does this match our next index
                  //

                  if( frag->index == frag_index )
                  {
                        //
                        // is this the last fragment
                        //

                        if( frag->last )
                        {
                              frag_done = true;
                              break;
                        }

                        //
                        // not the last fragment, look
                        // for the next one
                        //

                        frag_index++;
                        continue;
                  }
            }

            //
            // if we made a complete pass through
            // our fragment list and did not find
            // the next fragment, we dont have a
            // complete packet yet
            //

            if( index == count )
                  return false;
      }

      //
      // reassemble the packet from ike
      // fragments stored in our list
      //

      packet.reset();
      frag_done = false;
      frag_index = 1;

      while( !frag_done )
      {
            //
            // step through all fragments
            // and look for the next index
            //

            int count = frags.count();
            int index = 0;

            for( ; index < count; index++ )
            {
                  IKE_FRAG * frag = static_cast<IKE_FRAG*>( frags.get_entry( index ) );

                  //
                  // does this match our next index
                  //

                  if( frag->index == frag_index )
                  {
                        //
                        // add the data to our packet
                        //

                        packet.add( frag->data );

                        //
                        // is this the last fragment
                        //

                        if( frag->last )
                        {
                              frag_done = true;
                              break;
                        }

                        //
                        // not the last fragment, look
                        // for the next one
                        //

                        frag_index++;
                        continue;
                  }
            }
      }

      //
      // purge our fragment list
      //

      frags.clean();

      return true;
}


Generated by  Doxygen 1.6.0   Back to index