-- RPC: Authentication and security facilities

--RPCSecurity.mesa

-- Andrew Birrell September 7, 1982 1:47 pm

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

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

BEGIN

Header: PUBLIC TYPE = RPCPkt.Header;

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

GiveBackBuffer: PROC[b: BufferDefs.PupBuffer] =
-- NOTE: calls of this must be made outside our monitor, because
-- RPCPrivate.ReturnBuffer acquires the EthernetDriver monitor,
-- and the EthernetDriver may call EnqueueRecvd which acquires
-- our monitor!
IF Runtime.IsBound[RPCPrivate.ReturnBuffer]
THEN RPCPrivate.ReturnBuffer
ELSE PupDefs.ReturnFreePupBuffer;

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;

InvalidateConversations: INTERNAL PROC =
BEGIN
-- Invalidate conversations for which we are the originator, such that client
-- will get an error if he uses them.

FOR hash: ConvHashKey IN ConvHashKey
DO FOR conversation: Conversation ← conversations[hash], conversation.next
UNTIL conversation = NIL
DO IF conversation.id.originator = myHost THEN conversation.id.originator ← [[0],[0]] ENDLOOP;
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: RPCLupine.RPCPkt, l: RPCLupine.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: RPCLupine.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 ← 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: RPCLupine.DataLength = SIZE[RFAResponse] - SIZE[StringBody[0]] +
2 * SIZE[StringBody[MesaRPC.maxPrincipalLength]];

myHost: RPCPkt.Machine;

GetConnectionState: PUBLIC PROC[ decrypted: BOOLEAN,
callPkt: RPCLupine.RPCPkt]
RETURNS[ ok: BOOLEAN,
id: RPCPkt.ConnectionID,
call: RPCPkt.CallCount,
conv: Conversation,
l: RPCLupine.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 + RPCLupine.pktOverhead] OF WORD;
rfaPkt: RPCLupine.StubPkt = RPCLupine.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: RPCLupine.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: BufferDefs.PupBuffer,
callHeader: LONG POINTER TO RPCPkt.Header, -- encrypted --
callPktID: RPCPkt.PktID -- clear --,
convHandle: Conversation]
RETURNS[BOOLEAN] =
BEGIN
-- Remember BufferDefs.PupBuffer's don't look like RPCLupine.RPCPkt's! --
recvdHeader: LONG POINTER TO RPCPkt.Header = LOOPHOLE[@b.pupLength];
-- 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: CARDINALSIZE[RFAResponse] - SIZE[AuthenticatorLayout];
IF request.callerConv # callHeader.conv OR request.callerPktID # callHeader.pktID
THEN { GiveBackBuffer[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;

Restart: ENTRY PROC =
BEGIN
InvalidateConversations[];
END;

DO Initialize[]; STOP; Restart[]; ENDLOOP;

END.