/**************************************************************************** 
** File: pppoe.c
**
** Author: Andy Juniper
**
** Comments: PPP module.
**
** $Id: pppoe.c,v 1.5 2001/05/28 19:24:04 mborella Exp $
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Library General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "pppoe.h"
#include "ppp.h"

/*
 * PPPOE code types
 */

#define PPPOE_CODE_PADI   0x09
#define PPPOE_CODE_PADO   0x07
#define PPPOE_CODE_PADR   0x19
#define PPPOE_CODE_PADS   0x65
#define PPPOE_CODE_PADT   0xa7
#define PPPOE_CODE_SESS   0x00

/*
 * PPPOE tag types
 */

#define PPPOE_TAG_ENDOFLIST     0x0000
#define PPPOE_TAG_SERVICENAME   0x0101
#define PPPOE_TAG_ACNAME        0x0102
#define PPPOE_TAG_HOSTUNIQ      0x0103
#define PPPOE_TAG_ACCOOKIE      0x0104
#define PPPOE_TAG_VENDORSPEC    0x0105
#define PPPOE_TAG_RELAYSESSID   0x0110
#define PPPOE_TAG_SERVNAMEERR   0x0201
#define PPPOE_TAG_ACSYSERR      0x0202
#define PPPOE_TAG_GENERICERR    0x0203

/*
 * PPPOE code map
 */

strmap_t pppoe_code_map[] =
{
  { PPPOE_CODE_PADI   ,"PADI" },
  { PPPOE_CODE_PADO   ,"PADO" },
  { PPPOE_CODE_PADR   ,"PADR" },
  { PPPOE_CODE_PADS   ,"PADS" },
  { PPPOE_CODE_PADT   ,"PADT" },
  { PPPOE_CODE_SESS   ,"SESSION" },
  { 0, "" }
};

/*
 * PPPOE TLV map
 */

strmap_t pppoe_tlv_map[] =
{
  { PPPOE_TAG_ENDOFLIST     ,"End of List" },
  { PPPOE_TAG_SERVICENAME   ,"Service Name" },
  { PPPOE_TAG_ACNAME        ,"AC Name" },
  { PPPOE_TAG_HOSTUNIQ      ,"Host Uniq" },
  { PPPOE_TAG_ACCOOKIE      ,"AC Cookie" },
  { PPPOE_TAG_VENDORSPEC    ,"Vendor Specific" },
  { PPPOE_TAG_RELAYSESSID   ,"Relay Session ID" },
  { PPPOE_TAG_SERVNAMEERR   ,"Service Name Error" },
  { PPPOE_TAG_ACSYSERR      ,"AC System Error" },
  { PPPOE_TAG_GENERICERR    ,"Generic Error" },
  { 0, "" }
};

/*
 * PPPOE frame format
 */

typedef struct pppoe
{
  u_int8_t  vertype;
  u_int8_t  code;
  u_int16_t session;
  u_int16_t length;
} pppoe_t;

typedef struct pppoe_tlv
{
  u_int16_t tagtype;
  u_int16_t taglen;
} pppoe_tlv_t;

extern struct arg_t * my_args;

/*----------------------------------------------------------------------------
**
** dump_pppoe_hdr()
**
** Displays PPPOE headers
**
**----------------------------------------------------------------------------
*/

int dump_pppoe_hdr(packet_t *pkt)
{
  pppoe_t pppoe;
  int len;

  /* Set the layer */
  set_layer(LAYER_DATALINK);

  /*
   * Get the header
   */
  
  if (get_packet_bytes((u_int8_t *) &pppoe, pkt, sizeof(pppoe_t)) == 0)
    return 0;
  
  len = ntohs(pppoe.length);
  
  /*
   * Dump the header
   */
  
  if (my_args->m)
    {
      display_minimal_string("| PPPOE ");
    }
  else
    {
      display_header_banner("PPPOE Header");
      display("Version/Type", &pppoe.vertype, 1, DISP_HEX);
      display("Session ID", (u_int8_t *) &pppoe.session, 2, DISP_HEX);
      display("Length", (u_int8_t *) &len, 2, DISP_DEC);
      display_strmap_hex("Message code", pppoe.code, pppoe_code_map);
    }
  
  /* dump the hex buffer */
  hexbuffer_flush();
    
  return len;
}

/*----------------------------------------------------------------------------
**
** dump_pppoed()
**
** Displays PPPOE discovery packets 
**
**----------------------------------------------------------------------------
*/

void dump_pppoed(packet_t *pkt) 
{
  pppoe_tlv_t tlv;
  int len;
  int offset;
  int totlen;
  u_int8_t data[1000]; /* should be enough */

  totlen = dump_pppoe_hdr(pkt);

  /* do no more if we don't need to */
  if (my_args->m) 
    return;
    
  /* dump the TLVs */
  while ((totlen > 0) &&
	 (get_packet_bytes((u_int8_t*)&tlv, pkt, sizeof(pppoe_tlv_t)))) 
    {
      /* do the header */
      len = ntohs(tlv.taglen);
      totlen -= (sizeof(pppoe_tlv_t)+len);
      display_strmap_hex("  TLV type", ntohs(tlv.tagtype), pppoe_tlv_map);
      display("  TLV len", (u_int8_t*)&len, 2, DISP_DEC);

      /* copy the data out */
      len = (len>sizeof(data))?sizeof(data)-1:len;
      if (len == 0) continue;
      if (get_packet_bytes(data, pkt, len) == 0) 
	return;
      offset=0;

      if (tlv.tagtype == PPPOE_TAG_ENDOFLIST) break;

      /* and the data */
      switch (ntohs(tlv.tagtype)) {
      case PPPOE_TAG_SERVICENAME:
      case PPPOE_TAG_ACNAME:
      case PPPOE_TAG_SERVNAMEERR:
      case PPPOE_TAG_ACSYSERR:
      case PPPOE_TAG_GENERICERR:
	/* all string values, if present, not necessarily null terminated */
	data[len]=0;
	display("  TLV value", data, len, DISP_STRING);
	break;
	
      case PPPOE_TAG_VENDORSPEC:
	/* 
	 * 4 bytes vendor id followed by whatever - we dump as hex by 
	 * falling through 
	 */
	display("  TLV vendor", data, 4, DISP_HEX);
	offset+=4;
	
      case PPPOE_TAG_HOSTUNIQ:
      case PPPOE_TAG_ACCOOKIE:
      case PPPOE_TAG_RELAYSESSID:
      default:
	/* all hex values, if present, and not necessarily null terminated */
	display("  TLV value", data+offset, len-offset, DISP_HEX);
	break;
	
      }
    }

  /* dump the hex buffer */
  hexbuffer_flush();
  
}

/*----------------------------------------------------------------------------
**
** dump_pppoes()
**
** Displays PPPOE data session packets 
**
**----------------------------------------------------------------------------
*/

void dump_pppoes(packet_t *pkt) 
{
  dump_pppoe_hdr(pkt);
  dump_ppp(pkt);
}

