-- File: RPCSecurity.mesa - last edit:
-- AOF                  8-Feb-88 17:04:13
-- HGM                  21-Jan-84 20:41:01
-- Andrew Birrell       September 7, 1982 1:47 pm
-- Copyright (C) 1984, 1988 by Xerox Corporation. All rights reserved. 

-- RPC: Authentication and security facilities


DIRECTORY
  DESFace,
  Heap USING [FreeNode, MakeNode, systemZone],
  Inline USING [LongCOPY, LowHalf],
  NameInfoDefs USING [AuthenticateKey, CheckStamp],
  PSB USING [PsbIndex, PsbNull],
  PupDefs USING [PupBuffer, ReturnBuffer, GetLocalPupAddress, PupRouterSendThis],
  PupTypes USING [PupAddress],
  MesaRPC USING [
    AuthenticateFailed, Conversation, ConversationLevel, EncryptionKey,
    maxPrincipalLength, Principal, SecurityLevel, unencrypted],
  RPCInternal USING [ConversationObject],
  MesaRPCLupine USING [
    DataLength, Dispatcher, GetStubPkt, Header, pktOverhead, RPCPkt, StubPkt],
  RPCPkt USING [
    CallCount, ConnectionID, ConversationID, DispatcherDetails, Header, Machine,
    PktConversationID, PktExchange, PktID, pktLengthOverhead, SetupResponse],
  RPCPrivate USING [rpcSocket],
  System USING [GetClockPulses, GetGreenwichMeanTime, SecondsSinceEpoch];

