/* ***************************************************************** *
 * Copyright 1998 International Business Machines Corporation. All   *
 * Rights Reserved.                                                  *
 *                                                                   *
 * Please read this carefully.  Your use of this reference           *
 * implementation of certain of the IETF public-key infrastructure   *
 * specifications ("Software") indicates your acceptance of the      *
 * following.  If you do not agree to the following, do not install  *
 * or use any of the Software.                                       *
 *                                                                   *
 * Permission to use, reproduce, distribute and create derivative    *
 * works from the Software ("Software Derivative Works"), and to     *
 * distribute such Software Derivative Works is hereby granted to    *
 * you by International Business Machines Corporation ("IBM").  This *
 * permission includes a license under the patents of IBM that are   *
 * necessarily infringed by your use of the Software as provided by  *
 * IBM.                                                              *
 *                                                                   *
 * IBM licenses the Software to you on an "AS IS" basis, without     *
 * warranty of any kind.  IBM HEREBY EXPRESSLY DISCLAIMS ALL         *
 * WARRANTIES OR CONDITIONS, EITHER EXPRESS OR IMPLIED, INCLUDING,   *
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OR CONDITIONS OF       *
 * MERCHANTABILITY, NON INFRINGEMENT AND FITNESS FOR A PARTICULAR    *
 * PURPOSE.  You are solely responsible for determining the          *
 * appropriateness of using this Software and assume all risks       *
 * associated with the use of this Software, including but not       *
 * limited to the risks of program errors, damage to or loss of      *
 * data, programs or equipment, and unavailability or interruption   *
 * of operations.                                                    *
 *                                                                   *
 * IBM WILL NOT BE LIABLE FOR ANY DIRECT DAMAGES OR FOR ANY SPECIAL, *
 * INCIDENTAL, OR  INDIRECT DAMAGES OR FOR ANY ECONOMIC              *
 * CONSEQUENTIAL DAMAGES (INCLUDING LOST PROFITS OR SAVINGS), EVEN   *
 * IF IBM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IBM  *
 * will not be liable for the loss of, or damage to, your records or *
 * data, or any damages claimed by you based on a third party claim. *
 *                                                                   *
 * IBM wishes to obtain your feedback to assist in improving the     *
 * Software.  You grant IBM a world-wide, royalty-free right to use, *
 * copy, distribute, sublicense and prepare derivative works based   *
 * upon any feedback, including materials, error corrections,        *
 * Software Derivatives, enhancements, suggestions and the like that *
 * you provide to IBM relating to the Software (this does not        *
 * include products for which you charge a royalty and distribute to *
 * IBM under other terms and conditions).                            *
 *                                                                   *
 * You agree to distribute the Software and any Software Derivatives *
 * under a license agreement that: 1) is sufficient to notify all    *
 * licensees of the Software and Software Derivatives that IBM       *
 * assumes no liability for any claim that may arise regarding the   *
 * Software or Software Derivatives, and 2) that disclaims all       *
 * warranties, both express and implied, from IBM regarding the      *
 * Software and Software Derivatives.  (If you include this          *
 * Agreement with any distribution of the Software or Software       *
 * Derivatives you will have met this requirement.)  You agree that  *
 * you will not delete any copyright notices in the Software.        *
 *                                                                   *
 * This Agreement is the exclusive statement of your rights in the   *
 * Software as provided by IBM.   Except for the rights granted to   *
 * you in the second paragraph above, You are not granted any other  *
 * patent rights, including but not limited to the right to make     *
 * combinations of the Software with products that infringe IBM      *
 * patents. You agree to comply with all applicable laws and         *
 * regulations, including all export and import laws and regulation. *
 * This Agreement is governed by the laws of the State of New York.  *
 * This Agreement supersedes all other communications,               *
 * understandings or agreements we may have had prior to this        *
 * Agreement.                                                        *
 * ***************************************************************** */

#include <icl.h>
#include <x509.h>
#include <slindex.h>
#include <ismmsg.h>
#include <pkiTask.h>

// The ICL file contains a series if IC records, each of
// which is an icl_header object, followed an ASN.1-encode 
// icl_data object.  The icl_header contains a field that
// gives the length of the ASN.1-encoded portion.

typedef struct {
  uint32 state;
  char comment[64];
  char issued[14];
  char last_change[14];  
  uint32 length;
} icl_header_t;


static slindex_t index_by_sno("BySerialNo");
static FILE * data_file = NULL;
static pki_mutex_t mutex = PKI_MUTEX_INIT;



