123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- MODULE TFTPServer; (** AUTHOR "be"; PURPOSE "TFTP server"; *)
- IMPORT IP, UDP, Files, Kernel, KernelLog, Random;
- CONST
- Ok = UDP.Ok;
- (* General Settings *)
- TFTPPort = 69;
- MaxSocketRetries = 64;
- MaxRetries = 5;
- MaxWait = 3;
- BlockSize = 512;
- DataTimeout = 3000; (* ms *)
- AckTimeout = 3000; (* ms *)
- (* Packet Types *)
- RRQ = 1;
- WRQ = 2;
- DATA = 3;
- ACK = 4;
- ERROR = 5;
- RRQId = "TFTP RRQ: ";
- WRQId = "TFTP WRQ: ";
- TFTPId = "TFTP Server: ";
- TYPE
- ErrorMsg = ARRAY 32 OF CHAR;
- TFTP = OBJECT
- VAR socket: UDP.Socket;
- fip: IP.Adr;
- lport, fport: LONGINT;
- res: WORD;
- dead: BOOLEAN;
- buf: ARRAY BlockSize + 4 OF CHAR;
- timer: Kernel.Timer;
- (* Log functions *)
- PROCEDURE LogEnter(level: LONGINT);
- BEGIN IF (TraceLevel >= level) THEN KernelLog.Enter END
- END LogEnter;
- PROCEDURE LogExit(level: LONGINT);
- BEGIN IF (TraceLevel >= level) THEN KernelLog.Exit END
- END LogExit;
- PROCEDURE Log(level: LONGINT; CONST s: ARRAY OF CHAR);
- BEGIN IF (TraceLevel >= level) THEN KernelLog.String(s) END
- END Log;
- PROCEDURE LogInt(level, i: LONGINT);
- BEGIN IF (TraceLevel >= level) THEN KernelLog.Int(i, 0) END
- END LogInt;
- (* Get2 - reads a (big endian) 16bit value from 'buf' at position 'ofs'..'ofs'+1 *)
- PROCEDURE Get2(CONST buf: ARRAY OF CHAR; ofs: LONGINT): LONGINT;
- BEGIN RETURN ORD(buf[ofs])*100H + ORD(buf[ofs+1])
- END Get2;
- (* Put2 - writes a (big endian) 16bit value to 'buf' at position 'ofs'..'ofs'+1 *)
- PROCEDURE Put2(VAR buf: ARRAY OF CHAR; ofs, value: LONGINT);
- BEGIN buf[ofs] := CHR(value DIV 100H MOD 100H); buf[ofs+1] := CHR(value MOD 100H)
- END Put2;
- (* PacketType - returns the type of a packet *)
- PROCEDURE PacketType(CONST buf: ARRAY OF CHAR): LONGINT;
- BEGIN RETURN Get2(buf, 0)
- END PacketType;
- (* ExtractString - extracts a 0X terminated 8bit string from a buffer *)
- PROCEDURE ExtractString(CONST buf: ARRAY OF CHAR; VAR ofs: LONGINT; VAR s: ARRAY OF CHAR);
- VAR pos: LONGINT;
- BEGIN
- WHILE (ofs < LEN(buf)) & (buf[ofs] # 0X) DO
- IF (pos < LEN(s)-1) THEN s[pos] := buf[ofs]; INC(pos) END;
- INC(ofs)
- END;
- s[pos] := 0X; INC(ofs)
- END ExtractString;
- (* SendAck - sends an ack packet *)
- PROCEDURE SendAck(blockNr: LONGINT; VAR res: WORD);
- VAR ackHdr: ARRAY 4 OF CHAR; retries: LONGINT;
- BEGIN
- Put2(ackHdr, 0, ACK); Put2(ackHdr, 2, blockNr);
- REPEAT
- INC(retries);
- socket.Send(fip, fport, ackHdr, 0, LEN(ackHdr), res);
- UNTIL (res = Ok) OR (retries > MaxRetries)
- END SendAck;
- (* SendError - sends an error packet *)
- PROCEDURE SendError(errNo: INTEGER; s: ErrorMsg; VAR res: WORD);
- VAR errHdr: ARRAY BlockSize+4 OF CHAR; p, retries: LONGINT;
- BEGIN
- Put2(errHdr, 0, ERROR); Put2(errHdr, 2, errNo);
- IF ((errNo = 0) & (s = "")) OR ((errNo > 0) & (errNo < 8)) THEN s := errorMsg[errNo] END;
- WHILE (p < BlockSize-1) & (s[p] # 0X) DO errHdr[4+p] := s[p]; INC(p) END;
- errHdr[4+p] := 0X;
- REPEAT
- INC(retries);
- socket.Send(fip, fport, errHdr, 0, p+4, res)
- UNTIL (res = Ok) OR (retries > MaxRetries)
- END SendError;
- PROCEDURE Die;
- BEGIN { EXCLUSIVE }
- dead := TRUE
- END Die;
- PROCEDURE AwaitDeath;
- BEGIN { EXCLUSIVE }
- AWAIT(dead)
- END AwaitDeath;
- END TFTP;
- TFTPRRQ = OBJECT(TFTP)
- VAR
- ip: IP.Adr;
- ack: ARRAY 4 OF CHAR;
- port, len, wait, retries, blockNr: LONGINT;
- acked: BOOLEAN;
- file: Files.File;
- r: Files.Rider;
- (* Init - constructor *)
- PROCEDURE &Init*(fip: IP.Adr; fport: LONGINT; CONST filename: Files.FileName; VAR res: WORD);
- VAR retries: LONGINT;
- BEGIN SELF.fip := fip; SELF.fport := fport;
- file := Files.Old(filename);
- IF (file # NIL) THEN
- REPEAT
- INC(retries); lport := 1024 + generator.Integer() MOD 64512;
- NEW(socket, lport, res);
- UNTIL (res # UDP.PortInUse) OR (retries > MaxSocketRetries)
- ELSE
- res := -1
- END
- END Init;
- BEGIN {ACTIVE}
- IF (socket = NIL) THEN RETURN END;
- LogEnter(2); Log(2, RRQId); Log(2, "sending file on port "); LogInt(2, lport); Log(2, "..."); LogExit(2);
- file.Set(r, 0);
- Put2(buf, 0, 3); (* DATA packet *)
- blockNr := 0; acked := TRUE;
- WHILE ~r.eof & acked DO
- INC(blockNr);
- buf[2] := CHR(blockNr DIV 100H); buf[3] := CHR(blockNr MOD 100H);
- file.ReadBytes(r, buf, 4, BlockSize);
- retries := 0;
- REPEAT
- INC(retries);
- LogEnter(3); Log(3, RRQId); Log(3, "sending block "); LogInt(3, blockNr);
- Log(3, " ("); LogInt(3, BlockSize-r.res); Log(3, " bytes) ");
- IF (retries > 1) THEN Log(3, "(retry "); LogInt(3, retries); Log(3, ")") END;
- LogExit(3);
- socket.Send(fip, fport, buf, 0, 4 + BlockSize - r.res, res);
- wait := 0;
- REPEAT
- INC(wait);
- LogEnter(3); Log(3, RRQId); Log(3, "waiting for ack... ");
- IF (wait > 1) THEN Log(3, "(retry "); LogInt(3, wait); Log(3, ")") END;
- LogExit(3);
- acked := FALSE;
- socket.Receive(ack, 0, 4, AckTimeout, ip, port, len, res);
- LogEnter(3); Log(3, RRQId);
- IF (res = UDP.Timeout) THEN Log(3, "timeout")
- ELSIF (res = Ok) THEN
- acked := (res = Ok) & (PacketType(ack) = ACK) & (Get2(ack, 2) = blockNr) & (IP.AdrsEqual(ip, fip)) & (fport = port);
- IF acked THEN Log(3, "got ack") ELSE Log(3, "ack failed") END
- ELSE
- Log(3, "unknown error "); LogInt(3, LONGINT(res))
- END;
- LogExit(3)
- UNTIL acked OR (res # Ok) OR (wait > MaxWait)
- UNTIL acked OR (retries > MaxRetries)
- END;
- LogEnter(2); Log(2, RRQId);
- IF ~acked THEN Log(2, "file not completely sent")
- ELSE Log(2, "file successfully sent")
- END;
- LogExit(2);
- NEW(timer);
- timer.Sleep(AckTimeout+500);
- Die
- END TFTPRRQ;
- TFTPWRQ = OBJECT(TFTP)
- VAR
- ip: IP.Adr;
- port, len, waitPacket, retries, blockNr: LONGINT;
- Abort: BOOLEAN;
- file: Files.File;
- r: Files.Rider;
- (* Init - constructor *)
- PROCEDURE &Init*(fip: IP.Adr; fport: LONGINT; CONST filename: Files.FileName; VAR res: WORD);
- VAR retries: LONGINT;
- BEGIN
- SELF.fip := fip; SELF.fport := fport; res := 0;
- file := Files.Old(filename);
- IF (file = NIL) THEN
- file := Files.New(filename);
- IF (file = NIL) THEN
- LogEnter(1); Log(1, TFTPId); Log(1, "unexpected error: can't create '"); Log(1, filename); Log(1, "'"); LogExit(1);
- res := -1;
- ELSE
- REPEAT
- INC(retries); lport := 1024 + generator.Integer() MOD 64512;
- NEW(socket, lport, res)
- UNTIL (res # UDP.PortInUse) OR (retries > MaxSocketRetries)
- END
- ELSE
- res := -1
- END
- END Init;
- BEGIN {ACTIVE}
- IF (socket = NIL) THEN RETURN END;
- LogEnter(2); Log(2, WRQId); Log(2, "receiving file on port "); LogInt(2, lport); Log(2, "..."); LogExit(2);
- file.Set(r, 0);
- Files.Register(file);
- blockNr := 0;
- SendAck(blockNr, res);
- IF (res = Ok) THEN
- REPEAT
- INC(blockNr);
- LogEnter(3); Log(3, WRQId); Log(3, " receiving block "); LogInt(3, blockNr);
- IF (retries > 1) THEN Log(3, " (retry "); LogInt(3, retries); Log(3, ")") END;
- LogExit(3);
- socket.Receive(buf, 0, LEN(buf), DataTimeout, ip, port, len, res);
- IF (res = Ok) THEN
- IF IP.AdrsEqual(ip, fip) & (fport = port) THEN
- IF (PacketType(buf) = DATA) THEN
- IF (Get2(buf, 2) = blockNr) THEN
- file.WriteBytes(r, buf, 4, len-4);
- file.Update();
- IF (r.res = 0) THEN
- SendAck(blockNr, res);
- Abort := res # Ok
- ELSE
- LogEnter(3); Log(3, WRQId); Log(3, errorMsg[3]); LogExit(3);
- SendError(3, "", res);
- Abort := TRUE
- END
- ELSE (* bad block number, client must send packet again *)
- INC(waitPacket); len := BlockSize;
- LogEnter(3); Log(3, WRQId); Log(3, "Bad block number ("); LogInt(3, waitPacket); Log(3, ")"); LogExit(3)
- END
- ELSE (* wrong packet type *)
- LogEnter(3); Log(3, WRQId); Log(3, errorMsg[4]); LogExit(3);
- SendError(4, "", res);
- Abort := TRUE
- END
- ELSE (* wrong client ip/port *)
- LogEnter(3); Log(3, WRQId); Log(3, errorMsg[5]); LogExit(3);
- SendError(5,"", res)
- END
- ELSIF (res = UDP.Timeout) THEN
- INC(waitPacket); len := BlockSize;
- LogEnter(3); Log(3, WRQId); Log(3, "Timeout ("); LogInt(3, waitPacket); Log(3, ")"); LogExit(3)
- ELSE (* unknown error (UDP/IP error) *)
- LogEnter(3); Log(3, WRQId); Log(3, errorMsg[0]); LogExit(3);
- SendError(0, "", res);
- Abort := TRUE
- END;
- UNTIL Abort OR (waitPacket > MaxWait) OR (len < BlockSize);
- LogEnter(2); Log(2, WRQId);
- IF (len < BlockSize) THEN
- file.Update();
- Log(2, "file successfully received")
- ELSE
- Log(2, "file transfer aborted");
- IF (waitPacket > MaxWait) THEN Log(2, " (timeout)") END
- END;
- LogExit(2)
- ELSE
- LogEnter(2); Log(2, WRQId); Log(2, "can't send initial ack"); LogExit(2);
- END;
- NEW(timer);
- timer.Sleep(AckTimeout+500);
- socket.Close;
- Die
- END TFTPWRQ;
- TFTPServer = OBJECT(TFTP)
- VAR
- ofs,len: LONGINT;
- ipstr, mode: ARRAY 16 OF CHAR;
- filename: Files.FileName;
- Stop, allowWrite: BOOLEAN;
- tftprrq: TFTPRRQ;
- tftpwrq: TFTPWRQ;
- PROCEDURE &Init*(port: LONGINT; VAR res: WORD);
- BEGIN NEW(socket, port, res); lport := port
- END Init;
- PROCEDURE WriteMode(allow: BOOLEAN);
- BEGIN allowWrite := allow
- END WriteMode;
- PROCEDURE Close;
- BEGIN { EXCLUSIVE }
- socket.Close; Stop := TRUE
- END Close;
- BEGIN { ACTIVE }
- IF (res = Ok) THEN
- LogEnter(1); Log(1, TFTPId); Log(1, "listening on port "); LogInt(1, lport); LogExit(1);
- REPEAT
- socket.Receive(buf, 0, LEN(buf), 1000, fip, fport, len, res);
- IF (res = Ok) THEN
- IP.AdrToStr(fip, ipstr);
- LogEnter(2);
- Log(2, TFTPId); Log(2, "connected to "); Log(2, ipstr); Log(2, " on port "); LogInt(2, fport);
- LogExit(2);
- CASE PacketType(buf) OF
- | RRQ:
- ofs := 2;
- ExtractString(buf, ofs, filename); ExtractString(buf, ofs, mode);
- LogEnter(2);
- Log(2, TFTPId); Log(2, "read request for '"); Log(2, filename); Log(2, "', mode '"); Log(2, mode); Log(2, "' ");
- LogExit(2);
- NEW(tftprrq, fip, fport, filename, res); tftprrq := NIL;
- IF (res = -1) THEN
- LogEnter(2); Log(2, TFTPId); Log(2, "read request: "); Log(2, errorMsg[1]); LogExit(2);
- SendError(1, "", res)
- ELSIF (res # Ok) THEN
- LogEnter(2); Log(2, TFTPId); Log(2, "read request: "); Log(2, ": error "); LogInt(2, LONGINT(res)); LogExit(2);
- SendError(0, "", res)
- ELSE
- LogEnter(2); Log(2, TFTPId); Log(2, "read request: "); Log(2, ": transfer started"); LogExit(2)
- END
- | WRQ:
- ofs := 2;
- ExtractString(buf, ofs, filename); ExtractString(buf, ofs, mode);
- LogEnter(2);
- Log(2, TFTPId); Log(2, "write request for '"); Log(2, filename); Log(2, "', mode '"); Log(2, mode); Log(2, "' ");
- LogExit(2);
- IF allowWrite THEN
- NEW(tftpwrq, fip, fport, filename, res); tftpwrq := NIL;
- IF (res = -1) THEN
- LogEnter(2); Log(2, TFTPId); Log(2, "write request: "); Log(2, errorMsg[6]); LogExit(2);
- SendError(6, "", res)
- ELSIF (res # Ok) THEN
- LogEnter(2); Log(2, TFTPId); Log(2, "write request: "); Log(2, ": error "); LogInt(2, LONGINT(res)); LogExit(2);
- SendError(0, "", res)
- ELSE
- LogEnter(2); Log(2, TFTPId); Log(2, "write request: "); Log(2, ": transfer started"); LogExit(2)
- END
- ELSE
- LogEnter(2); Log(2, TFTPId); Log(2, "write request: "); Log(2, errorMsg[2]); LogExit(2);
- SendError(2, "", res)
- END
- ELSE LogEnter(2); Log(2, TFTPId); Log(2, "Invalid request"); LogExit(2)
- END
- ELSIF (res = UDP.Timeout) THEN (* nothing *)
- ELSE
- Stop := TRUE;
- LogEnter(2); Log(2, TFTPId); Log(2, "socket error "); LogInt(2, LONGINT(res)); LogExit(2);
- END
- UNTIL Stop;
- END;
- Die
- END TFTPServer;
- VAR
- tftpserver: TFTPServer;
- TraceLevel: LONGINT;
- errorMsg: ARRAY 8 OF ErrorMsg;
- generator: Random.Generator;
- PROCEDURE Start*;
- VAR res: WORD;
- BEGIN
- IF (tftpserver = NIL) THEN
- KernelLog.Enter; KernelLog.String("Starting TFTP Server..."); KernelLog.Exit;
- NEW(tftpserver, TFTPPort, res);
- IF (res # UDP.Ok) THEN
- tftpserver := NIL;
- KernelLog.Enter; KernelLog.String("TFTP Server: UDP port not available"); KernelLog.Exit
- END
- ELSE
- KernelLog.Enter; KernelLog.String("TFTP Server: already running"); KernelLog.Exit
- END
- END Start;
- PROCEDURE Stop*;
- BEGIN
- IF (tftpserver # NIL) THEN
- tftpserver.Close; tftpserver.AwaitDeath; tftpserver := NIL;
- KernelLog.Enter; KernelLog.String("TFTP Server stopped"); KernelLog.Exit
- ELSE
- KernelLog.Enter; KernelLog.String("TFTP Server not running"); KernelLog.Exit
- END
- END Stop;
- PROCEDURE AllowWrite*;
- BEGIN
- IF (tftpserver # NIL) THEN
- tftpserver.WriteMode(TRUE);
- KernelLog.Enter; KernelLog.String("TFTP Server: writing allowed"); KernelLog.Exit
- ELSE
- KernelLog.Enter; KernelLog.String("TFTP Server: not running. use TFTPServer.Start"); KernelLog.Exit
- END
- END AllowWrite;
- PROCEDURE DenyWrite*;
- BEGIN
- IF (tftpserver # NIL) THEN
- tftpserver.WriteMode(FALSE);
- KernelLog.Enter; KernelLog.String("TFTP Server: writing denied"); KernelLog.Exit;
- ELSE
- KernelLog.Enter; KernelLog.String("TFTP Server: not running. use TFTPServer.Start"); KernelLog.Exit
- END
- END DenyWrite;
- PROCEDURE TraceLevel0*;
- BEGIN TraceLevel := 0
- END TraceLevel0;
- PROCEDURE TraceLevel1*;
- BEGIN TraceLevel := 1
- END TraceLevel1;
- PROCEDURE TraceLevel2*;
- BEGIN TraceLevel := 2
- END TraceLevel2;
- PROCEDURE TraceLevel3*;
- BEGIN TraceLevel := 3
- END TraceLevel3;
- BEGIN
- errorMsg[0] := "Undefined error.";
- errorMsg[1] := "File not found.";
- errorMsg[2] := "Access violation.";
- errorMsg[3] := "Disk full.";
- errorMsg[4] := "Illegal TFTP operation.";
- errorMsg[5] := "Unknown transfer ID.";
- errorMsg[6] := "File already exists.";
- errorMsg[7] := "No such user.";
- TraceLevel := 2;
- NEW(generator)
- END TFTPServer.
- System.Free TFTPServer ~
- TFTPServer.Start
- TFTPServer.Stop
- TFTPServer.AllowWrite
- TFTPServer.DenyWrite
- TFTPServer.TraceLevel0
- TFTPServer.TraceLevel1
- TFTPServer.TraceLevel2
- TFTPServer.TraceLevel3
|