/* -*- C -*- */
/*
 * Assembler for DOCSIS cable modem configuration files
 *
 * $Id: docas.c,v 1.1 2000/07/27 16:17:00 petras Exp $
 * 
 * Copyright (C) 2000  Dietmar Petras  <Dietmar.Petras@ELSA.de>
 * 
 * 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 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.
 *
 * History:
 * $Log: docas.c,v $
 * Revision 1.1  2000/07/27 16:17:00  petras
 * * Based on Revision 1.6 of Assembler.c
 * * Bug fix with reallocation of OutputBuffer
 *
 * Revision 1.6  2000/07/10 14:29:18  petras
 * Renamed to docas.c to allow compilation on DOS with limited file names :-(
 *
 * Revision 1.5  2000/04/17 23:31:09  petras
 * Release 1.2
 *
 * Revision 1.4  2000/04/17 23:20:35  petras
 * Correct version number information from 1.0 to 1.1.
 *
 * Revision 1.3  2000/04/21 12:43:05  petras
 * New command line option `-i N' for generation of illegal configuration
 * files to be used by Application Test Procedure (ATP) MAC-05.
 *
 * Revision 1.2  2000/04/21 12:08:12  petras
 * After faulty fopen print error message from strerror().
 *
 * Revision 1.1  2000/03/01 23:19:37  petras
 * Initial revision.
 *
 */

#include "docas.h"

#include "lexan.h"
#include "yaccan.h"
#include "hmac_md5.h"
#include "getopt.h"

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>


/* {{{ Manipulating output buffer */

byte *OutputBuffer;
int OutputSize = 0;
int WritePtr;

int Use_HMAC_MD5 = 0; /* calculate CMTS_MIC with 0:MD5 else HMAC_MD5 */

/* ATP MAC-05 requires generation of illegal configuration files: */
/* - No End-Of-Data marker (ATP_IllegalConfig_NoEOD!=0) */
int ATP_IllegalConfig_NoEOD = 0;
/* - Invalid CM MIC */
int ATP_IllegalConfig_InvalidCM_MIC = 0;

void WriteByte(byte b) {
  /* initial Output on startup */
  if (OutputSize==0) {
    OutputSize = 4096;
    OutputBuffer = (byte *)malloc(OutputSize);
    WritePtr = 0;
  }
  /* If necessary, enlarge OutputBuffer */
  if (WritePtr == OutputSize) {
    int new_size = OutputSize * 2;
    byte *new_output = (byte *)malloc(new_size);
    memcpy(new_output, OutputBuffer, OutputSize);
    free(OutputBuffer);
    OutputBuffer = new_output;
    OutputSize = new_size;
  }
  OutputBuffer[WritePtr++] = b;
}

int GroupLenPos; /* next byte is first of group */

void StartGroup(byte type) {
  WriteByte(type);
  GroupLenPos = WritePtr;
  WriteByte(0); /* dummy for group length */
}

void EndGroup(eCMTS_MIC_DataType type) {
  int group_len = WritePtr - GroupLenPos - 1;
  OutputBuffer[GroupLenPos] = (byte)group_len;
  if (type!=eCMTS_MIC_NIL) {
    ReportCMTS_MIC_Data(type, GroupLenPos-1, group_len+2);
  }
}

void WriteNumber(unsigned long n, int len) {
  if (len>=4) WriteByte((byte)(n>>24 & 0xff));
  if (len>=3) WriteByte((byte)(n>>16 & 0xff));
  if (len>=2) WriteByte((byte)(n>> 8 & 0xff));
  if (len>=1) WriteByte((byte)(n     & 0xff));
}

void WriteString(const char *s, int len) {
  int i;
  for (i=0; i<len; i++) WriteByte((byte)s[i]);
}

int NextWritePosition() { return WritePtr; }
char *PtrInOutputBuffer(int pos) { return OutputBuffer+pos; }

void WriteByteAtPosition(int pos, byte b) {
  if (pos>=OutputSize) {
    fprintf(stderr, "Fatal internal error: pos>OutputSize in WriteByteAtPosition()!\n");
    exit(-1);
  }
  OutputBuffer[pos] = b;
}

void OutputAndClose(const char *out_file_name) {
  FILE *out_file = NULL;
  int i;
   
  if ((out_file = fopen(out_file_name, "wb")) == NULL) {	
    fprintf(stderr, "Error: Cannot open file %s, %s!\n",
	    out_file_name, strerror(errno));
    exit(-1);
  }
   

  for (i=0; i<WritePtr; i++) {
    fputc(OutputBuffer[i], out_file);
  }
  if (!ATP_IllegalConfig_NoEOD) {
    /* append End-of-Data Marker */
    fputc(255, out_file);
  }

  fclose(out_file);
	   
  free(OutputBuffer);
  OutputSize = 0;
}