static void encTime(const utcDateTime & t, char ts[14]) {
  char tst[15];
  sprintf(tst, 
          "%4.4d%2.2.d%2.2d%2.2d%2.2d%2.2d",
          t.year,
          t.month,
          t.day,
          t.hour,
          t.min,
          t.sec);
  memcpy(ts, tst, 14);
}

static void decTime(const char ts[14], utcDateTime & t) {
  sscanf(ts, 
         "%4.4d%2.2.d%2.2d%2.2d%2.2d%2.2d",
         &t.year,
         &t.month,
         &t.day,
         &t.hour,
         &t.min,
         &t.sec);
  t.msec = 1;
}


static uint32 int_to_sno_key(uint32 i, unsigned char key[4]) {
  uint32 status;
  asn_integer ai;
  buffer_t keybuf;

  if (status = ai.set_value(i)) return status;
  if (status = ai.write(keybuf)) return status;
  if (keybuf.data_len > 4) 
  memcpy(key, keybuf.data+keybuf.data_len-4, 4);
  else if (keybuf.data_len == 4)
  memcpy(key, keybuf.data, 4);
  else {
    memset(key, 0, 4);
    memcpy(key+4-keybuf.data_len, keybuf.data, keybuf.data_len);
  };
  return 0;
}

static uint32 cert_to_sno_key(const x509_certificate & cert, unsigned char key[4]) {
  uint32 status;
  buffer_t keybuf;

  if (status = cert.tbsCertificate.serialNumber.write(keybuf)) return status;
  if (keybuf.data_len > 4) 
  memcpy(key, keybuf.data+keybuf.data_len-4, 4);
  else if (keybuf.data_len == 4)
  memcpy(key, keybuf.data, 4);
  else {
    memset(key, 0, 4);
    memcpy(key+4-keybuf.data_len, keybuf.data, keybuf.data_len);
  };
  return 0;
}

uint32 Jnh_ICL_Init(const char * file) {
  uint32 status;
  char sno_index_file_name[128];
  char data_file_name[128];
  
  strncpy(sno_index_file_name, file, sizeof(sno_index_file_name));
  strncpy(data_file_name, file, sizeof(data_file_name));

  strcat(data_file_name, ".JCL");
  strcat(sno_index_file_name, ".JCX");

  pkiCreateMutex(&mutex, true);


  if (status = index_by_sno.set_file(sno_index_file_name)) goto end;
  if (status = index_by_sno.define_index(4,
                                         data_file_name,
                                         true)) goto end;
  if (status = index_by_sno.open()) goto end;
  if ((data_file = fopen(data_file_name, "r+b")) == NULL) {
    data_file = fopen(data_file_name, "w+b");
  };
  if (data_file == NULL) {
    index_by_sno.close();
    status = ISM_FILE_ERROR;
    goto end;
  };

end:
  pkiUnlockMutex(&mutex);
  return status;

}

uint32 Jnh_ICL_Add(const x509_certificate * cert,
                   const char * comment) {

  buffer_t buf;
  buffer_t keybuf;
  uint32 status;
  uint32 index;
  long offset;
  unsigned char key[4];
  icl_header_t icl_header;
  utcDateTime utc;
  time_t nowtime;
  struct tm now;

  if (status = cert->write(buf)) return status;
  buf.clear();

  if (status = cert_to_sno_key(*cert, key)) return status;

  pkiLockMutex(&mutex);
  fseek(data_file, 0L, SEEK_END);
  offset = ftell(data_file);

  icl_header.length = htonl(buf.data_len);
  icl_header.state = htonl(ICL_ST_ISSUED);
  nowtime = time(NULL);
  memcpy(&now, gmtime(&nowtime), sizeof(struct tm));
  utc.year = now.tm_year + 1900;
  utc.month = now.tm_year + 1;
  utc.day = now.tm_mday;
  utc.hour = now.tm_hour;
  utc.min = now.tm_min;
  utc.sec = now.tm_sec;
  utc.msec = 1;
  encTime(utc, icl_header.last_change);
  encTime(utc, icl_header.issued);
  strncpy(icl_header.comment, comment, sizeof(icl_header.comment));
  
  fwrite(&icl_header, sizeof(icl_header), 1, data_file);
  fwrite(buf.data, buf.data_len, 1, data_file);
  if (status = index_by_sno.insert(key, offset)) goto end;

end:
  pkiUnlockMutex(&mutex);
  return status;
}

