/* ***************************************************************** *
 * 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 <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Jonah.h>
#include <transports.h>
#include <tid.h>
#include <PreReg.h>
#include <ObjectStore.h>
#include <ObjectDefs.h>
#include <JonahOst.h>
#include <JonahIni.h>
#include <sections.h>
#include <ApiNotify.h>
#include <jnhTimer.h>
#include <messaging.h>
#include <winsock2.h>
#include <apimsg.h>


static uint32
ServerActive(void)
{
  if (IniAmIRA()) {
    return ObjStRACertReqActive;
  } else if (IniAmICA()) {
    return ObjStCACertReqActive;
  } else {
    return API_WRONG_SERVER_TYPE;
  }
}

static uint32
ServerGranted()
{
  if (IniAmIRA()) {
    return ObjStRACertCAApproved;
  } else {
    return ObjStEECertIssued;
  }
}

static uint32
ServerRejected()
{
  if (IniAmIRA()) {
    return ObjStRACertReqRejecting;
  } else if (IniAmICA()) {
    return ObjStCACertReqRejected;
  } else {
    return ObjStEECertReqRejected;
  }
}

uint32
generate_pkix_msg(const ObjStoreData *objPtr, unsigned char **message, 
                  uint32 *len)
{
  buffer_t              bodyBuffer;
  PKIMessage            msg;
  uint32                tidLen;
  uint32                status;
  buffer_t              myNameBuf;
  buffer_t              recipient;
  buffer_t              messageBuffer;
  r_buffer_t            mbuf;
  unsigned char         *tid;
  unsigned char         *tmsg;
 
  do {
    objPtr->transactionID.value.get_value(tid, tidLen);
    if (objPtr->recipient.value.selected() != 2) {
      status = API_UNSUPPORTED_NAME_TYPE;
      break;
    } else {
      objPtr->recipient.value.directoryname.get_value_UTF8(recipient);
    }
    switch(objPtr->msg.value.selected()) {
    case OBJ_MSG_PREREG:        // PREREG
      status = API_UNSUPPORTED_MESSAGE_TYPE;
    case OBJ_MSG_IREQ:  // Initial cert request
      if ((status = objPtr->msg.value.ireq.value.write(bodyBuffer))) {
        break;
      }
      msg.body.select(0);
      if ((status = msg.body.ir.value.read(bodyBuffer))) {
        break;
      }
      break;
    case OBJ_MSG_CERTREQ:
      if ((status = objPtr->msg.value.certreq.value.write(bodyBuffer))) {
        break;
      }
      msg.body.select(2);
      if ((status = msg.body.ir.value.read(bodyBuffer))) {
        break;
      }
      break;
    case OBJ_MSG_IREP:
      if ((status = objPtr->msg.value.irep.value.write(bodyBuffer))) {
        break;
      }
      msg.body.select(1);
      if ((status = msg.body.ip.value.read(bodyBuffer))) {
        break;
      }
      break;
    case OBJ_MSG_CERTREP:
      if ((status = objPtr->msg.value.certrep.value.write(bodyBuffer))) {
        break;
      }
      msg.body.select(3);
      if ((status = msg.body.ip.value.read(bodyBuffer))) {
        break;
      }
      break;
    case OBJ_MSG_REVREQ:
    case OBJ_MSG_REVREP:
      status = API_UNSUPPORTED_MESSAGE_TYPE;
      break;
    default:
      // XXX no message can be created with this message type
      status = API_UNKNOWN_MESSAGE_TYPE;
    }
    if (status) break;
    if ((status = msg.header.pvno.set_value(PKIX_VERSION1))) break;
    if (objPtr->sender.value.is_present()) {
      if ((status = objPtr->sender.value.directoryname.
           get_value_UTF8(myNameBuf))) {
        break;
      }
    } else {
      char *MyName = NULL;
      if ((MyName = (char *) malloc(BUFSIZ)) == NULL) {
        throw "Out of memory in RA_preregister_user";
      }
      if (IniReadString(GEN_SECTION, GEN_NAME, (char *)  MyName, 
                                 BUFSIZ) == FALSE) {
        status = API_WHO_AM_I;
        break;
      }
      myNameBuf.clear();
      myNameBuf.append((unsigned char *) MyName, strlen(MyName));
    }
    if ((status = msg.header.sender.select(2))) break;
    if ((status = msg.header.sender.directoryname.set_value_UTF8(myNameBuf))) {
      break;
    }
    if ((status = msg.header.recipient.select(2))) break;
    if ((status = msg.header.recipient.directoryname.
         set_value_UTF8(recipient))) {
      break;
    }
    if ((status = msg.header.transactionID.value.set_value(tid, tidLen))) {
      break;
    }
    if ((status = msg.write(messageBuffer))) {
      break;
    }
    if ((tmsg = (unsigned char *) malloc(messageBuffer.data_len)) == NULL) {
      throw "Out of memory in generate_pkix_message";
    }
    memcpy(tmsg, messageBuffer.data, messageBuffer.data_len);
    *message = tmsg;
    *len = messageBuffer.data_len;
  } while(0);
  return status;
}

uint32
generate_protection(const PKIMessage *pmsgPtr, const ObjStoreData *objsPtr, 
                    uint32 objsid, PKIProtection &prot)
{
  return 0;
}


uint32
process_pkix_msg(const PKIMessage *pmsgPtr, ObjStoreData *objsPtr, 
                 uint32 objsid)
{
  uint32                status;
  long                  pvers;
  uint32                objClass;
  uint32                objsTIDtime;
  uint32                msgTIDtime;
  uint32                type;
  uint32                gplen;
  uint32                mplen;
  unsigned char         *me = NULL;
  unsigned char         *msgTID;
  uint32                msgTIDlen;
  unsigned char         *objsTID;
  uint32                objsTIDlen;
  unsigned char         *gprot = NULL;
  unsigned char         *mprot = NULL;
  asn_x500name          tname;
  PKIProtection         prot;
  r_buffer_t            tbuf;
  buffer_t              msgBody;
  pkistatus_t           cstatus;

  do {
    if ((status = pmsgPtr->header.pvno.get_value(pvers))) {
      break;
    }
    if (pvers != PKIX_VERSION1) {
      status = API_UNSUPPORTED_PKIX_VERSION;
      break;
    }
    if ((status = JnhGetObjectFlags(objsid, &objClass))) {
      break;
    }
    if (objClass && !(objClass & ObjClStSurrogate)) { 
      status = API_OBJECT_IS_SURROGATE;
      break;
    }
    if (pmsgPtr->header.sender.selected() != 2) { // XXX x.500 names
      status = API_UNSUPPORTED_NAME_TYPE;
      break;
    }
    if(pmsgPtr->header.recipient.selected() != 2) { // XXX x.500 names
      status = API_UNSUPPORTED_NAME_TYPE;
      break;
    }
#ifdef RECIPIENT_CHECK
    if ((me = (unsigned char *) malloc(BUFSIZ)) == NULL) {
      throw "out of memory in process_pkix";
    }
    if ((IniReadString(GEN_SECTION, GEN_NAME, (char *) me, 
                       BUFSIZ)) == false) {
      status = API_WHO_AM_I;
      break;
    }
    tbuf.data = me;
    tbuf.data_len = strlen((char *) me);
    if ((status = tname.set_value_UTF8(tbuf))) {
      break;
    }
    if (tname != pmsgPtr->header.recipient.directoryname) {
      status = API_NOT_RECIPIENT;
      break;
    }
#endif
    if (pmsgPtr->header.protectionAlg.is_present()) {
      ApiDisplay(DISPLAY_LOGDEBUG,  (unsigned char *) 
                 "PKIX message is protected");
      if (!pmsgPtr->protection.value.is_present()) {
        status = API_NO_PROTECTION;
        break;
      }
      if ((status = generate_protection(pmsgPtr, objsPtr, objsid, prot))) {
        break;
      }
      if ((status = prot.get_value(gprot, gplen))) {
        break;
      }
      if ((status = pmsgPtr->protection.value.get_value(mprot, mplen))) {
        break;
      }
      if (mplen != gplen || memcmp(mprot, gprot, gplen)) {
        status = API_BAD_PROTECTION;
      }
    }
    if ((status = objsPtr->transactionID.value.get_value(objsTID, 
                                                         objsTIDlen))) {
      break;
    }
    if ((status = pmsgPtr->header.transactionID.value.
         get_value(msgTID, msgTIDlen))) {
      break;
    }
    if ((status = tid_get_value(objsTID, TID_TIME, &objsTIDtime))) {
      break;
    }
    if ((status = tid_get_value(msgTID, TID_TIME, &msgTIDtime))) {
      break;
    }
    if (objsTIDtime != msgTIDtime) {
      status = API_TRANSACTIONID_MISMATCH;
      break;
    }
    if ((status = objsPtr->transactionID.value.
         set_value(msgTID, msgTIDlen))) {
      break;
    }
    type = pmsgPtr->body.selected();
    switch(type) {
    case 0:     // IR
      if ((status = pmsgPtr->body.ir.value.write(msgBody))) 
        break;
      objsPtr->msg.value.select(OBJ_MSG_IREQ);
      if ((status = objsPtr->msg.value.ireq.value.read(msgBody)))
        break;
      objClass = (ObjClStActive | ObjClTypeCert | ServerActive());
      break;
    case 1:     // IP
      if ((status = pmsgPtr->body.ip.value.write(msgBody))) 
        break;
      if ((status = objsPtr->msg.value.irep.value.read(msgBody))) 
        break;
      objsPtr->msg.value.select(OBJ_MSG_IREP);
      if ((status = pmsgPtr->body.ip.value.response[0]->
           status.status.get_value(cstatus))) {
        break;
      }
      if (cstatus == GRANTED || cstatus == GRANTED_WITH_MODS) {
        objClass = (ObjClStActive | ObjClTypeCert | ObjClStActive |
                    ServerGranted());
      } else {
        objClass = (ObjClStActive | ObjClTypeCert | ObjClStActive |
                    ServerRejected());
      }
      break;
    case 2:     // CR
      if ((status = pmsgPtr->body.cr.value.write(msgBody)))
        break;
      if ((status = objsPtr->msg.value.certreq.value.read(msgBody)))
        break;
      objsPtr->msg.value.select(OBJ_MSG_CERTREQ);
      objClass = (ObjClStActive | ObjClTypeCert | ServerActive());
      break;
    case 3:     // CP
      if ((status = pmsgPtr->body.cp.value.write(msgBody)))
        break;
      if ((status = objsPtr->msg.value.certrep.value.read(msgBody)))
        break;
      objsPtr->msg.value.select(OBJ_MSG_CERTREP);
      if ((status = pmsgPtr->body.cp.value.response[0]->
           status.status.get_value(cstatus))) {
        break;
      }
      if (cstatus == GRANTED || cstatus == GRANTED_WITH_MODS) {
        objClass = (ObjClStActive | ObjClTypeCert | ServerGranted());
      } else {
        objClass = (ObjClStActive | ObjClTypeCert | ServerRejected());
      }
      break;
    default:
      status = API_UNSUPPORTED_MESSAGE_TYPE;
      break;
    }
    if (status) break;
  } while(0);
  if (status) {
    objClass = (objClass | ObjFlagError);
  }
  JnhSetObjectFlags(objsid, objClass);
  return status;
}

uint32
send_message(unsigned char *message, uint32 len, uint32 type, 
             unsigned char *url, uint32 *reference, uint32 *time, 
             unsigned char **reply, uint32 *replyLen)
{
  JnhTcpSender  sender;
  ipAddressV4   target;
  int           named;
  char          *scheme = NULL;
  char          *host = NULL;
  int           port;
  char          *tp = NULL;;
  unsigned char *buf = NULL;
  unsigned char *resp = NULL;
  unsigned char *trespPtr = NULL;
  uint32        respLen;
  unsigned char i1, i2, i3, i4;
  uint32        status;
  uint32        tmp;

  do {  
    tp = strdup((char *) url);
    scheme = tp;
    if ((tp = strchr(tp, ':')) == NULL) {
      status = API_INVALID_URL;
      break;
    }
    *tp++ = NULL;
    host = tp+2;
    if ((tp = strchr(tp, ':')) == NULL) {
      IniReadInteger(TRANS_SECTION, TRANS_PORT, port, TRANS_PORTDEF);
    } else {
      *tp++ = NULL;
      port = atoi(tp);
      if (port == 0) {
        status =  API_INVALID_TCPPORT;
      }
    }
    if (sscanf(host, "%d.%d.%d.%d", &i1, &i2, &i3, &i4) != 4) {
      named = TRUE;
    }
    if (named) {
      status = target.setAddress(host);
    } else {
      status = target.setAddress(i1, i2, i3, i4);
    }
    if (status) {
      break;
    }
    if ((buf = (unsigned char *) malloc(len + 1)) == NULL) {
      throw "Out of memory in send_message";
    }
    *buf++ = (unsigned char) type;
    memcpy(buf--, message, len);
    
    if ((resp = (unsigned char *) malloc(BUFSIZ*4)) == NULL) {
      throw "Out of memory in send_message";
    }
    trespPtr = resp;
    if ((status = sender.send(buf, len + 1, resp, BUFSIZ*4, &respLen, 
                            target, port))) {
      break;
    }
    switch(*resp++) {
    case POLL_REP:
      memcpy((void *) &tmp, resp, 4);
      *reference = ntohl(tmp);
      memcpy((void *) &tmp, (resp + 4), 4);
      *time = ntohl(tmp);
      *reply = NULL;
      *replyLen = 0;
      break;
    case FINAL_MSG_REP:
      if ((*reply = (unsigned char *) malloc(respLen)) == NULL) {
        throw "Out of memory in send_message.";
      }
      memcpy(*reply, resp, (respLen - 1));
      *time = 0;
      *reference = 0;
      *replyLen = respLen - 1;
      break;
    case ERROR_MSG_REP:
      memcpy((void *) &tmp, resp, 4);
      status = ntohl(tmp);
      break;
    default:
      status = API_UNKNOWN_MESSAGE_TYPE;
    }
  } while(0);
  if (trespPtr) free(trespPtr);
  if (buf)  free(buf);
  if (scheme) free(scheme);
  return status;
}
 
void 
poll(void *id)
{
  uint32        status;
  uint32        messageLen; 
  uint32        urlLen;
  uint32        pollTime;
  uint32        pollReference = 0;
  int           pollInterval;
  uint32        eventId;
  uint32        objsid = (uint32) id;
  uint32        objClass;
  uint32        origObjClass;
  time_t        now;
  long          pref = 0;
  long          ecnt = 0;
  uint32        tmpref;
  int           maxError;
  ObjStoreData  *objsPtr;
  unsigned char *message;
  unsigned char *url;
  unsigned char *reply;
  char          dispmsg[BUFSIZ];
  uint32        replyLen;
  r_buffer_t    pbuf;
  PKIMessage    pmsg;

  sprintf(dispmsg, "Polling server for objsid %d", objsid);
  ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) dispmsg);
  do {

    if ((status = JnhLockObject(objsid))) {
      sprintf(dispmsg, "poll for %d failed -- LockObject FAILED %d", objsid, 
              status);
      ApiDisplay(DISPLAY_LOGERROR, (utf8String) dispmsg);
      break;
    }
    if ((status = JnhGetObjectModify(objsid, &objClass, &objsPtr))) {
      sprintf(dispmsg, "OBJSID = %d -- GetObjectModify FAILED %d", objsid,
              status);
      ApiDisplay(DISPLAY_LOGERROR, (utf8String) dispmsg);
      break;
    }
    IniReadInteger(TRANS_SECTION, TRANS_MAXERROR, maxError, 
                   TRANS_MAXERRORDEF);
    IniReadInteger(TRANS_SECTION, TRANS_RETRY, pollInterval, TRANS_RETRYDEF);
    origObjClass = objClass;
    messageLen = sizeof(tmpref);
    if ((message = (unsigned char *)malloc(messageLen)) == NULL) {
      throw "Out of memory in poll";
    }
    if ((status = objsPtr->pollReference.value.get_value(pref))) {
      ecnt++;
      break;
    }
    if ((objsPtr->pollErrorCount.value.is_present())) {
      if ((status = objsPtr->pollErrorCount.value.get_value(ecnt))) {
        break;
      }
    } else {
      ecnt = 0;
    }
    sprintf(dispmsg, "Polling for object %d refernce is %d", objsid, pref);
    ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) dispmsg);
    tmpref = htonl(pref);
    memcpy(message, &tmpref, messageLen);
    time(&now);
    if ((status = objsPtr->lastPoll.value.set_value(now))) {
      ecnt++;
      break;
    }
    if ((objsPtr->recipientURL.value.get_value(url, urlLen))) {
      ecnt++;
      break;
    }
    ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) "Calling send_message");
    if ((status = send_message(message, messageLen, POLL_REQ, url, 
                               &pollReference, &pollTime, &reply, 
                               &replyLen))) {
      sprintf(dispmsg, "OBJSID = %d -- send_message FAILED %d", objsid,
              status);
      ApiDisplay(DISPLAY_LOGERROR, (utf8String) dispmsg);
      objsPtr->pollError.value.set_value(status);
      ecnt++;
      break;
    }
    ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) "send_message returned");
    if (replyLen) {
      sprintf(dispmsg, "poll got response for %d", objsid);
      ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) dispmsg);       // Debug
      pbuf.data = reply;
      pbuf.data_len = replyLen;
      if ((status = pmsg.read(pbuf))) {
        break;
      }
      if ((status = process_pkix_msg(&pmsg, objsPtr, objsid))) {
        sprintf(dispmsg, "OBJSID = %d -- accept_pkix_msg FAILED %d", objsid,
                status);
        ApiDisplay(DISPLAY_LOGERROR, (utf8String) dispmsg);
        objsPtr->pollError.value.set_value(status);
        ecnt++;
        break;
      }
      sprintf(dispmsg, "OBJSID = %d -- Resetting poll attrib", objsid);
      ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) dispmsg);
      objsPtr->pollReference.value.set_value(0);
      objsPtr->pollTime.value.set_value(0);
    } else {
      sprintf(dispmsg, "going to re-poll for %d (ref = %d) at %s", 
              objsid, pollReference, ctime((long *) &pollTime));
      ApiDisplay(DISPLAY_LOGDEBUG, (utf8String) dispmsg);
      objsPtr->pollTime.value.set_value(pollTime);
      objsPtr->pollReference.value.set_value(pollReference);
      if ((status = JnhRegisterEventAbs(pollTime, poll, (void *) 
                                        objsid, false, &eventId))) {
        sprintf(dispmsg, "OBJSID = %d -- JnhRegisterEventAbs FAILED %d", 
                objsid, status);
        ApiDisplay(DISPLAY_LOGERROR, (utf8String) dispmsg);
        objsPtr->pollError.value.set_value(status);
        ecnt++;
        break;
      }
    }
  } while(0);
  JnhGetObjectFlags(objsid, &objClass);
  if (status) {
    objClass = objClass | ObjFlagError;
    objsPtr->pollErrorCount.value.set_value(ecnt);
    if (ecnt < maxError) { 
      time(&now);
      pollTime = now + pollInterval;
      objsPtr->pollTime.value.set_value(pollTime);
      sprintf(dispmsg, 
              "OBJSID = %d -- Polling retry due to error next poll %s", 
              objsid, ctime((long *) &pollTime));
      ApiDisplay(DISPLAY_LOGERROR, (utf8String) dispmsg);
      if ((status = JnhRegisterEventAbs(pollTime, poll, (void *) 
                                        objsid, false, &eventId))) {
        objClass = objClass & ~ObjClFlagAll;
        objClass = objClass | ObjFlagFatal;
      }
    } else {
        sprintf(dispmsg, "OBJSID = %d -- MAX RETRY HIT ON POLL STATUS = %d", 
                objsid, status);
        ApiDisplay(DISPLAY_LOGERROR, (utf8String) dispmsg);
        objClass = objClass & ~ObjClFlagAll;
        objClass = objClass | ObjFlagFatal;
    }
  }
  JnhSetObjectFlags(objsid, objClass);
  if (origObjClass != objClass) {
    ApiNotify(objsid, objClass);
  }
  JnhSynchObject(objsid);
  return;
}