/* }}} */
/* {{{ Calculate and append CM Message Integrity Check (MIC) */

void Append_CM_MIC() {
  struct MD5Context cm_mic_md5context;
  byte cm_mic[16];
  
  /* calculate CM MIC */
  MD5Init(&cm_mic_md5context);
  MD5Update(&cm_mic_md5context, OutputBuffer, WritePtr);
  MD5Final(cm_mic, &cm_mic_md5context);
  
  if (ATP_IllegalConfig_InvalidCM_MIC) {
    /*
     * ATP MAC-05 requires generation of illegal configuration files with
     * an invalid CM MIC.
     * I make the CM MIC illegal by just toggling the first byte
     */
    cm_mic[0] = ~cm_mic[0];
  }

  /* write to output */
  WriteByte(6); /* type */
  WriteByte(16);
  WriteString(cm_mic, 16);
  ReportCMTS_MIC_Data(eCMTS_MIC_CM_MIC, NextWritePosition()-18, 18);
}

/* }}} */
/* {{{ Calculate and append CMTS Message Integrity Check (MIC) */

struct CMTS_MIC_Data {
  int	Start;
  int	Length;
  struct CMTS_MIC_Data *Next;
};
    
struct CMTS_MIC_DataChain {
  struct CMTS_MIC_Data *First;
  struct CMTS_MIC_Data *Last;
};

struct CMTS_MIC_DataChain DownFreqData           = { NULL, NULL};
struct CMTS_MIC_DataChain UpChanIdData           = { NULL, NULL};
struct CMTS_MIC_DataChain NetworkAccessData      = { NULL, NULL};
struct CMTS_MIC_DataChain COSData                = { NULL, NULL};
struct CMTS_MIC_DataChain BPIData                = { NULL, NULL};
struct CMTS_MIC_DataChain VendorSpecificInfoData = { NULL, NULL};
struct CMTS_MIC_DataChain CM_MIC_Data            = { NULL, NULL};
struct CMTS_MIC_DataChain MaxNumCpeData          = { NULL, NULL};


void ReportCMTS_MIC_Data(eCMTS_MIC_DataType type, int start, int length) {
  struct CMTS_MIC_DataChain *chain;
  struct CMTS_MIC_Data *new;

  switch (type) {
   case eCMTS_MIC_DownFreq:
    chain = &DownFreqData;
    break;
   case eCMTS_MIC_UpChanId:
    chain = &UpChanIdData;
    break;
   case eCMTS_MIC_NetworkAccess:
    chain = &NetworkAccessData;
    break;
   case eCMTS_MIC_COS:
    chain = &COSData;
    break;
   case eCMTS_MIC_BPI:
    chain = &BPIData;
    break;
   case eCMTS_MIC_VendorSpecificInfo:
    chain = &VendorSpecificInfoData;
    break;
   case eCMTS_MIC_CM_MIC:
    chain = &CM_MIC_Data;
    break;
   case eCMTS_MIC_MaxNumCpe:
    chain = &MaxNumCpeData;
    break;
   default:
    abort();
  }
  new = (struct CMTS_MIC_Data*)malloc(sizeof(struct CMTS_MIC_Data));
  new->Start = start;
  new->Length = length;
  new->Next = NULL;

  if (chain->Last) chain->Last->Next=new;
  chain->Last = new;
  if (!chain->First) chain->First=new;
}

void AppendChainTo_CMTS_MIC(struct CMTS_MIC_DataChain *chain,
			    struct MD5Context *cmts_mic_md5context) {
  struct CMTS_MIC_Data *cur = chain->First;
  while (cur) {
    struct CMTS_MIC_Data *free_ptr;
    if (Use_HMAC_MD5) {
      HMAC_MD5Update(cmts_mic_md5context, PtrInOutputBuffer(cur->Start), cur->Length);
    } else {
      MD5Update(cmts_mic_md5context, PtrInOutputBuffer(cur->Start), cur->Length);
    }
    free_ptr = cur;
    cur=cur->Next;
    free(free_ptr);
  }
  chain->First = chain->Last = NULL;
}
    
  

