DIRECTORY BasicTime, Convert, Endian USING [FFromCard], FTP, FTPInternal, IO, Pup USING [Address, nullSocket, Socket], PupBuffer USING [Buffer, FileLookupReply], PupName USING [MyName], PupSocket USING [CreateServer, Destroy, FreeBuffer, Get, ExtractRope, Kick, ReturnError, ReturnToSender, SetUserBytes, SetUserSize, Socket, waitForever], PupStream USING [CloseReason, CreateListener, DestroyListener, Listener, StreamClosing, Timeout], PupWKS USING [fileLookup, ftp], PupType USING [fileLookup, fileLookupError, fileLookupReply], Rope, RuntimeError USING [UNCAUGHT]; FTPServer: CEDAR MONITOR IMPORTS BasicTime, Convert, Endian, FTP, FTPInternal, IO, PupName, PupSocket, PupStream, Rope, RuntimeError EXPORTS FTP = BEGIN ROPE: TYPE = Rope.ROPE; CARD: TYPE = LONG CARDINAL; Connections: INT _ 0; -- number of existing connections that have not yet been destroyed debugging: BOOL _ TRUE; versionRope: ROPE = Convert.RopeFromCard[from: FTPInternal.ftpVersion, showRadix: FALSE] ; versionDate: ROPE = "April 11, 1986 8:01:52 am PST"; Object: PUBLIC TYPE = FTPInternal.Object; Handle: TYPE = FTPInternal.Handle; myName: ROPE _ PupName.MyName[]; Listener: TYPE = REF ListenerObject; ListenerObject: PUBLIC TYPE = RECORD [ listener: PupStream.Listener, filter: FTP.AcceptProc, serverProcs: FTP.ServerProcs, fileInfo: FTP.FileInfoProc _ NIL, fileInfoSocket: PupSocket.Socket _ NIL, fileInfoWorkers: LIST OF PROCESS _ NIL ]; CreateListener: PUBLIC PROC [socket: Pup.Socket _ Pup.nullSocket, procs: FTP.ServerProcs, accept: FTP.AcceptProc _ NIL, timeoutSeconds: INT _ 30, fileInfo: FTP.FileInfoProc, fileInfoSocket: Pup.Socket _ Pup.nullSocket, fileInfoProcesses: CARDINAL [1..10]] RETURNS [l: Listener] = { him: Listener = NEW[ListenerObject]; IF socket = Pup.nullSocket THEN socket _ PupWKS.ftp; IF fileInfoSocket = Pup.nullSocket THEN fileInfoSocket _ PupWKS.fileLookup; him.fileInfo _ fileInfo; him.listener _ PupStream.CreateListener[ local: socket, worker: Worker, getTimeout: timeoutSeconds*1000, putTimeout: timeoutSeconds*1000, clientData: him, filter: Filter, -- NIL => Accept all requests echoFilter: NIL]; -- NIL => Answer all echos him.filter _ accept; him.serverProcs _ procs; IF fileInfo # NIL THEN { him.fileInfoSocket _ PupSocket.CreateServer[ recvBuffers: fileInfoProcesses * 2, local: fileInfoSocket, getTimeout: PupSocket.waitForever ]; FOR processes: CARDINAL IN [0..fileInfoProcesses) DO him.fileInfoWorkers _ CONS[FORK FileInfoServer[him], him.fileInfoWorkers]; ENDLOOP; }; RETURN[him]; }; DestroyListener: PUBLIC PROC [l: Listener] = { PupStream.DestroyListener[l.listener]; FOR p: LIST OF PROCESS _ l.fileInfoWorkers, p.rest UNTIL p = NIL DO PupSocket.Kick[l.fileInfoSocket]; ENDLOOP; FOR p: LIST OF PROCESS _ l.fileInfoWorkers, p.rest UNTIL p = NIL DO TRUSTED { JOIN p.first; }; ENDLOOP; IF l.fileInfoSocket # NIL THEN PupSocket.Destroy[l.fileInfoSocket]; }; Server: PUBLIC PROC [stream: IO.STREAM, procs: FTP.ServerProcs] = { him: Listener = NEW[ListenerObject]; him.serverProcs _ procs; innerServer[stream, him]; }; Filter: PROC [clientData: REF ANY, remote: Pup.Address] RETURNS [reject: ROPE] = { listener: Listener = NARROW[clientData]; accept: BOOL _ TRUE; IF listener.filter # NIL THEN [accept: accept, reason: reject] _ listener.filter[remote, Connections]; IF accept THEN { bumpConnections[]; RETURN[NIL]; }; IF reject = NIL THEN reject _ "Rejected"; }; Worker: PROC [stream: IO.STREAM, clientData: REF ANY, remote: Pup.Address] = { listener: Listener = NARROW[clientData]; innerServer[stream, listener]; debumpConnections[]; stream.Close[ ]; }; innerServer: PROC [stream: IO.STREAM, listener: Listener] = { closing: BOOLEAN _ FALSE; closeReason: PupStream.CloseReason _ localAbort; { ENABLE { PupStream.StreamClosing => {closeReason _ why; closing _ TRUE; GOTO Exit}; PupStream.Timeout => {closeReason _ transmissionTimeout; closing _ TRUE; GOTO Exit}; RuntimeError.UNCAUGHT => IF NOT debugging THEN {closeReason _ localAbort; GOTO Exit}; ABORTED, UNWIND => {closeReason _ localAbort; GOTO Exit}; }; ftpHandle: Handle; remoteHerald: ROPE; AFew: CARDINAL = 6; Index: TYPE = [0..AFew); lastFewMarks: RECORD[ index: Index _ 0, buffer: ARRAY Index OF FTPInternal.Mark _ ALL[comment] ]; ftpHandle _ NEW[Object _ []]; ftpHandle.pList[local] _ NEW[FTPInternal.PListObject]; ftpHandle.byteStream _ stream; remoteHerald _ AwaitCallingMessage[ftpHandle]; IF NOT closing THEN SendHerald[ftpHandle, listener, remoteHerald]; UNTIL closing DO mark: FTPInternal.Mark; code: FTP.FailureCode; [mark, code] _ ftpHandle.GetCommand[]; lastFewMarks.buffer[lastFewMarks.index] _ mark; lastFewMarks.index _ (lastFewMarks.index + 1) MOD AFew; SELECT mark FROM retrieve => { Retrieve[ftpHandle, listener]; }; store => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; ftpHandle.GenerateNo[badCommand, "Command superseded by NewStore"]; }; comment => { [] _ ftpHandle.GetText[gobbleEOC: FALSE]; }; newStore => { Store[ftpHandle, listener]; }; enumerate => { Enumeration[ftpHandle, listener, enumerate]; }; newEnumerate => { Enumeration[ftpHandle, listener, newEnumerate]; }; delete => { Delete[ftpHandle, listener]; }; rename => { Rename[ftpHandle, listener] }; ENDCASE => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; ftpHandle.GenerateNo[badCommand, "Command undefined or unimplemented"]; }; ENDLOOP; EXITS Exit => NULL; }; }; AwaitCallingMessage: PROC [h: Handle] RETURNS [msg: ROPE] = { mark: FTPInternal.Mark; code: FTP.FailureCode; [mark, code] _ h.GetCommand[]; WHILE mark # version OR code # LOOPHOLE[FTPInternal.ftpVersion] DO IF mark # version THEN { h.GenerateFailed[protocolError, "First command must be version"]; [mark, code] _ h.GetCommand[]; LOOP; }; h.GenerateFailed[protocolError, "Incompatible protocol version"]; ENDLOOP; msg _ h.GetText[gobbleEOC: TRUE]; }; SendHerald: PROC [h: Handle, l: Listener, remoteHerald: ROPE] = { localHerald: ROPE _ Rope.Cat[myName, " Cedar FTP Version ", versionRope, "File server of ", versionDate]; IF l.serverProcs.version # NIL THEN localHerald _ l.serverProcs.version[h, remoteHerald]; h.PutCommand[mark: version, code: LOOPHOLE[FTPInternal.ftpVersion], text: localHerald, sendEOC: TRUE]; }; Retrieve: PROC [ftpHandle: Handle, listener: Listener] = { xferOK: BOOL; comp: FTP.ServerCompleteProc = { IF xferOK THEN ftpHandle.PutCommand[mark: yes, text: "Transfer complete", sendEOC: FALSE]; RETURN[xferOK]; }; confirm: FTP.ConfirmTransferProc = { mark: FTPInternal.Mark; code: FTP.FailureCode; ftpHandle.PutCommandAndPList[mark: hereIsPList, pList: ftpHandle.pList[local], sendEOC: TRUE]; [mark, code] _ ftpHandle.GetCommand[]; SELECT mark FROM yes => { xferOK _ TRUE; [] _ ftpHandle.GetText[gobbleEOC: TRUE]; }; no => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; xferOK _ FALSE; }; ENDCASE => ftpHandle.GenerateFailed[protocolError]; IF xferOK THEN { ftpHandle.PutCommand[mark: hereIsFile]; RETURN [ftpHandle.byteStream]; } ELSE { RETURN [NIL]; }; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.retrieve[h: ftpHandle, confirm: confirm, complete: comp ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutEOC[]; EXITS return => RETURN; }; Store: PROC [ftpHandle: Handle, listener: Listener] = { xferOK: BOOL; comp: FTP.ServerCompleteProc = { xferOK _ ftpHandle.GetYesNo[gobbleEOC: TRUE]; IF xferOK THEN ftpHandle.PutCommand[mark: yes, text: "Transfer Completed", sendEOC: FALSE]; RETURN[xferOK]; }; confirm: FTP.ConfirmTransferProc = { mark: FTPInternal.Mark; code: FTP.FailureCode; ftpHandle.PutCommandAndPList[mark: hereIsPList, pList: ftpHandle.pList[local], sendEOC: TRUE]; [mark, code] _ ftpHandle.GetCommand[]; SELECT mark FROM hereIsFile => { RETURN [ftpHandle.byteStream]; }; no => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; RETURN [NIL]; }; ENDCASE => ftpHandle.GenerateFailed[protocolError]; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.store[h: ftpHandle, confirm: confirm, complete: comp ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutEOC[]; EXITS return => RETURN; }; Enumeration: PROC [ftpHandle: Handle, listener: Listener, protocol: {enumerate, newEnumerate}] = { firstTime: BOOL _ TRUE; noteFileProc: PROC[h: Handle] = { IF firstTime OR protocol= enumerate THEN h.PutCommand[mark: hereIsPList]; firstTime _ FALSE; h.PutPList[pList: h.pList[local]]; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.enumerate[h: ftpHandle, noteFile: noteFileProc ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; IF firstTime THEN { ftpHandle.GenerateNo[code: fileNotFound, text: "File not found.", sendEOC: TRUE]; } ELSE ftpHandle.PutEOC[]; EXITS return => RETURN; }; Delete: PROC [ftpHandle: Handle, listener: Listener] = { deleteOK: BOOL; comp: FTP.ServerCompleteProc = { IF deleteOK THEN ftpHandle.PutCommand[mark: yes, text: "Delete OK", sendEOC: FALSE]; RETURN[deleteOK]; }; confirm: FTP.ConfirmProc = { mark: FTPInternal.Mark; code: FTP.FailureCode; ftpHandle.PutCommandAndPList[mark: hereIsPList, pList: ftpHandle.pList[local], sendEOC: TRUE]; [mark, code] _ ftpHandle.GetCommand[]; deleteOK _ TRUE; SELECT mark FROM yes => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; RETURN [TRUE]; }; no => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; RETURN [FALSE]; }; ENDCASE => ftpHandle.GenerateFailed[protocolError]; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { deleteOK _ FALSE; ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.delete[h: ftpHandle, confirm: confirm, complete: comp ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutEOC[]; EXITS return => RETURN; }; Rename: PROC [ftpHandle: Handle, listener: Listener] = { ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: FALSE]; ftpHandle.pList[local] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.rename[h: ftpHandle ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutCommand[mark: yes, text: "Rename OK", sendEOC: TRUE]; EXITS return => RETURN; }; bumpConnections: ENTRY PROC = { Connections _ Connections + 1; }; debumpConnections: ENTRY PROC = { Connections _ Connections - 1; }; FileInfoServer: PROC [him: Listener] = TRUSTED { socket: PupSocket.Socket _ him.fileInfoSocket; DO b: PupBuffer.Buffer _ PupSocket.Get[socket]; IF b = NIL THEN EXIT; SELECT b.type FROM PupType.fileLookup => { fileName: ROPE _ PupSocket.ExtractRope[b]; create: BasicTime.GMT; ok, return: BOOL; bytes: CARD; version: CARDINAL; [ok, return, version, create, bytes] _ him.fileInfo[fileName, b.source]; IF ok THEN { altoTime: LONG CARDINAL = BasicTime.ToPupTime[create]; b.type _ PupType.fileLookupReply; b.fileLookupReply _ [ version: version, createTime: Endian.FFromCard[altoTime], length: Endian.FFromCard[bytes] ]; PupSocket.SetUserSize[b, SIZE[PupBuffer.FileLookupReply]]; } ELSE { b.type _ PupType.fileLookupError; PupSocket.SetUserBytes[b, 0]; }; IF return THEN PupSocket.ReturnToSender[b] ELSE PupSocket.FreeBuffer[b]; }; ENDCASE => PupSocket.ReturnError[b, LOOPHOLE[100B], "File Lookup expected"]; ENDLOOP; }; END. *FTPServer.mesa Last edited by: Bob Hagmann April 7, 1986 9:55:37 am PST Carl Hauser, May 6, 1986 4:45:33 pm PDT Hal Murray, June 4, 1986 0:48:55 am PDT FTP. Internal Procedures ignore comments Single Packet Protocol for File Info Perform the "Single Packet Protocol for File Info". Forked as many times as needed. Init Bob Hagmann April 7, 1986 9:54:30 am PST mapped protocolError to transientError while catching FTP.Failed. protocolError is local and cannot be sent via the protocol. changes to: Retrieve, Store, Enumeration, Delete, Rename Κi– "cedar" style˜Jšœ™™Icode™(K™'K™'—unitšΟk ˜ K˜ K˜Kšœœ ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœœ˜(Kšœ œ˜*Kšœœ ˜Kšœ œŠ˜™Kšœ œR˜aKšœœ˜Kšœœ0˜=K˜Kšœ œœ˜K˜—šœ œ˜Kšœœœ3˜kKšœœ˜ Kšœ˜Kšœœœ˜Kšœœœœ˜K˜Kšœ œΟcB˜YKšœ œœ˜Kšœ œAœ˜ZKšœ œ#˜4K˜Kšœœœ˜)Kšœœ˜"K˜Kšœœ˜ K˜—J˜™Lšœ œœ˜$šœœœœ˜&J˜Jšœœ ˜Kšœ œ ˜Kšœ œ˜!Kšœ#œ˜'Kšœœœœ˜&Kšœ˜—šΟnœœœ.œœœœœPœ œ˜šKšœœ˜$Kšœœ˜4Kšœ!œ$˜KKšœ˜šœ(˜(Kšœ˜Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ˜Kšœž˜-Kšœ œž˜,—Kšœ˜Kšœ˜šœ œœ˜šœ,˜,Kšœ#˜#Kšœ˜Kšœ$˜$—šœ œœ˜4Kšœœœ+˜JKšœ˜—K˜—Kšœ˜ Kšœ˜K˜—šŸœœœ˜.Kšœ&˜&š œœœœœœ˜CKšœ!˜!Kšœ˜—š œœœœœœ˜CKšœœ ˜Kšœ˜—Kšœœœ%˜CJ˜J˜—š Ÿœœœ œœ œ˜CIdefaultšœœ˜$Mšœ˜Mšœ˜Mšœ˜—L˜—™K˜š Ÿœœœœœ œ˜RMšœœ ˜(Kšœœœ˜KšœœœI˜fMšœœœœ˜3Mšœ œœ˜)Mšœ˜M˜—š Ÿœœ œœœœ˜NMšœœ ˜(Kšœ˜Kšœ˜Kšœ˜Mšœ˜M˜—šΟb œœ œœ˜=Jšœ œœ˜Jšœ0˜0˜šœ˜Jšœ9œœ˜JJšœCœœ˜TJš œ œœœ œœ˜UJšœœœ˜9J˜—Jšœ˜Jšœœ˜Jšœœ˜Jšœœ ˜šœœ˜Jšœ˜Jšœœœœ ˜6J˜—Jšœ œ˜Jšœœ˜6Jšœ˜Jšœ.˜.Jšœœ œ/˜Bšœ ˜J˜Kšœœ ˜Jšœ&˜&Jšœ/˜/Jšœ.œ˜7šœ˜šœ ˜ Jšœ˜J˜—šœ ˜ Jšœ"œ˜(JšœC˜CJ˜—šœ ˜ J™Jšœ"œ˜)J˜—šœ ˜ Jšœ˜J˜—šœ˜Jšœ,˜,J˜—šœ˜Jšœ/˜/J˜—šœ ˜ Jšœ˜J˜—šœ ˜ Jšœ˜J˜—šœ˜ Jšœ"œ˜(JšœG˜GJšœ˜——Jšœ˜—š˜Jšœœ˜ —J˜—M˜—šŸœœ œœ˜=J˜Kšœœ ˜Jšœ˜šœœœ˜Cšœœ˜JšœA˜AJšœ˜Jšœ˜J˜—JšœA˜AJšœ˜—Jšœœ˜!Jšœ˜—šŸ œœ(œ˜AJšœ œY˜jJšœœœ6˜YJšœ"œ6œ˜fJšœ˜J˜—šΠbnœœ,˜:Jšœœ˜ šœœ˜ JšœœEœ˜ZJšœ ˜J˜—šœ œ˜$J˜Kšœœ ˜JšœXœ˜^Jšœ&˜&šœ˜šœ˜Jšœ œ˜Jšœ"œ˜(J˜—šœ˜Kšœ"œ˜(Kšœ œ˜K˜—Jšœ,˜3—šœœ˜Jšœ'˜'Jšœ˜J˜—šœœ˜Jšœœ˜ J˜—J˜—Jšœ8œ˜>šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœOœ ˜^Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ˜š˜Jšœ œ˜—Jšœ˜J˜—š‘œœ,˜7Jšœœ˜ šœœ˜ Jšœ'œ˜-JšœœFœ˜[Jšœ ˜J˜—šœ œ˜$J˜Kšœœ ˜JšœXœ˜^Jšœ&˜&šœ˜šœ˜Jšœ˜J˜—šœ˜Kšœ"œ˜(Jšœœ˜ K˜—Jšœ,˜3—J˜—Jšœ8œ˜>šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœLœ ˜[Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ˜š˜Jšœ œ˜—Jšœ˜J˜—š‘ œœQ˜bJšœ œœ˜šœœ˜!Jšœ œœ œ!˜IJšœ œ˜Jšœ"˜"J˜—Jšœ8œ˜>šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœFœ ˜UJšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜šœ œ˜JšœKœ˜QJ˜—Jšœœ˜š˜Jšœ œ˜—Jšœ˜J˜—š‘œœ,˜8Jšœ œ˜šœœ˜ Jšœ œ=œ˜TJšœ ˜J˜—šœ œ˜J˜Kšœœ ˜JšœXœ˜^Jšœ&˜&Jšœ œ˜šœ˜šœ˜Jšœ"œ˜(Jšœœ˜J˜—šœ˜Kšœ"œ˜(Kšœœ˜K˜—Jšœ,˜3—J˜—Jšœ8œ˜>šœ)œœ˜5šœ2œ ˜BJšœ œ˜Jš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœMœ ˜\Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ˜š˜Jšœ œ˜—Jšœ˜—š‘œœ,˜8Jšœ8œ˜?Jšœ7œ˜=šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœ+œ ˜:Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ<œ˜Bš˜Jšœ œ˜—Jšœ˜—J˜š œœœ˜Mšœ˜M˜—š œœœ˜!Mšœ˜M˜—M˜—™$šŸœœœ˜0K™TKšœ.˜.š˜Kšœ,˜,Kšœœœœ˜šœ˜šœ˜Kšœ œ˜*Kšœœ˜Kšœ œ˜Kšœœ˜ Kšœ œ˜KšœH˜Hšœœ˜ Kšœ œœ˜6Kšœ!˜!šœ˜Kšœ˜Kšœ'˜'Kšœ"˜"—Kšœœ˜:K˜—šœœ˜Kšœ!˜!Kšœ˜K˜—Kšœœœ˜HKšœ˜—Kšœœ ˜L—Kšœ˜—Kšœ˜—K˜—™Lšœ˜—J˜J˜™(Kšœ~™~Kšœ Οr,™8—K™—…—2ΞEa