<> <> <> DIRECTORY FS, IO, MessageWindow, Process, Protocols, Pup, PupName, PupStream, PupWKS, Rope, UserCredentials; PupTelnet: CEDAR MONITOR LOCKS pc USING pc: PupConversation <> IMPORTS FS, IO, MessageWindow, Process, Protocols, PupName, PupStream, Rope, UserCredentials = {OPEN Protocols; pupTelnet: Protocol _ NEW [ProtocolRep _ [ name: "PupTelnet", Instantiate: Instantiate, StartConnect: StartConnect, StartDisconnect: StartDisconnect, GiveUp: GiveUp, SetOnLine: SetOnLine ]]; PupConversation: TYPE = REF PupConversationRep; PupConversationRep: TYPE = MONITORED RECORD [ fromClient, toClient, serverStream, logStream: IO.STREAM _ NIL, charsSentSinceFlush: NAT _ 0, changer: PROCESS _ NIL, change: CONDITION, procs: ARRAY Who OF PROCESS _ ALL[NIL], phase: Phase _ running, serverToUsering: BOOL _ TRUE, gettingClientChar: BOOL _ FALSE]; Phase: TYPE = {running, destroying, destroyed}; Who: TYPE = {uToS, sToU}; whoBar: ARRAY Who OF Who _ [uToS: sToU, sToU: uToS]; MarkByte: TYPE = PupStream.MARK; setLineWidth: MarkByte = 2; setPageLength: MarkByte = 3; timingMark: MarkByte = 5; timingMarkReply: MarkByte = 6; doSetLineWidth: BOOL _ TRUE; minLogKeep: CARDINAL _ 10; Instantiate: PROC [client: Client, logFileName: ROPE _ NIL] RETURNS [c: Conversant] = { pc: PupConversation _ NEW [PupConversationRep _ [ fromClient: client.fromClient, toClient: client.toClient ]]; IF logFileName # NIL THEN SetLog[pc, logFileName]; TRUSTED { Process.InitializeCondition[@pc.change, Process.SecondsToTicks[60]]; Process.EnableAborts[@pc.change]; }; c _ NEW [ConversantPrivate _ [ protocol: pupTelnet, client: client, conversantData: pc, driver: client.fromClient ]]; TRUSTED { Process.Detach[pc.procs[uToS] _ FORK UserToServer[c, pc]]; Process.Detach[pc.procs[sToU] _ FORK ServerToUser[c, pc]]; }; }; SetLog: PROC [pc: PupConversation, logFileName: ROPE] = { oldKeep: CARDINAL _ 1; pc.logStream _ FS.StreamOpen[fileName: logFileName, accessOptions: create]; oldKeep _ FS.FileInfo[logFileName !FS.Error => CONTINUE].keep; IF oldKeep < minLogKeep THEN FS.SetKeep[logFileName, minLogKeep !FS.Error => CONTINUE]; }; NoteDestruction: ENTRY PROC [c: Conversant, pc: PupConversation] = { ENABLE UNWIND => {}; InternalNoteDestruction[c, pc]; }; InternalNoteDestruction: INTERNAL PROC [c: Conversant, pc: PupConversation] = { SELECT pc.phase FROM running => pc.phase _ destroying; destroying, destroyed => NULL; ENDCASE => ERROR; BROADCAST pc.change; SELECT c.state.connectivity FROM connected => InternalReallyStartDisconnect[c, pc, NIL, FALSE]; connecting, disconnecting => InternalGiveUp[c, pc, NIL]; disconnected => NULL; ENDCASE => ERROR; pc.phase _ destroyed; BROADCAST pc.change; }; <> <> <> < {};>> <> < StartDisconnect[c, FALSE];>> < GiveUp[c];>> < NULL;>> < ERROR;>> <> <<};>> StartConnect : PROC [ c: Conversant, serverName: ROPE, login: BOOL _ FALSE] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; IF pc.phase >= destroying THEN { Scream["Can't connect a destroyed conversant!"]; RETURN}; SELECT c.state.connectivity FROM disconnected => { c.serverName _ serverName; c.state.connectivity _ connecting; c.state.onLine _ TRUE; c.driver _ IO.noInputStream; BROADCAST pc.change; c.client.type.NoteState[c ! UNWIND => { c.state.connectivity _ disconnected; BROADCAST pc.change; }; ]; TRUSTED {Process.Detach[ pc.changer _ FORK Connect[c, pc, serverName, login] ]}; }; connecting, connected, disconnecting => { Scream["Already connected to %g!", [rope[c.serverName]]]; c.client.type.NoteState[c]; }; ENDCASE => ERROR; }; Really[pc]; }; SetConnectivity: ENTRY PROC [c: Conversant, pc: PupConversation, cy: Connectivity, cond: UNSAFE PROCESS _ NIL, serverStream: IO.STREAM _ NIL] RETURNS [ok: BOOL] = { IF ok _ (cond = NIL OR cond = pc.changer) THEN { c.state.connectivity _ cy; c.state.onLine _ cy = connected; pc.serverStream _ serverStream; c.driver _ IF NOT c.state.onLine THEN c.client.fromClient ELSE IF serverStream # NIL THEN serverStream ELSE IO.noInputStream; BROADCAST pc.change; c.client.type.NoteState[c]; }; }; Connect: PROC [c: Conversant, pc: PupConversation, serverName: ROPE, login: BOOL] = { self: UNSAFE PROCESS = Process.GetCurrent[]; toClient: IO.STREAM = c.client.toClient; { ENABLE { IO.Error => IF ec = StreamClosed AND stream = toClient THEN GOTO Destroyed; IO.EndOfStream => IF stream = toClient THEN GOTO Destroyed; UNWIND => [] _ SetConnectivity[c, pc, disconnected, self]; }; addr: Pup.Address; serverStream: IO.STREAM _ NIL; toClient.PutF["\015\012Opening connection to %g ... ", IO.rope[serverName]]; toClient.Flush[]; addr _ PupName.NameLookup[serverName, PupWKS.telnet ! PupName.Error => { Process.CheckForAbort[]; toClient.PutRope["PUP name lookup error"]; FinishStringWithErrorMsg[toClient, text]; GOTO Fail; } ]; Process.CheckForAbort[]; serverStream _ PupStream.Create[addr, 10000, 10000 ! PupStream.StreamClosing => { Process.CheckForAbort[]; toClient.PutRope["Can't connect"]; FinishStringWithErrorMsg[toClient, text]; GOTO Fail; } ]; Process.CheckForAbort[]; toClient.PutF["open.\015\012"]; toClient.Flush[]; IF doSetLineWidth THEN { PupStream.SendMark[serverStream, setLineWidth]; serverStream.PutChar[0C]; serverStream.Flush[!PupStream.Timeout => RESUME]; }; IF login THEN { name, password: Rope.ROPE; [name: name, password: password] _ UserCredentials.Get[]; serverStream.PutRope["Login "]; serverStream.PutRope[name]; IF Rope.Find[s1: name, s2: "."] = -1 THEN serverStream.PutRope[".PA"]; serverStream.PutRope[" "]; serverStream.PutRope[password]; serverStream.PutRope[" \n"]; serverStream.Flush[!PupStream.Timeout => RESUME]; }; IF NOT SetConnectivity[c, pc, connected, self, serverStream] THEN serverStream.Close[TRUE !IO.Error => CONTINUE]; EXITS Fail => { [] _ SetConnectivity[c, pc, disconnected, self]; }; Destroyed => NoteDestruction[c, pc]; }}; FinishStringWithErrorMsg: PROC [toClient: IO.STREAM, errorMsg: ROPE, ending: ROPE _ ".\015\012"] = { IF errorMsg # NIL THEN toClient.PutF[":\t%s", IO.rope[errorMsg]]; toClient.PutRope[ending]; toClient.Flush[]; }; StartDisconnect: PROC [c: Conversant, verbosely: BOOL _ TRUE] = { ReallyStartDisconnect[c, IO.PutFR["Closing connection to %g", [rope[c.serverName]]], verbosely]; }; ReallyStartDisconnect: PROC [c: Conversant, msg: ROPE, verbosely: BOOL _ TRUE] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; InternalReallyStartDisconnect[c, pc, msg, verbosely]; }; Really[pc]; }; InternalReallyStartDisconnect: INTERNAL PROC [c: Conversant, pc: PupConversation, msg: ROPE, verbosely: BOOL] = { IF pc.logStream # NIL THEN pc.logStream.Flush[!FS.Error => CONTINUE]; SELECT c.state.connectivity FROM disconnected => { Scream["Already disconnected ?%g?", [rope[msg]]]; c.client.type.NoteState[c]; }; connecting => { Scream["Can't disconnect because still connecting ?%g?", [rope[msg]]]; c.client.type.NoteState[c]; }; connected => { c.state.connectivity _ disconnecting; c.state.onLine _ FALSE; c.driver _ IO.noInputStream; BROADCAST pc.change; c.client.type.NoteState[c ! UNWIND => { c.state.connectivity _ disconnected; BROADCAST pc.change; }; ]; TRUSTED {Process.Detach[ pc.changer _ FORK Disconnect[c, pc, msg, verbosely] ]}; }; disconnecting => { Scream["Already disconnecting from %g!", [rope[c.serverName]]]; c.client.type.NoteState[c]; }; ENDCASE => ERROR; }; Disconnect: PROC [c: Conversant, pc: PupConversation, msg: ROPE, verbosely: BOOL] = { self: UNSAFE PROCESS = Process.GetCurrent[]; { ENABLE { IO.Error => IF ec = StreamClosed AND stream = c.client.toClient THEN GOTO Destroyed; IO.EndOfStream => IF stream = c.client.toClient THEN GOTO Destroyed; }; ok: BOOL; serverStream: IO.STREAM = pc.serverStream; IF verbosely THEN { c.client.toClient.PutF["\015\012%g ... ", [rope[msg]]]; c.client.toClient.Flush[]}; serverStream.Close[TRUE !IO.Error => CONTINUE]; ok _ SetConnectivity[c, pc, disconnected, self, NIL]; IF ok AND verbosely THEN { c.client.toClient.PutRope[" ... closed\015\012"]; c.client.toClient.Flush[]}; EXITS Destroyed => NoteDestruction[c, pc]; }}; GiveUp: PROC [c: Conversant, verbosely: BOOL _ TRUE] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; InternalGiveUp[c, pc, IF verbosely THEN "... gave up!\015\012" ELSE NIL]; }; Really[pc]; }; InternalGiveUp: INTERNAL PROC [c: Conversant, pc: PupConversation, msg: ROPE _ NIL] = { SELECT c.state.connectivity FROM connecting, disconnecting => { oldChanger: PROCESS _ pc.changer; pc.changer _ NIL; IF c.state.connectivity = connecting THEN TRUSTED {Process.Abort[oldChanger]}; c.state.connectivity _ disconnected; c.state.onLine _ FALSE; c.driver _ c.client.fromClient; BROADCAST pc.change; c.client.type.NoteState[c]; IF msg # NIL THEN { ENABLE IO.Error => IF ec = StreamClosed AND stream = c.client.toClient THEN {InternalNoteDestruction[c, pc]; CONTINUE}; c.client.toClient.PutRope[msg]; c.client.toClient.Flush[]; }; }; disconnected, connected => { Scream["Not connecting or disconnecting!"]; c.client.type.NoteState[c]; }; ENDCASE => ERROR; }; SetOnLine: PROC [c: Conversant, onLine: BOOL] = { pc: PupConversation = NARROW[c.conversantData]; Really: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; c.state.onLine _ onLine; c.driver _ IF NOT onLine THEN c.client.fromClient ELSE IF pc.serverStream # NIL THEN pc.serverStream ELSE IO.noInputStream; BROADCAST pc.change; c.client.type.NoteState[c]; }; Really[pc]; }; flushPeriod: NAT _ 50; UserToServer: PROC [c: Conversant, pc: PupConversation] = { PreChar: ENTRY PROC [pc: PupConversation] RETURNS [do: {proceed, exit}] = { IF pc.phase >= destroying THEN RETURN [exit]; pc.gettingClientChar _ TRUE; do _ proceed; }; Stuff: ENTRY PROC [pc: PupConversation] RETURNS [do: {disconnect, proceed, exit}] = { ENABLE UNWIND => {}; pc.gettingClientChar _ FALSE; UNTIL c.state.connectivity=connected OR (NOT c.state.onLine) OR pc.phase >= destroying DO WAIT pc.change ENDLOOP; do _ proceed; SELECT TRUE FROM pc.phase >= destroying => do _ exit; NOT c.state.onLine => pc.toClient.PutChar[char]; ENDCASE => { ENABLE PupStream.StreamClosing => SELECT c.state.connectivity FROM connected => { do _ disconnect; whyDisconnect _ text; CONTINUE}; disconnected, connecting, disconnecting => { }; ENDCASE => ERROR; pc.serverStream.PutChar[char]; IF pc.charsSentSinceFlush >= flushPeriod OR pc.fromClient.CharsAvail[] = 0 THEN { pc.serverStream.Flush[!PupStream.Timeout => RESUME]; pc.charsSentSinceFlush _ 0; } ELSE pc.charsSentSinceFlush _ pc.charsSentSinceFlush + 1; }; }; char: CHAR; whyDisconnect: ROPE _ NIL; DO ENABLE IO.EndOfStream, IO.Error, ABORTED => EXIT; SELECT PreChar[pc] FROM proceed => NULL; exit => EXIT; ENDCASE => ERROR; char _ pc.fromClient.GetChar[! IO.Error => IF ec = StreamClosed AND stream = pc.fromClient THEN GOTO Destroyed; ABORTED => RETRY ]; DO SELECT Stuff[pc] FROM exit => GOTO Exit; proceed => EXIT; disconnect => ReallyStartDisconnect[ c, IO.PutFR[ "Connection being closed by %s %g", [rope[c.serverName]], [rope[whyDisconnect]]], TRUE ]; ENDCASE => ERROR; ENDLOOP; ENDLOOP; EXITS Exit => NULL; Destroyed => NoteDestruction[c, pc]; }; ServerToUser: PROC [c: Conversant, pc: PupConversation] = { serverStream: IO.STREAM _ NIL; serverName: ROPE _ "??"; char: CHAR; Stuff: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; IF pc.logStream # NIL THEN pc.logStream.PutChar[char]; UNTIL (c.state.connectivity=connected AND c.state.onLine) OR pc.phase >= destroying DO WAIT pc.change ENDLOOP; IF pc.phase >= destroying THEN RETURN; pc.toClient.PutChar[char ! IO.Error => IF ec = StreamClosed AND stream = pc.toClient THEN {InternalNoteDestruction[c, pc]; CONTINUE}; ]; serverStream _ pc.serverStream; serverName _ c.serverName; }; Acquire: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; UNTIL c.state.connectivity=connected OR pc.phase >= destroying DO WAIT pc.change ENDLOOP; serverStream _ pc.serverStream; serverName _ c.serverName; }; NeedToClose: ENTRY PROC [pc: PupConversation] RETURNS [need: BOOL] = { need _ SELECT c.state.connectivity FROM connected => TRUE, disconnected, disconnecting, connecting => FALSE, ENDCASE => ERROR; }; StopServing: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; pc.serverToUsering _ FALSE; BROADCAST pc.change; }; Acquire[pc]; {ENABLE UNWIND => StopServing[pc]; DO whyDisconnect: ROPE _ NIL; { ENABLE { PupStream.StreamClosing => {whyDisconnect _ text; GOTO Disc}; IO.EndOfStream, IO.Error, ABORTED => EXIT; UNWIND => { IF pc.logStream # NIL THEN pc.logStream.Close[!FS.Error => CONTINUE]; }; }; IF pc.phase >= destroying THEN EXIT; IF serverStream.EndOf[] THEN { mySST: MarkByte; mySST _ PupStream.ConsumeMark[serverStream]; IF pc.phase >= destroying THEN EXIT; IF mySST = timingMark THEN { Reply: ENTRY PROC [pc: PupConversation] = { ENABLE UNWIND => {}; PupStream.SendMark[serverStream, timingMarkReply]}; Reply[pc]; }; }; IF pc.phase >= destroying THEN EXIT; char _ serverStream.GetChar[! PupStream.Timeout, IO.EndOfStream => GOTO ReAcquire; ]; Stuff[pc]; EXITS Disc => { IF NeedToClose[pc] THEN ReallyStartDisconnect[c, IO.PutFR["Connection being closed by %s %g", [rope[serverName]], [rope[whyDisconnect]]], TRUE]; Acquire[pc]; }; ReAcquire => Acquire[pc]; }ENDLOOP; }; StopServing[pc]; IF pc.logStream # NIL THEN pc.logStream.Close[!FS.Error => CONTINUE]; }; Scream: PROC [fmt: ROPE, v1, v2: IO.Value _ [null[]]] = { MessageWindow.Append[ message: IO.PutFR[fmt, v1, v2], clearFirst: TRUE ]; MessageWindow.Blink[]; }; Protocols.RegProtocol[pupTelnet]; }.