/* Calculate and append CMTS Message Integrity Check (MIC) */
void Append_CMTS_MIC(const char *secret) {
  /* The CMTS MIC covers the following fields in the OutputBuffer:
   *
   * Downstream Frequency Configuration Setting
   * Upstream Channel ID Configuration Setting
   * Network Access Configuration Setting
   * Class of Service Configuration Setting
   * Baseline Privacy Configuration Setting
   * Vendor-Specific Configuration Settings
   * CM MIC Configuration Setting
   * Maximum Number of CPEs
   * TFTP Server Timestamp
   * TFTP Server Provisioned Modem Address
   * Authentication string (shared secret)
   */

  struct MD5Context cmts_mic_md5context;
  byte cmts_mic[16];
  
  /* calculate CM MIC */
  if (Use_HMAC_MD5) {
    HMAC_MD5Init(&cmts_mic_md5context, secret, strlen(secret));
  } else {
    MD5Init(&cmts_mic_md5context);
  }

  AppendChainTo_CMTS_MIC(&DownFreqData, &cmts_mic_md5context);
  AppendChainTo_CMTS_MIC(&UpChanIdData, &cmts_mic_md5context);
  AppendChainTo_CMTS_MIC(&NetworkAccessData, &cmts_mic_md5context);
  AppendChainTo_CMTS_MIC(&COSData, &cmts_mic_md5context);
  AppendChainTo_CMTS_MIC(&BPIData, &cmts_mic_md5context);
  AppendChainTo_CMTS_MIC(&VendorSpecificInfoData, &cmts_mic_md5context);
  AppendChainTo_CMTS_MIC(&CM_MIC_Data, &cmts_mic_md5context);
  AppendChainTo_CMTS_MIC(&MaxNumCpeData, &cmts_mic_md5context);

  if (!Use_HMAC_MD5) {
    MD5Update(&cmts_mic_md5context, secret, strlen(secret));
  }

  if (Use_HMAC_MD5) {
    HMAC_MD5Final(cmts_mic, &cmts_mic_md5context);
  } else {
    MD5Final(cmts_mic, &cmts_mic_md5context);
  }
  
  /* write to output */
  WriteByte(7); /* type */
  WriteByte(16);
  WriteString(cmts_mic, 16);
}

/* }}} */
/* {{{ HelpMessage() */

void HelpMessage(const char *program) {
  fprintf(stderr, "\nThis is the GNU DOCSIS configuration file assembler version 1.2\n\n");
  fprintf(stderr, "Usage:  %s [options] input-file output-file\n\n", program);
  fprintf(stderr, "Output format: DOCSIS-1.0 configuration files\n\n");
  fprintf(stderr, "Options:\n");
  fprintf(stderr, "  -m          encode CMTS MIC with old MD5 algorithm (default)\n");
  fprintf(stderr, "  -n          encode CMTS MIC with new HMAC-MD5 algorithm\n");
  fprintf(stderr, "  -s secret   authentication string (shared secret between server and CMTS)\n");
  fprintf(stderr, "  -h          Print this message\n");
  fprintf(stderr, "  -i num      Generate illegal configuration file for ATP testing.\n");
  fprintf(stderr, "              Valid numbers are:\n");
  fprintf(stderr, "                1: No End-Of-Data marker\n");
  fprintf(stderr, "                2: Invalid CM MIC\n");
  fprintf(stderr, "  -e          Print syntax example\n\n");
  fprintf(stderr, "  -l          debug lexical analyser\n");
  fprintf(stderr, "  -p          debug parser\n\n");
  fprintf(stderr, "Report bugs to Dietmar.Petras@ELSA.de\n\n");
  fprintf(stderr, "%s version 1.2, Copyright (C) 2000 by Dietmar Petras\n\n", program);
  fprintf(stderr, "%s comes with ABSOLUTELY NO WARRANTY; This is free software,\n", program);
  fprintf(stderr, "and you are welcome to redistribute it under certain conditions; read the\n");
  fprintf(stderr, "COPYING file from the distribution.\n\n");
}

/* }}} */
/* {{{ SyntaxExample() */