RPCSecurity: MONITOR
  IMPORTS
    DESFace, Heap, Inline, NameInfoDefs, PupDefs, MesaRPC, MesaRPCLupine, RPCPkt, System
  EXPORTS
    MesaRPC --encryption stuff-- , RPCInternal --GetAuthenticator-- , MesaRPCLupine
  SHARES MesaRPCLupine =

  BEGIN

  Header: PUBLIC TYPE = RPCPkt.Header;

  ConcreteHeader: PROC [abstract: LONG POINTER TO MesaRPCLupine.Header]
    RETURNS [LONG POINTER TO Header] = INLINE {RETURN[abstract]};

  CopyPrincipal: PROC [from: MesaRPC.Principal, to: LONG POINTER] = {
    Inline.LongCOPY[from: from, to: to, nwords: SIZE[StringBody [from.length]]]};

  AllocPrincipal: PROC [from: MesaRPC.Principal] RETURNS [to: MesaRPC.Principal] =
    BEGIN
    IF from = NIL THEN RETURN[NIL];
    to ← Heap.MakeNode[n: SIZE[StringBody [from.length]]];
    CopyPrincipal[from, to];
    END;

  CopyAuth: PROC [from: Authenticator, to: LONG POINTER] = {
    Inline.LongCOPY[from: BASE[from], to: to, nwords: LENGTH[from]]};

  AllocAuth: PROC [from: Authenticator] RETURNS [to: Authenticator] =
    BEGIN
    IF BASE[from] = NIL THEN RETURN[DESCRIPTOR[NIL, 0]];
    to ← DESCRIPTOR[Heap.MakeNode[n: LENGTH[from]], LENGTH[from]];
    CopyAuth[from, BASE[to]];
    END;


  -- ******** Conversation management ******** --

  ConversationID: PUBLIC TYPE = RPCPkt.ConversationID;

  ConversationObject: PUBLIC TYPE = RPCInternal.ConversationObject;

  Conversation: TYPE = LONG POINTER TO ConversationObject;

  lastConversation: ConversationID;

  firstConversation: PUBLIC RPCPkt.PktConversationID;

  GenerateConversation: PUBLIC PROC RETURNS [conversation: Conversation] = {
    RETURN[EntryGenerate[none, , NIL, NIL, DESFace.nullKey, DESCRIPTOR[NIL, 0]]]};

  EntryGenerate: ENTRY PROC [
    level: MesaRPC.SecurityLevel, iv: DESFace.IV,
    originator, responder: MesaRPC.Principal, convKey: DESFace.Key,
    auth: Authenticator] RETURNS [conversation: Conversation] =
    BEGIN
    lastConversation.count.ls ← lastConversation.count.ls + 1;
    IF lastConversation.count.ls = 0 THEN
      lastConversation.count.ms ← lastConversation.count.ms + 1;
    conversation ← AddConversation[
      lastConversation, level, iv, originator, responder, convKey, auth];
    END;

  Authenticator: TYPE = LONG DESCRIPTOR FOR ARRAY OF WORD;
  -- for A talking to B using key CK, contains:
  -- {KY}KB, spare, { {CK}KB, spare, time, A }KY .
  -- The keys are single cipher blocks.
  -- The rest is encrypted with CBC-check using a zero IV --

  AuthenticatorLayout: TYPE = MACHINE DEPENDENT RECORD [
    ky(0): DESFace.Key,
    kySpare(4): DESFace.Key,  -- space for larger keys! --
    ck(8): DESFace.Key,
    ckSpare(12): DESFace.Key,  -- space for larger keys! --
    time(16): LONG CARDINAL,
    a(18): StringBody];

  BadConversationLevel: ERROR = CODE;

  StartConversation: PUBLIC PROC [
    caller: MesaRPC.Principal, key: MesaRPC.EncryptionKey,
    callee: MesaRPC.Principal, level: MesaRPC.ConversationLevel]
    RETURNS [conversation: Conversation] =
    BEGIN
    authenticator: Authenticator;
    convKey: DESFace.Key;
    iv: DESFace.IV;
    IF level NOT IN MesaRPC.ConversationLevel THEN ERROR BadConversationLevel[];
    [authenticator, convKey, iv] ← Authenticate[caller, key, callee];
    RETURN[EntryGenerate[level, iv, caller, callee, convKey, authenticator]]
    END;

  Authenticate: PROC [
    caller: MesaRPC.Principal, key: MesaRPC.EncryptionKey,
    callee: MesaRPC.Principal]
    RETURNS [authenticator: Authenticator, convKey: DESFace.Key, iv: DESFace.IV] =
    BEGIN
    -- TEMP: This entire procedure should use a secure R-Server protocol! --
    nBlks: CARDINAL =
      (SIZE[AuthenticatorLayout] - SIZE[StringBody [0]] + SIZE[
         StringBody [caller.length]] + SIZE[DESFace.Block] - 1 --rounding--
        )/SIZE[DESFace.Block];
    authLength: CARDINAL = nBlks*SIZE[DESFace.Block];
    -- TEMP: copy into MDS for GrapevineUser's benefit --
    buff: STRING = [MesaRPC.maxPrincipalLength];
    IF caller.length > buff.maxlength THEN
      ERROR MesaRPC.AuthenticateFailed[badCaller];
    IF callee.length > buff.maxlength THEN
      ERROR MesaRPC.AuthenticateFailed[badCallee];
    Inline.LongCOPY[
      from: caller, to: buff, nwords: SIZE[StringBody [caller.length]]];
    SELECT NameInfoDefs.AuthenticateKey[buff, key] FROM
      group, notFound => ERROR MesaRPC.AuthenticateFailed[badCaller];
      allDown => ERROR MesaRPC.AuthenticateFailed[communications];
      badPwd => ERROR MesaRPC.AuthenticateFailed[badKey];
      ENDCASE => NULL;
    Inline.LongCOPY[
      from: callee, to: buff, nwords: SIZE[StringBody [callee.length]]];
    SELECT NameInfoDefs.CheckStamp[buff] FROM
      group, notFound => ERROR MesaRPC.AuthenticateFailed[badCallee];
      allDown => ERROR MesaRPC.AuthenticateFailed[communications];
      ENDCASE => NULL;
    iv ← DESFace.GetRandomIV[];
    authenticator ← DESCRIPTOR[Heap.MakeNode[n: authLength], authLength];
    BEGIN
    -- TEMP: construct authenticator in clear ourselves! --
    kb: DESFace.Key ← DESFace.nullKey -- !!! -- ;
    authRec: LONG POINTER TO AuthenticatorLayout = LOOPHOLE[BASE[authenticator]];
    authRec.ky ← DESFace.GetRandomKey[];
    authRec.ck ← convKey ← DESFace.GetRandomKey[];
    authRec.time ← 0;
    Inline.LongCOPY[
      from: caller, to: @(authRec.a), nwords: SIZE[StringBody [caller.length]]];
    DESFace.CorrectParity[@kb];
    DESFace.EncryptBlock[
      key: kb, from: LOOPHOLE[@authRec.ck], to: LOOPHOLE[@authRec.ck]];
    DESFace.CBCCheckEncrypt[
      key: authRec.ky, nBlks: nBlks - 2, from: LOOPHOLE[@authRec.ck],
      to: LOOPHOLE[@authRec.ck], seed: [0, 0, 0, 0]];
    DESFace.EncryptBlock[
      key: kb, from: LOOPHOLE[@authRec.ky], to: LOOPHOLE[@authRec.ky]];
    END;
    END;

  ConvHashKey: TYPE = [0..127];

  conversations: LONG POINTER TO ARRAY ConvHashKey OF Conversation =
    Heap.systemZone.NEW[ARRAY ConvHashKey OF Conversation ← ALL[NIL]];

  InconsistentHashTable: ERROR = CODE;

  EntryAddConversation: ENTRY PROC [
    id: ConversationID, level: MesaRPC.SecurityLevel, iv: DESFace.IV,
    originator, responder: MesaRPC.Principal, convKey: DESFace.Key,
    auth: Authenticator] RETURNS [conversation: Conversation] = INLINE {
    RETURN[AddConversation[id, level, iv, originator, responder, convKey, auth]]};

  AddConversation: INTERNAL PROC [
    id: ConversationID, level: MesaRPC.SecurityLevel, iv: DESFace.IV,
    originator, responder: MesaRPC.Principal, convKey: DESFace.Key,
    auth: Authenticator] RETURNS [conversation: Conversation] =
    BEGIN
    dataPtr: LONG POINTER TO Conversation ← @conversations[
      id.count.ls MOD SUCC[LAST[ConvHashKey]]];
    DO
      SELECT TRUE FROM
        dataPtr↑ = NIL =>
          BEGIN
          conversation ← Heap.systemZone.NEW[
            ConversationObject ← [
            NIL, id, level, convKey, iv, AllocPrincipal[originator],
            AllocPrincipal[responder], auth]];
          dataPtr↑ ← conversation;
          EXIT;
          END;
        dataPtr↑.id = id => {conversation ← dataPtr↑; EXIT -- already there -- };
        ENDCASE => dataPtr ← @(dataPtr↑.next);
      ENDLOOP;
    END;

  UnknownConversation: ERROR = CODE;

  EndConversation: PUBLIC ENTRY PROC [conversation: Conversation] =
    BEGIN
    dataPtr: LONG POINTER TO Conversation ← @conversations[
      conversation.id.count.ls MOD SUCC[LAST[ConvHashKey]]];
    IF conversation.id.originator # myHost THEN
      RETURN WITH ERROR UnknownConversation[];
    DO
      SELECT TRUE FROM
        dataPtr↑ = NIL => RETURN WITH ERROR UnknownConversation[];
        dataPtr↑ = conversation =>
          BEGIN
          dataPtr↑ ← dataPtr↑.next;
          IF conversation.originator # NIL THEN
            Heap.FreeNode[p: conversation.originator];
          IF conversation.responder # NIL THEN
            Heap.FreeNode[p: conversation.responder];
          IF BASE[conversation.authenticator] # NIL THEN
            Heap.FreeNode[p: BASE[conversation.authenticator]];
          Heap.systemZone.FREE[@conversation];
          EXIT;
          END;
        ENDCASE => dataPtr ← @(dataPtr↑.next);
      ENDLOOP;
    END;

  GetConversationID: PUBLIC ENTRY PROC [conversation: Conversation]
    RETURNS [id: ConversationID] = {
    RETURN[IF conversation = NIL THEN [[[0], [0]], [0, 0]] ELSE conversation.id]};

  GetCaller: PUBLIC ENTRY PROC [conversation: Conversation]
    RETURNS [MesaRPC.Principal] = {
    RETURN[IF conversation = NIL THEN NIL ELSE conversation.originator]};

  GetLevel: PUBLIC ENTRY PROC [conversation: Conversation]
    RETURNS [level: MesaRPC.SecurityLevel] = {
    RETURN[IF conversation = NIL THEN none ELSE conversation.level]};




  -- ******** Packet encryption and decryption ******** --

  encryptedHeaderLength: CARDINAL =
    SIZE[RPCPkt.PktID] + SIZE[RPCPkt.DispatcherDetails];

  Check: TYPE = LONG CARDINAL;  -- cipher checksum at end of packet --
  -- bottom 2 bits indicate block rounding; remaining 30 bits should be 0 --

  EncryptPkt: PUBLIC PROC [pkt: MesaRPCLupine.RPCPkt, l: MesaRPCLupine.DataLength]
    RETURNS [CARDINAL] =
    BEGIN
    header: LONG POINTER TO RPCPkt.Header = @pkt.header;
    conv: Conversation = pkt.convHandle;
    IF conv # NIL AND conv.level # none AND conv.level # authOnly THEN
      BEGIN
      words: CARDINAL =
        l -- stub data words -- + encryptedHeaderLength + SIZE[Check];
      nBlks: CARDINAL =
        (words + SIZE[DESFace.Block] - 1 -- round up -- )/SIZE[DESFace.Block];
      blocks: DESFace.Blocks = LOOPHOLE[@header.pktID];
      checkPtr: LONG POINTER TO Check = LOOPHOLE[@blocks[nBlks] - SIZE[Check]];
      checkPtr↑ ← nBlks*SIZE[DESFace.Block] - words;
      SELECT conv.level FROM
        ECB => DESFace.ECBEncrypt[conv.key, nBlks, blocks, blocks];
        CBC => DESFace.CBCEncrypt[conv.key, nBlks, blocks, blocks, conv.iv];
        CBCCheck =>
          DESFace.CBCCheckEncrypt[conv.key, nBlks, blocks, blocks, conv.iv];
        ENDCASE => ERROR;
      RETURN[
        nBlks*SIZE[DESFace.Block] - encryptedHeaderLength +
          RPCPkt.pktLengthOverhead]
      END
    ELSE RETURN[l + RPCPkt.pktLengthOverhead]
    END;

  DecryptPkt: PUBLIC PROC [
    header: LONG POINTER TO RPCPkt.Header, convHandle: Conversation]
    RETURNS [ok: BOOLEAN, l: MesaRPCLupine.DataLength] =
    BEGIN
    ok ← TRUE;
    IF convHandle # NIL AND convHandle.level # none
      AND convHandle.level # authOnly THEN
      BEGIN
      nBlks: CARDINAL =
        (header.length - RPCPkt.pktLengthOverhead + encryptedHeaderLength)/SIZE[
          DESFace.Block];
      blocks: DESFace.Blocks = LOOPHOLE[@header.pktID];
      checkPtr: LONG POINTER TO Check = LOOPHOLE[@blocks[nBlks] - SIZE[Check]];
      SELECT convHandle.level FROM
        ECB => DESFace.ECBDecrypt[convHandle.key, nBlks, blocks, blocks];
        CBC =>
          DESFace.CBCDecrypt[
            convHandle.key, nBlks, blocks, blocks, convHandle.iv];
        CBCCheck =>
          DESFace.CBCCheckDecrypt[
            convHandle.key, nBlks, blocks, blocks, convHandle.iv];
        ENDCASE => ERROR;
      IF checkPtr↑ IN [0..SIZE[DESFace.Block]) THEN
        l ← Inline.LowHalf[
          nBlks*SIZE[DESFace.Block] - checkPtr↑ - SIZE[Check] -
            encryptedHeaderLength]
      ELSE ok ← FALSE;
      END
    ELSE l ← header.length - RPCPkt.pktLengthOverhead;
    END;




  -- ******** Requests-For-Authenticator's (RFA's) ******** --

  RFARequest: TYPE = MACHINE DEPENDENT RECORD [
    -- conversationID, {pktID}CK, nonceID
    callerConv(0): RPCPkt.PktConversationID,
    callerPktID(2): RPCPkt.PktID -- still encrypted -- ,
    nonceID(6): LONG CARDINAL -- nominated by requester -- ];

  RFAResponse: TYPE = MACHINE DEPENDENT RECORD [
    -- {iv, connectionID, callCount, nonceID+1}CK,
    -- null, authScheme, authLength, authenticator, callee.
    -- The initial part is encrypted with CBC using a zero IV.
    iv(0): DESFace.IV,
    connection(4): RPCPkt.ConnectionID,
    call(8): RPCPkt.CallCount,
    nonceID(10): LONG CARDINAL,
    -- ends here if callee originated the conversation --
    level(12): MesaRPC.SecurityLevel,
    -- ends here if level = none --
    authLength(13): CARDINAL,  -- length of authenticator (words) --
    authenticator(14): AuthenticatorLayout
    -- followed by Callee principal name --
    ];

  responseCKBlocks: CARDINAL = 12/SIZE[DESFace.Block];
  -- Size of {iv, connectionID, callCount, nonceID+1}

  rfaDataLength: MesaRPCLupine.DataLength =
    SIZE[RFAResponse] - SIZE[StringBody [0]] +
      2*SIZE[StringBody [MesaRPC.maxPrincipalLength]];

  myHost: RPCPkt.Machine;

  GetConnectionState: PUBLIC PROC [decrypted: BOOLEAN, callPkt: MesaRPCLupine.RPCPkt]
    RETURNS [
      ok: BOOLEAN, id: RPCPkt.ConnectionID, call: RPCPkt.CallCount,
      conv: Conversation, l: MesaRPCLupine.DataLength] =
    BEGIN
    -- The RFA packet looks a lot like the first packet of a new call.
    -- The response, a data packet, will look a lot like the result of
    -- this call.
    callHeader: LONG POINTER TO Header = @callPkt.header;
    rfaPktSpace: ARRAY [1..rfaDataLength + MesaRPCLupine.pktOverhead] OF WORD;
    rfaPkt: MesaRPCLupine.StubPkt = MesaRPCLupine.GetStubPkt[@rfaPktSpace];
    myNonceID: LONG CARDINAL = System.GetClockPulses[];
    request: LONG POINTER TO RFARequest = LOOPHOLE[LONG[@(rfaPkt[0])]];
    response: LONG POINTER TO RFAResponse = LOOPHOLE[LONG[@(rfaPkt[0])]];
    rfaHeader: LONG POINTER TO Header = @rfaPkt.header;
    request.callerConv ← callHeader.conv;
    request.callerPktID ← callHeader.pktID;  --still encrypted--
    request.nonceID ← myNonceID;
    rfaPkt.convHandle ← MesaRPC.unencrypted;
    rfaHeader↑ ← ConcreteHeader[@callPkt.header]↑;
    RPCPkt.SetupResponse[rfaHeader];
    rfaHeader.conv ← firstConversation;
    rfaHeader.pktID.activity ← 0;
    -- rfaHeader.pktID.callSeq will be set by PktExchange --
    rfaHeader.pktID.pktSeq ← 0;
    BEGIN
    responseLength: MesaRPCLupine.DataLength ← RPCPkt.PktExchange[
      rfaPkt, SIZE[RFARequest], rfaDataLength, authReq].newLength;
    IF responseLength < SIZE[RFAResponse] - SIZE[AuthenticatorLayout] THEN
      RETURN[FALSE, , , , ];
    END;
    IF response.level = none THEN {
      l ← callHeader.length - RPCPkt.pktLengthOverhead; conv ← NIL}
    ELSE
      BEGIN
      -- response = {IV,cond,call,nonce}CK,,{KY}KB,{{CK}KB,time,A}KY
      --TEMP-- kb: DESFace.Key ← DESFace.nullKey -- !!! -- ;
      DESFace.CorrectParity[@kb];
      DESFace.DecryptBlock[
        key: kb, from: LOOPHOLE[@response.authenticator.ky],
        to: LOOPHOLE[@response.authenticator.ky]];
      DESFace.CBCCheckDecrypt[
        key: response.authenticator.ky,
        nBlks: response.authLength/SIZE[DESFace.Block] - 2,
        from: LOOPHOLE[@response.authenticator.ck],
        to: LOOPHOLE[@response.authenticator.ck], seed: [0, 0, 0, 0]];
      DESFace.DecryptBlock[
        key: kb, from: LOOPHOLE[@response.authenticator.ck],
        to: LOOPHOLE[@response.authenticator.ck]];
      DESFace.CBCCheckDecrypt[
        key: response.authenticator.ck, nBlks: responseCKBlocks,
        from: LOOPHOLE[response], to: LOOPHOLE[response], seed: [0, 0, 0, 0]];
      conv ← EntryAddConversation[
        -- Construct a ConversationID from a PktConversationID --
        [
        IF response.connection.conv.originator = caller THEN
        response.connection.caller ELSE myHost, [
        response.connection.conv.ls, response.connection.conv.ms]],
        response.level, response.iv, @response.authenticator.a, NIL,
        response.authenticator.ck, DESCRIPTOR[NIL, 0]];
      callPkt.convHandle ← conv;
      IF NOT decrypted THEN
        BEGIN
        ok: BOOLEAN;
        [ok, l] ← DecryptPkt[callHeader, conv];
        IF NOT ok THEN RETURN[FALSE, , , , ];
        END;
      END;
    -- correctness of nonceID means he knows CK.
    IF response.nonceID # myNonceID + 1 THEN RETURN[FALSE, , , , ];
    IF response.connection # [
      callHeader.conv, callHeader.srceHost, callHeader.pktID.activity] THEN
      RETURN[FALSE, , , , ];
    IF response.call # callHeader.pktID.callSeq THEN RETURN[FALSE, , , , ];
    RETURN[TRUE, response.connection, response.call, conv, l];
    END;

  ReplyToRFA: PUBLIC PROC [
    b: PupDefs.PupBuffer, callHeader: LONG POINTER TO RPCPkt.Header,  -- encrypted --
    callPktID: RPCPkt.PktID -- clear -- , convHandle: Conversation]
    RETURNS [BOOLEAN] =
    BEGIN
    -- Remember PupDefs.PupBuffer's don't look like MesaRPCLupine.RPCPkt's! --
    recvdHeader: LONG POINTER TO RPCPkt.Header = LOOPHOLE[b.pup];
    -- copy request out of buffer to avoid overwriting our args! --
    request: RFARequest = LOOPHOLE[recvdHeader + SIZE[RPCPkt.Header], LONG POINTER
      TO RFARequest]↑;
    response: LONG POINTER TO RFAResponse =
      LOOPHOLE[recvdHeader + SIZE[RPCPkt.Header]];
    used: CARDINAL ← SIZE[RFAResponse] - SIZE[AuthenticatorLayout];
    IF request.callerConv # callHeader.conv
      OR request.callerPktID # callHeader.pktID THEN {
      PupDefs.ReturnBuffer[b]; RETURN[FALSE]};
    response.connection ← [
      callHeader.conv, callHeader.srceHost, callPktID.activity];
    response.call ← callPktID.callSeq;
    response.nonceID ← request.nonceID + 1;
    IF callHeader.conv = firstConversation THEN response.level ← none
    ELSE
      BEGIN
      IF callHeader.conv.originator = callee THEN --TEMP-- ERROR;
      response.level ← convHandle.level;
      response.iv ← convHandle.iv;
      DESFace.CBCCheckEncrypt[
        key: convHandle.key, nBlks: responseCKBlocks, from: LOOPHOLE[response],
        to: LOOPHOLE[response], seed: [0, 0, 0, 0]];
      response.authLength ← LENGTH[convHandle.authenticator];
      CopyAuth[from: convHandle.authenticator, to: @response.authenticator];
      used ← used + LENGTH[convHandle.authenticator];
      CopyPrincipal[
        from: convHandle.originator,
        to: @response.authenticator + LENGTH[convHandle.authenticator]];
      used ← used + SIZE[StringBody [convHandle.originator.length]];
      END;
    BEGIN
    header: Header ← recvdHeader↑;
    RPCPkt.SetupResponse[@header];
    header.length ← RPCPkt.pktLengthOverhead + used;
    header.oddByte ← no;
    header.type ← [0, rpc, end, dontAck, data];
    header.pktID.pktSeq ← header.pktID.pktSeq + 1;
    header.srceHost ← myHost;
    header.srceSoc ← RPCPrivate.rpcSocket;
    header.srcePSB ← PSB.PsbNull;
    recvdHeader↑ ← header;
    END;
    PupDefs.PupRouterSendThis[b];
    RETURN[TRUE]
    END;



  -- Initialization --

  Initialize: ENTRY PROC =
    BEGIN
    myAddr: PupTypes.PupAddress ← PupDefs.GetLocalPupAddress[
      RPCPrivate.rpcSocket, NIL];
    now: MACHINE DEPENDENT RECORD [ls: CARDINAL, spare: [0..1], ms: [0..77777B]] =
      LOOPHOLE[System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]]];
    firstHandle: Conversation;
    myHost ← [myAddr.net, myAddr.host];
    lastConversation ← [originator: myHost, count: [ls: now.ls, ms: now.ms]];
    -- Use first available conversationID for unencrypted conversations.
    firstHandle ← AddConversation[
      lastConversation, none, , NIL, NIL, DESFace.nullKey, DESCRIPTOR[NIL, 0]];
    firstConversation ← [
      ls: firstHandle.id.count.ls, originator: caller,
      ms: firstHandle.id.count.ms];
    END;

  Initialize[];

  END.