// The two flavors of lookup() both return an ICLContext object, which 
// places a lock on the ICL until it is released (via a call to 
// Jnh_ICL_ReleaseContext).  The lock may be on just the specific record
// or the entire ICL - therefore you cannot have more than one ICL
// operation outstanding at any time.
uint32 Jnh_ICL_Lookup(const x509_certificate * cert,
                      ICLContext & context,
                      boolean modify = false) {

  uint32 status = 0;
  unsigned char key[4];
  uint32 result;

  if (status = cert_to_sno_key(*cert, key)) return status;
  
  pkiLockMutex(&mutex);

  status = index_by_sno.lookup(key, result);
  context = result;

end:
  pkiUnlockMutex(&mutex);
  return status;
}

uint32 Jnh_ICL_Lookup(const asn_x500name * issuer,
                      uint32 serialNumber,
                      ICLContext & context,
                      boolean modify = false) {

  uint32 status = 0;
  unsigned char key[4];
  uint32 result;

  if (status = int_to_sno_key(serialNumber, key)) return status;
  
  pkiLockMutex(&mutex);

  status = index_by_sno.lookup(key, result);
  context = result;

end:
  pkiUnlockMutex(&mutex);
  return status;
}

uint32 Jnh_ICL_GetCert(ICLContext & context,
                       x509_certificate & cert) {

  uint32 status;
  buffer_t buf;
  icl_header_t icl_header;

  pkiLockMutex(&mutex);
  if (fseek(data_file, context, SEEK_SET) < 0) {
    status = ISM_RECORD_NOT_FOUND;
    goto end;
  };
  fread(&icl_header, sizeof(icl_header), 1, data_file);
  icl_header.length = ntohl(icl_header.length);
  buf.extend(icl_header.length);  

  fread(buf.data, icl_header.length, 1, data_file);
  buf.data_len = icl_header.length;
  if (status = cert.read(buf)) goto end;
    
  
end:
  pkiUnlockMutex(&mutex);
  return status;
}

uint32 Jnh_ICL_GetState(ICLContext & context,
                        uint32 & state,
                        char ** comment) {
  uint32 status = 0;
  buffer_t buf;
  icl_header_t icl_header;

  pkiLockMutex(&mutex);
  if (fseek(data_file, context, SEEK_SET) < 0) {
    status = ISM_RECORD_NOT_FOUND;
    goto end;
  };


  fread(&icl_header, sizeof(icl_header), 1, data_file);
  icl_header.state = ntohl(icl_header.state);

  state = icl_header.state;
  if ((*comment = (char *)malloc(65)) == NULL) {
    status = ISM_NOMEM;
    goto end;
  };
  strncpy(*comment, icl_header.comment, 64);
  (*comment)[64] = 0;

end:
  pkiUnlockMutex(&mutex);
  return status;
}

uint32 Jnh_ICL_FreeCommentString(char * comment) {
  free(comment);
  return 0;
}

uint32 Jnh_ICL_GetIssuedTime(ICLContext & context,
                             utcDateTime & issued_time) {
  uint32 status = 0;
  long length;
  buffer_t buf;
  icl_header_t icl_header;


  pkiLockMutex(&mutex);
  if (fseek(data_file, context, SEEK_SET) < 0) {
    status = ISM_RECORD_NOT_FOUND;
    goto end;
  };
  fread(&icl_header, sizeof(icl_header), 1, data_file);
  decTime(icl_header.issued, issued_time);

end:
  pkiUnlockMutex(&mutex);
  return status;

}

uint32 Jnh_ICL_GetLastTime(ICLContext & context,
                           utcDateTime & lastChange_time) {
  uint32 status = 0;
  long length;
  buffer_t buf;
  icl_header_t icl_header;


  pkiLockMutex(&mutex);
  if (fseek(data_file, context, SEEK_SET) < 0) {
    status = ISM_RECORD_NOT_FOUND;
    goto end;
  };
  fread(&icl_header, sizeof(icl_header), 1, data_file);
  decTime(icl_header.last_change, lastChange_time);

end:
  pkiUnlockMutex(&mutex);
  return status;


}


uint32 Jnh_ICL_SetState(ICLContext & context,
                        uint32 state,
                        const char * comment) {
  uint32 status = 0;
  icl_header_t icl_header;
  buffer_t buf;

  pkiLockMutex(&mutex);
  if (fseek(data_file, context, SEEK_SET) < 0) {
    status = ISM_RECORD_NOT_FOUND;
    goto end;
  };
  fread(&icl_header, sizeof(icl_header), 1, data_file);
  icl_header.state = htonl(state);
  strncpy(icl_header.comment, comment, sizeof(icl_header.comment));

  fseek(data_file, context, SEEK_SET);
  fwrite(&icl_header, sizeof(icl_header), 1, data_file);

end:
  pkiUnlockMutex(&mutex);
  return status;

}

uint32 Jnh_ICL_ReleaseContext(ICLContext & context) {
  return 0;
}