void SyntaxExample() {
  printf("%% Example for syntax of GNU DOCSIS configuration file assembler\n");
  printf("%%\n");
  printf("down_freq        = 501000000;\n");
  printf("up_chan_id       = 3;\n");
  printf("network_access   = on;\n");
  printf("vendor_id        = 0x3f 0x3f 0x3f;\n");
  printf("cpe_mac_addr     = 00:a0:57:00:aa:00;\n");
  printf("max_num_cpe      = 10;\n");
  printf("upgrade_filename = \"new_firmware.img\";\n");
  printf("upgrade_server   = 137.226.4.1;\n");
  printf("%%\n");
  printf("cos {\n");
  printf("   class_id       = 1;\n");
  printf("   max_down_rate  = 10000000;\n");
  printf("   max_up_rate    = 10000000;\n");
  printf("   up_chan_prio   = 7;\n");
  printf("   min_up_rate    = 0;\n");
  printf("   max_burst_size = 1518;\n");
  printf("   cos_bpi_enable = enabled;\n");
  printf("}\n");
  printf("%%\n");
  printf("cos {\n");
  printf("   class_id       = 2;\n");
  printf("   max_down_rate  = 100000;\n");
  printf("   max_up_rate    = 10000;\n");
  printf("   up_chan_prio   = 3;\n");
  printf("   min_up_rate    = 1000;\n");
  printf("   max_burst_size = 1518;\n");
  printf("   cos_bpi_enable = disabled;\n");
  printf("}\n");
  printf("%%\n");
  printf("modem_cap {\n");
  printf("   concatenation  = off;\n");
  printf("}\n");
  printf("%%\n");
  printf("bpi {\n");
  printf("   authorize_wait_timeout        = 10;\n");
  printf("   reauthorize_wait_timeout      = 10;\n");
  printf("   authorization_grace_time      = 60;\n");
  printf("   operational_wait_timeout      =  5;\n");
  printf("   rekey_wait_timeout            =  5;\n");
  printf("   tek_grace_time                = 60;\n");
  printf("   authorize_reject_wait_timeout = 60;\n");
  printf("}\n");
  printf("%%\n");
  printf("snmp_mib_object 1.3.6.1.1 = int 12345678;\n");
  printf("snmp_mib_object 1.3.6.1.2 = string \"public\";\n");
  printf("snmp_mib_object 1.3.6.1.3 = string 1 127 255;\n");
  printf("snmp_mib_object 1.3.6.1.3 = string 0x3f 0x3f 0x3f;\n");
  printf("snmp_mib_object 1.3.6.1.4 = null;\n");
  printf("snmp_mib_object 1.3.6.1.5 = object_id 1.3.6.1.6;\n");
  printf("snmp_mib_object 1.3.6.1.6 = ipaddress 0.0.0.0;\n");
  printf("%%\n");
  printf("vendor_specific_info {\n");
  printf("   vendor_id = 0x63 0x63 0x63;\n");
  printf("}\n");
}

/* }}} */
/* {{{ main() */

int main(int argc, char **argv) {
  int c;
  const char *cmts_mic_secret = "";

  /* default is no debugging of lexical analyser and parser */
  yy_flex_debug=0;
  yydebug=0;

  opterr = 0; /* getopt does not report errors */
  
  while ((c=getopt(argc, argv, "mns:lpehi:"))!=-1) {
    switch (c) {
     case 'n':
      Use_HMAC_MD5 = 1;
      break;
     case 'm':
      Use_HMAC_MD5 = 0;
      break;
     case 's':
      cmts_mic_secret = optarg;
      break;
     case 'l':
      yy_flex_debug=1;
      break;
     case 'p':
      yydebug=1;
      break;
     case 'e':
      SyntaxExample();
      exit(0);
      break;
     case 'i':
      {
	const char *nptr=optarg;
	char *endptr=NULL;
	long value = strtol(nptr, &endptr, 10);
	if (nptr[0]=='\0' || endptr[0]!='\0') {
	  fprintf(stderr, "no numeric value in `-i' option\n");
	  exit(0);
	}
	switch (value) {
	 case 1:
	  ATP_IllegalConfig_NoEOD = 1;
	  break;
	 case 2:
	  ATP_IllegalConfig_InvalidCM_MIC = 1;
	  break;
	 default:
	  fprintf(stderr, "illegal numeric value in `-i' option, valid range : [1..2]\n");
	  exit(0);
	}
      }
      break;
     case 'h':
      HelpMessage(argv[0]);
      exit(0);
      break;
     case '?': /* unknown option character */
      fprintf(stderr, "%s: unknown option `-%c'\n", argv[0], optopt);
      fprintf(stderr, "Use `%s -h' for a complete list of options.\n", argv[0]);
      exit(-1);
      break;
     default:
      abort();
      break;
    }
  }

  if (argc-optind!=2) {
    fprintf(stderr, "usage: %s [options] input-file output-file\n", argv[0]);
    fprintf(stderr, "Use `%s -h' for a complete list of options.\n", argv[0]);
    exit(-1);
  }

  YYSourceName = argv[optind++];
  yyin=fopen(YYSourceName, "rb");
  if (!yyin) {
    fprintf(stderr, "Error: Cannot open file %s, %s!\n", YYSourceName, strerror(errno));
    exit(-1);
  }
  yyparse();
  fclose(yyin);

  Append_CM_MIC();
  Append_CMTS_MIC(cmts_mic_secret);

  OutputAndClose(argv[optind++]);
  return 0;
}

/* }}} */


/* {{{ Local variables */

/*
  Local variables:
  folded-file: t
  c-basic-offset: 2
  End:
*/

/* }}} */
