www.pudn.com > TFTPUtil_Source_Version_1.3.0.zip > TFTPServerProcess.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using NSpring.Logging;
using NSpring.Logging.Loggers;
using System.Diagnostics;
using System.Reflection;
namespace TFTPUtil
{
///
/// The TFTPUtil Server Class
///
public class TFTPServerProcess
{
#region VARS
private TFTPState State;
///
/// The GUID for this connection
///
public readonly Guid ident = Guid.NewGuid();
private Logger logger;
private int TimeoutCounter = 0;
private int ResendInterval = 1; //How many seconds before resending
private int Timeout = 10; //How many seconds before counting TID timedout
private bool sentOACK = false;
///
/// Allow RFC 2347 (TFTP Option Extension)
///
public bool AllowOptions = true;
///
/// Specifies if we allow read requests
///
public bool AllowRRQ = true;
///
/// Specifies if we allow write requests
///
public bool AllowWRQ = false;
///
/// Specifies if write requests can overwrite an existing file
///
public bool AllowWRQOverwrite = false;
private int ListenerPort = 69; //The UDP port we should be listening for requests on
private const int MaxRecvSize = 65468; //Maximum receive size in bytes
private bool Loop = false; //Controls if we should be receiving datagrams
///
/// Check for existing TIDs in RRQ and WRQ requests
///
public bool RRQWRQStateCheck = true;
private IPEndPoint myEndpoint; //The IPEndPoint that the server is using
private Socket mySocket; //The socket the server is using
private const int DefaultBlockSize = 512;
private bool ClientMode = false;
private bool PutClientMode = false;
private string ClientHost;
private string FullPath = System.IO.Directory.GetCurrentDirectory();
///
/// The TFTP server event handler
///
public event TFTPServerProcessEventHandler TFTPServerProcessEvent;
private readonly object CurrStatesLock = new object();
private Level EventLevel = Level.Info;
private Level LogLevel = Level.Info;
///
/// A byte array of ASCII characters representing the TFTP error number 1
///
public readonly byte[] Error1 = { 0, 5, 0, 1, 70, 105, 108, 101, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100, 46, 0 };
///
/// A byte array of ASCII characters representing the TFTP error number 2
///
public readonly byte[] Error2 = { 0, 5, 0, 2, 65, 99, 99, 101, 115, 115, 32, 118, 105, 111, 108, 97, 116, 105, 111, 110, 46, 0 };
///
/// A byte array of ASCII characters representing the TFTP error number 3
///
public readonly byte[] Error3 = { 0, 5, 0, 3, 68, 105, 115, 107, 32, 102, 117, 108, 108, 32, 111, 114, 32, 97, 108, 108, 111, 99, 97, 116, 105, 111, 110, 32, 101, 120, 99, 101, 101, 100, 101, 100, 46, 0 };
///
/// A byte array of ASCII characters representing the TFTP error number 4
///
public readonly byte[] Error4 = { 0, 5, 0, 4, 73, 108, 108, 101, 103, 97, 108, 32, 84, 70, 84, 80, 32, 111, 112, 101, 114, 97, 116, 105, 111, 110, 46, 0 };
///
/// A byte array of ASCII characters representing the TFTP error number 5
///
public readonly byte[] Error5 = { 0, 5, 0, 5, 85, 110, 107, 110, 111, 119, 110, 32, 116, 114, 97, 110, 115, 102, 101, 114, 32, 73, 68, 46, 0 };
///
/// A byte array of ASCII characters representing the TFTP error number 6
///
public readonly byte[] Error6 = { 0, 5, 0, 6, 70, 105, 108, 101, 32, 97, 108, 114, 101, 97, 100, 121, 32, 101, 120, 105, 115, 116, 115, 46, 0 };
///
/// A byte array of ASCII characters representing the TFTP error number 7
///
public readonly byte[] Error7 = { 0, 5, 0, 7, 78, 111, 32, 115, 117, 99, 104, 32, 117, 115, 101, 114, 46, 0 };
///
/// A byte array of ASCII characters representing the TFTP error number 8
///
public readonly byte[] Error8 = { 0, 5, 0, 8, 84, 114, 97, 110, 115, 102, 101, 114, 32, 116, 101, 114, 109, 105, 110, 97, 116, 101, 100, 32, 100, 117, 101, 32, 116, 111, 32, 111, 112, 116, 105, 111, 110, 32, 110, 101, 116, 105, 97, 116, 105, 111, 110, 46, 0};
///
/// A byte array of ASCII characters representing the "unknown error"
///
public readonly byte[] ErrorUnknown = { 0, 5, 0, 0, 65, 110, 32, 117, 110, 107, 110, 107, 110, 111, 119, 110, 32, 101, 114, 114, 111, 114, 32, 111, 99, 99, 117, 114, 114, 101, 100, 46, 0 };
#endregion
///
/// Initalizes an instance of the TFTPServerProcess class with defaults values
///
public TFTPServerProcess()
{
//randomly generate the port to connect
Random rnd = new Random();
IPHostEntry localMachineInfo = Dns.GetHostEntry(Dns.GetHostName());
myEndpoint = new IPEndPoint(localMachineInfo.AddressList[0], 0);
}
///
/// Initalizes an instance of the TFTPServerProcess class
///
/// A string specifying the path the TFTP server should retrieve files from
/// The NSpring level to log messages via the specified method
/// The NSpring level to log messages
/// Allow read requests
/// Allow write requests
/// Allow write requests to overwrite existing files
/// If we shoud allow TFTP option extensions from TFTP clients
/// Defines if we should check for TIDs in read and write requests
/// An integer in milliseconds specifying how long before resending the last packet
/// An integer in milliseconds specifying how long before the request is timed out
/// A NSpring logger to use
/// An IPAddress that we should listen on
public TFTPServerProcess(string FilePath,
Level LoggingLevel,
Level DisplayLevel,
bool AllowRRQ,
bool AllowWRQ,
bool AllowWRQOverwrite,
bool AllowTFTPOptions,
bool CheckReadWriteState,
int ResendInterval,
int TimeoutInterval,
Logger logger,
IPAddress srvaddr)
{
//randomly generate the port to connect
Random rnd = new Random();
IPHostEntry localMachineInfo = Dns.GetHostEntry(Dns.GetHostName());
myEndpoint = new IPEndPoint(localMachineInfo.AddressList[0], 0);
for (int i = 0; i < localMachineInfo.AddressList.Length; i++)
{
if (localMachineInfo.AddressList[i].Equals(srvaddr))
{
myEndpoint = new IPEndPoint(localMachineInfo.AddressList[i], 0);
i = localMachineInfo.AddressList.Length;
}
}
FullPath = FilePath;
this.LogLevel = LoggingLevel;
EventLevel = DisplayLevel;
AllowOptions = AllowTFTPOptions;
this.AllowRRQ = AllowRRQ; ;
this.AllowWRQ = AllowWRQ;
this.AllowWRQOverwrite = AllowWRQOverwrite;
RRQWRQStateCheck = CheckReadWriteState;
this.ResendInterval = ResendInterval;
Timeout = TimeoutInterval;
this.logger = logger;
}
///
/// Stops the TFTP server from listening for requests
///
public void StopListener()
{
//System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(2, true);
//System.Diagnostics.StackFrame sf = st.GetFrame(0);
//AddMsg(Level.Debug, ident.ToString() + ": StopListener called by " + sf.ToString());
AddMsg(Level.Verbose, ident.ToString() + ": Process stopping Listening.");
Loop = false;
try
{
// We only need to stop receiving so we can still send our last packet
if (mySocket != null)
{
mySocket.Shutdown(SocketShutdown.Receive);
//mySocket.Close(Timeout*ResendInterval);
}
}
catch (ObjectDisposedException ex)
{
StackFrame stackFrame = new StackFrame();
MethodBase methodBase = stackFrame.GetMethod();
AddMsg(Level.Verbose, "Caught socket object disposed exception in " + methodBase.Name);
AddMsg(Level.Debug, "Socket exception: " + ex.Message + "\n" + ex.StackTrace);
}
lock (CurrStatesLock)
{
if (State != null)
{
State.Close();
//State = null;
}
}
AddMsg(Level.Debug, ident.ToString() + ": TFTPServer Process Thread stopped listening on " + myEndpoint.Address.ToString() + ":" + ListenerPort.ToString());
}
///
/// Deconstructor
///
~TFTPServerProcess()
{
//CurrStates.Clear();
if (State != null)
{
State.Close();
State = null;
}
if ((mySocket != null))
{
try
{
mySocket.Shutdown(SocketShutdown.Both);
mySocket.Close();
}
catch (ObjectDisposedException ex)
{
StackFrame stackFrame = new StackFrame();
MethodBase methodBase = stackFrame.GetMethod();
AddMsg(Level.Verbose, "Caught socket object disposed exception in " + methodBase.Name);
AddMsg(Level.Debug, "Socket exception: " + ex.Message + "\n" + ex.StackTrace);
}
}
}
public void MyClose()
{
mySocket.Close();
mySocket = null;
}
///
/// Starts the TFTP server listening for requests
///
public void StartListener()
{
try
{
AddMsg(Level.Verbose, ident.ToString() + ": Creating Socket");
mySocket = new Socket(myEndpoint.Address.AddressFamily, SocketType.Dgram, ProtocolType.Udp);
AddMsg(Level.Verbose, ident.ToString() + ": Binding Socket");
mySocket.Bind(myEndpoint);
ListenerPort = ((IPEndPoint)mySocket.LocalEndPoint).Port;
Loop = true;
AddMsg(Level.Verbose, ident.ToString() + ": Process starting to Listen");
AddMsg(Level.Debug, ident.ToString() + ": TFTPServer Process Thread listening for requests on IP address " + myEndpoint.Address.ToString() + " port " + myEndpoint.Port.ToString());
StartReceive();
}
catch (SocketException ex)
{
Loop = false;
if (ex.ErrorCode == 10048)
{
AddMsg(Level.Exception, "Unable to start listening on " + myEndpoint.Address.ToString() + " port " + myEndpoint.Port.ToString() + ". There maybe another service listening on that port.");
}
else
{
StackFrame stackFrame = new StackFrame();
MethodBase methodBase = stackFrame.GetMethod();
AddMsg(Level.Verbose, ident.ToString() + ": Handled " + methodBase.Name + " socket exception: " + ex.Message);
AddMsg(Level.Debug, ident.ToString() + ": Handled " + methodBase.Name + " socket exception at: " + ex.StackTrace);
Loop = false;
}
}
}
///
/// Check the state of the current TFTP session
///
public void CheckStates()
{
//System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(2, true);
//System.Diagnostics.StackFrame sf = st.GetFrame(0);
//AddMsg(Level.Debug, ident.ToString() + ": Process CheckStates" + sf.ToString());
AddMsg(Level.Debug, ident.ToString() + ": " + DateTime.Now.ToString() + "Process CheckStates timer callback started.");
lock (CurrStatesLock)
{
if (State != null)
{
if (State.TransferState.Closed)
{
AddMsg(Level.Verbose, ident.ToString() + ": " + DateTime.Now.ToString() + " CheckStates timer calling stoplistener because the state is closed.");
StopListener();
}
else
{
if ((DateTime.Now.Ticks - State.Timestamp) > 0)
{
if (((DateTime.Now.Ticks - State.Timestamp) / 10000000) > State.TimeoutSeconds)
{
//If a state has had no activity for the specified interval remove it
AddMsg(Level.Info, "Timeout for " + State.TransferType + " request of file " + State.Filename + " connection " + State.RemoteIPAddress.ToString() + ":" + State.RemotePortNumber.ToString());
AddMsg(Level.Verbose, "Timeout was " + ((DateTime.Now.Ticks - State.Timestamp) / 10000000).ToString() + " seconds");
State.ErrorOccurred = true;
State.ErrorMsg = "Timeout occured for " + State.TransferType + " request of file " + State.Filename + " from " + State.RemoteIPAddress.ToString();
State.Close();
}
else if ((((DateTime.Now.Ticks - State.Timestamp) / 10000000) > State.TimeoutSeconds) && (State.OriginalOpcode == 2))
{
//If we didn't get another DATA packet since our last ACK resend
AddMsg(Level.Info, "Resending ACK to " + State.RemoteIPAddress.ToString() + ":" + State.RemotePortNumber.ToString());
IPEndPoint RemoteEndPoint = new IPEndPoint(IPAddress.Parse(State.RemoteIPAddress), State.RemotePortNumber);
Send(RemoteEndPoint, new byte[] { 0, 4, State.BlockIDByte1, State.BlockIDByte2 });
}
}
}
}
else if (TimeoutCounter > Timeout)
{
AddMsg(Level.Verbose, ident.ToString() + ": Stopping listener because no active sessions for timeout peroid.");
this.StopListener();
}
else
{
//The listener should not be running if there is not an active session
++TimeoutCounter;
}
}
AddMsg(Level.Debug, ident.ToString() + ": " + "Process CheckStates timer callback finished " + DateTime.Now.ToString());
}
private void StartReceive()
{
while (Loop)
{
bool ReceivedWorked = false;
//Setup maximum bytes to receive
Byte[] ReceivedBytes = new Byte[MaxRecvSize];
//Create an EndPoint that will connect to any IP and any port
EndPoint tempPoint = new IPEndPoint(IPAddress.Any, 0);
AddMsg(Level.Verbose, "Waiting for datagram...");
int NumBytesReceived = 0;
try
{
NumBytesReceived = mySocket.ReceiveFrom(ReceivedBytes, ref tempPoint);
ReceivedWorked = true;
}
catch (Exception ex)
{
//10004 occurs when we close the socket and we were still blocking on receivefrom
if (ex.GetType().FullName == "System.Net.Sockets.SocketException" && ((SocketException)ex).ErrorCode != 10004)
{
if (Loop)
{
AddMsg(Level.Info, "An error occurred while trying receive a packet");
AddMsg(Level.Verbose, "Handled receive packet exception: " + ex.Message);
AddMsg(Level.Debug, "Handled receive packet exception at: " + ex.StackTrace);
}
this.StopListener();
}
}
if (ReceivedWorked)
{
IPEndPoint RemoteEndPoint = (IPEndPoint)tempPoint;
AddMsg(Level.Verbose, "Received datagram from " + ((IPEndPoint)RemoteEndPoint).Address.ToString() + ":" + ((IPEndPoint)RemoteEndPoint).Port.ToString());
bool FoundMatch = true;
if (ClientMode)
{
AddMsg(Level.Debug, "Entered ClientMode");
FoundMatch = false;
bool IsIP = false;
try
{
IPAddress HostAddr = IPAddress.Parse(ClientHost);
IsIP = true;
if (RemoteEndPoint.Address.Equals(HostAddr))
FoundMatch = true;
}
catch
{
AddMsg(Level.Debug, "ClientHost \"" + ClientHost + "\" was not an IP address");
}
if (IsIP == false)
{
foreach (IPAddress ip in Dns.GetHostAddresses(ClientHost))
{
if (ip == RemoteEndPoint.Address)
FoundMatch = true;
}
}
}
if (FoundMatch)
{
ProcessDatagram(ReceivedBytes, NumBytesReceived, RemoteEndPoint);
}
}
}
}
///
/// Processes TFTP bytes
///
/// The array of bytes received from the RemoteEndPoint
/// The number of bytes received from the RemoteEndPoint
/// An IPEndPoint that is the remote TFTP device
public void ProcessDatagram(byte[] ReceivedBytes, int NumBytesReceived, IPEndPoint RemoteEndPoint)
{
int CurrOpcode = Convert.ToInt16(ReceivedBytes[1]);
string EndPointString = ((IPEndPoint)RemoteEndPoint).Address.ToString() + ":" + ((IPEndPoint)RemoteEndPoint).Port.ToString();
AddMsg(Level.Verbose, ident.ToString() + ": Found Opcode " + CurrOpcode.ToString() + " from " + EndPointString);
bool StateChk = true;
lock (CurrStatesLock)
{
StateChk = CheckForExistingTID(RemoteEndPoint);
}
string[] FilenameType = new string[2];
switch (CurrOpcode)
{
case 1: //RRQ
#region RRQ
if (AllowRRQ && (NumBytesReceived <= 516))
{
lock (CurrStatesLock)
{
AddMsg(Level.Verbose, ident.ToString() + ": Processing RRQ from " + EndPointString);
string[] FoundOptions = FindOptions(ReceivedBytes, NumBytesReceived);
if (RRQWRQStateCheck && (StateChk))
{
//If we find an existing TID we should send them an error and delete he state
//We should not have an existing TID for an RRQ or WRQ request
AddMsg(Level.Verbose, ident.ToString() + ": Sending unknown error generated in RRQ because of TID check to " + EndPointString);
SendUnknownError(RemoteEndPoint);
State.Close();
}
else if ((StateChk == false) && (FoundOptions[0] != "") && ((FoundOptions[1] == "octet") || (FoundOptions[1] == "netascii")))
{
State = CreateNewState(RemoteEndPoint, CurrOpcode, FoundOptions[0], FoundOptions[1], DefaultBlockSize);
if (SendOptions(FoundOptions, State, RemoteEndPoint) != false)
{
byte[] DataBytes = { };
try
{
DataBytes = State.GetData(0, 0);
byte[] SendBytes = new byte[DataBytes.Length + 4];
DataBytes.CopyTo(SendBytes, 4);
SendBytes[1] = 3; //DATA
SendBytes[3] = 1; //Block ID 1
AddMsg(Level.Verbose, ident.ToString() + ": Sending first data to " + EndPointString);
Send(RemoteEndPoint, SendBytes);
}
catch (System.IO.IOException ex)
{
State.ErrorOccurred = true;
//I hate searching for text in a message but I don't want to keep a list of IDs to handle either
if (ex.Message.ToLower().Contains("could not find"))
{
AddMsg(Level.Info, ex.Message + " requested by " + EndPointString);
State.ErrorMsg = ex.Message + " requested by " + EndPointString;
}
else
{
AddMsg(Level.Info, "A problem occurred during a read request from " + EndPointString);
State.ErrorMsg = "A problem occurred during a read request from " + EndPointString;
}
AddMsg(Level.Verbose, "Handled RRQ file exception: " + ex.Message);
AddMsg(Level.Debug, "Handled RRQ file exception: " + ex.StackTrace);
//Send file not found error
Send(RemoteEndPoint, Error1);
State.Close();
}
}
else
{
AddMsg(Level.Verbose, ident.ToString() + ": Sending tftp error 8 generated in RRQ to " + EndPointString);
Send(RemoteEndPoint, Error8);
State.ErrorOccurred = true;
State.ErrorMsg = "An error occured while processing TFTP options from " + EndPointString;
State.Close();
}
}
else
{
AddMsg(Level.Verbose, ident.ToString() + ": Sending unknown error generated in RRQ to " + EndPointString);
State.ErrorOccurred = true;
State.ErrorMsg = "An unknown error occured while processing a write request from " + EndPointString;
SendUnknownError(RemoteEndPoint);
State.Close();
}
}
}
else
{
Send(RemoteEndPoint, Error2);
}
break;
#endregion
case 2: //WRQ
#region WRQ
if (AllowWRQ && (NumBytesReceived <= 516))
{
lock (CurrStatesLock)
{
AddMsg(Level.Verbose, ident.ToString() + ": Processing WRQ from " + EndPointString);
//FilenameType = GetFilenameType(ReceivedBytes);
string[] FoundOptions = FindOptions(ReceivedBytes, NumBytesReceived);
if (RRQWRQStateCheck && (StateChk))
{
//If we find an existing TID we should send them an error and delete he state
//We should not have an existing TID for an RRQ or WRQ request
AddMsg(Level.Verbose, ident.ToString() + ": Sending unknown error generated in WRQ to " + EndPointString);
SendUnknownError(RemoteEndPoint);
State.Close();
}
else if ((StateChk == false) && (FoundOptions[0] != "") && ((FoundOptions[1] == "octet") || (FoundOptions[1] == "netascii")))
{
State = CreateNewState(RemoteEndPoint, CurrOpcode, FoundOptions[0], FoundOptions[1], DefaultBlockSize);
if (SendOptions(FoundOptions, State, RemoteEndPoint) != false)
{
// Only sent ack to wrq if you are not doing options
if (sentOACK == false)
{
AddMsg(Level.Verbose, ident.ToString() + ": Sending ACK for WRQ to " + EndPointString);
Send(RemoteEndPoint, new byte[] { 0, 4, 0, 0 }); //ACK with Block ID 0
}
}
else
{
AddMsg(Level.Verbose, ident.ToString() + ": Sending tftp error 8 generated in RRQ to " + EndPointString);
Send(RemoteEndPoint, Error8);
State.ErrorOccurred = true;
State.ErrorMsg = "An error occured while processing TFTP options from " + EndPointString;
State.Close();
}
}
else
{
AddMsg(Level.Verbose, ident.ToString() + ": Sending unknown error generated in WRQ to " + EndPointString);
State.ErrorOccurred = true;
State.ErrorMsg = "An unknown error occured while processing a write request from " + EndPointString;
SendUnknownError(RemoteEndPoint);
}
}
}
else
{
Send(RemoteEndPoint, Error2);
}
break;
#endregion
case 3: //DATA
#region DATA
AddMsg(Level.Verbose, ident.ToString() + ": Processing DATA for block " + ReceivedBytes[3].ToString() + " from " + EndPointString);
if (StateChk)
{
if (NumBytesReceived <= (State.BlockSize + 4))
{
try
{
if ((ReceivedBytes[2] == 0) && (ReceivedBytes[3] == 1) && (State.BlocksizeOption || State.TimeoutOption))
{
AddMsg(Level.Debug, ident.ToString() + ": Got a data packet so options were accepted");
lock (CurrStatesLock)
{
if (State.BlocksizeOption)
State.BlocksizeOptionACK = true;
if (State.TimeoutOption)
State.TimeoutOptionACK = true;
}
}
AddMsg(Level.Verbose, ident.ToString() + ": Starting to write DATA received form " + EndPointString);
byte[] WriteBytes = new byte[NumBytesReceived - 4];
Array.ConstrainedCopy(ReceivedBytes, 4, WriteBytes, 0, NumBytesReceived - 4);
lock (CurrStatesLock)
{
State.WriteData(WriteBytes, ReceivedBytes[2], ReceivedBytes[3]);
State.BlockIDByte1 = ReceivedBytes[2];
State.BlockIDByte2 = ReceivedBytes[3];
}
//Send ACK for last DATA block
AddMsg(Level.Verbose, ident.ToString() + ": Sending ACK for DATA to " + EndPointString);
Send(RemoteEndPoint, new byte[] { 0, 4, ReceivedBytes[2], ReceivedBytes[3] });
lock (CurrStatesLock)
{
if ((NumBytesReceived - 4) < State.BlockSize)
{
long TotalTime = (State.StopTransferTicks - State.StartTransferTicks) / 10000000;
long Rate;
if (TotalTime > 0)
Rate = State.Filesize / TotalTime;
else
Rate = State.Filesize;
AddMsg(Level.Info, "Successfully received file " + State.Filename + " (" + State.Filesize.ToString() + " bytes) from " + State.RemoteIPAddress + " in " + TotalTime.ToString() + " seconds (" + (Rate).ToString() + " bytes/sec).");
//This is the last block so go ahead and setup to delete state
State.Close();
StopListener();
}
}
}
catch (System.IO.IOException ex)
{
//We probably need to come back to this later and make find what exception we got and send the right tftp error
AddMsg(Level.Info, "A problem occurred while trying to write to the file from " + EndPointString);
AddMsg(Level.Verbose, "Handled DATA exception: " + ex.Message);
AddMsg(Level.Debug, "Handled DATA exception: " + ex.StackTrace);
Send(RemoteEndPoint, Error1);
lock (CurrStatesLock)
{
State.ErrorOccurred = true;
State.ErrorMsg = "A problem occurred while trying to write to the file from " + EndPointString;
State.Close();
}
}
}
else
{
AddMsg(Level.Info, EndPointString + " sent us more data than we expected, dropping packet.");
}
}
else
{
//Couldn't find TID so send ERR
AddMsg(Level.Verbose, ident.ToString() + ": Sending ERR because we found no TID for " + EndPointString);
lock (CurrStatesLock)
{
if (State != null)
{
State.ErrorOccurred = true;
State.ErrorMsg = "Could not find an existing session for TFTP request";
}
}
Send(RemoteEndPoint, Error5);
}
break;
#endregion
case 4: //ACK
#region ACK
AddMsg(Level.Verbose, "Processing ACK for block " + ReceivedBytes[3].ToString() + " from " + EndPointString);
if (StateChk)
{
lock (CurrStatesLock)
{
if (State.SentRecvLastBlock)
{
//We got an ACK for the last block so go ahead and delete the state
long TotalTime = (State.StopTransferTicks - State.StartTransferTicks) / 10000000;
long Rate;
if (TotalTime > 0)
Rate = State.Filesize / TotalTime;
else
Rate = State.Filesize;
AddMsg(Level.Info, "Successfully sent file " + State.Filename + " (" + State.Filesize.ToString() + " bytes) to " + State.RemoteIPAddress + " in " + TotalTime.ToString() + " seconds (" + (Rate).ToString() + " bytes/sec).");
AddMsg(Level.Verbose, "Removing state for " + State.RemoteIPAddress + ":" + State.RemotePortNumber);
//CurrStates.RemoveAt(StateIndex);
State.Close();
StopListener();
}
else
{
if ((ReceivedBytes[3] == 0) && (ReceivedBytes[4] == 0) && (State.BlocksizeOption || State.TimeoutOption))
{
AddMsg(Level.Debug, ident.ToString() + ": Got an ACK so options were accepted");
if (State.BlocksizeOption)
State.BlocksizeOptionACK = true;
if (State.TimeoutOption)
State.TimeoutOptionACK = true;
}
SendNextDataDatagram(ReceivedBytes, RemoteEndPoint, EndPointString);
}
}
}
else
{
//Couldn't find TID so send ERR
AddMsg(Level.Verbose, ident.ToString() + ": Sending error generated in ACK to " + EndPointString);
Send(RemoteEndPoint, Error5);
if (StateChk)
{
lock (CurrStatesLock)
{
State.Close();
}
}
}
break;
#endregion
case 5: //ERROR
#region ERROR
lock (CurrStatesLock)
{
State.ErrorOccurred = true;
ArrayList ErrorCharList = new ArrayList(5);
for (int index = 4; index < NumBytesReceived; index++)
{
if (ReceivedBytes[index] != 0)
{
ErrorCharList.Add(ReceivedBytes[index]);
}
else
{
break;
}
}
string errorMsg = System.Text.Encoding.ASCII.GetString((byte[])ErrorCharList.ToArray(typeof(byte)));
State.ErrorMsg = "Received TFTP error message \"" + errorMsg + "\" from " + EndPointString;
AddMsg(Level.Info, "Received TFTP error message \"" + errorMsg + "\" from " + EndPointString);
StopListener();
if (StateChk)
{
State.Close();
}
}
break;
#endregion
case 6: //OACK
#region OACK
string[] OACKOptions = FindOptions(ReceivedBytes, NumBytesReceived);
if (AllowOptions && (OACKOptions.Length > 2))
{
try
{
for (int i = 2; i < OACKOptions.Length; i++)
{
switch (OACKOptions[i].ToLower())
{
case "tsize": //RFC 2349
lock (CurrStatesLock)
{
State.TransferSizeOption = true;
}
break;
case "timeout": //RFC 2349
lock (CurrStatesLock)
{
State.TimeoutOptionACK = true;
}
break;
case "blksize": //RFC 2348
lock (CurrStatesLock)
{
State.BlocksizeOptionACK = true;
}
break;
case "multicast": //RFC 2090
break;
case "sbroadcast": //Saw this in some IBM doc but can't find an RFC
break;
default:
AddMsg(Level.Debug, "Found unknown option: " + OACKOptions[i]);
break;
}
i++; //Increment because we use them in pairs
}
}
catch (Exception ex)
{
AddMsg(Level.Warning, "A problem occurred when parsing the received packet");
AddMsg(Level.Verbose, "Handled exception in oack : " + ex.Message);
AddMsg(Level.Debug, "Handled oack exception: " + ex.StackTrace);
}
}
if (ClientMode && PutClientMode)
{
SendNextDataDatagram(new byte[] { 0, 0, 0, 0, }, RemoteEndPoint, EndPointString);
}
break;
#endregion
default:
AddMsg(Level.Verbose, ident.ToString() + ": Sending error4 generated in default case " + EndPointString);
Send(RemoteEndPoint, Error4);
StopListener();
break;
}
}
private void SendNextDataDatagram(byte[] ReceivedBytes, IPEndPoint RemoteEndPoint, string EndPointString)
{
try
{
byte[] DataBytes = State.GetData(ReceivedBytes[2], ReceivedBytes[3]);
byte[] SendBytes = new byte[DataBytes.Length + 4];
DataBytes.CopyTo(SendBytes, 4);
SendBytes[1] = 3;
SendBytes[2] = State.BlockIDByte1;
SendBytes[3] = State.BlockIDByte2;
AddMsg(Level.Verbose, ident.ToString() + ": Sending bytes to " + EndPointString);
Send(RemoteEndPoint, SendBytes);
}
catch (System.IO.IOException ex)
{
AddMsg(Level.Info, "A problem occurred while trying to read the file and send to " + EndPointString);
AddMsg(Level.Info, ex.Message);
AddMsg(Level.Verbose, "Handled ACK exception: " + ex.Message);
AddMsg(Level.Debug, "Handled ACK exception: " + ex.StackTrace);
Send(RemoteEndPoint, Error1);
State.Close();
StopListener();
}
}
private void Send(IPEndPoint RemoteEndPoint, byte[] Data)
{
try
{
mySocket.BeginSendTo(Data, 0, Data.Length, SocketFlags.None, RemoteEndPoint, new AsyncCallback(SendCallback), mySocket);
}
catch (Exception ex)
{
AddMsg(Level.Info, "A problem occurred while trying to send a packet to " + RemoteEndPoint.Address.ToString() + ":" + RemoteEndPoint.Port.ToString());
AddMsg(Level.Verbose, "Handled exception in send : " + ex.Message);
AddMsg(Level.Debug, "Handled RRQ file exception: " + ex.StackTrace);
}
}
private void SendUnknownError(IPEndPoint RemoteEndPoint)
{
Send(RemoteEndPoint, ErrorUnknown);
}
private bool CheckForExistingTID(IPEndPoint RemoteEndPoint)
{
bool ReturnVal = false;
if (State != null)
{
//The TFTP server may change the remote port on the first packet when we are the client
if (ClientMode &&
(RemoteEndPoint.Address.ToString() == State.RemoteIPAddress) &&
(myEndpoint.Address.ToString() == State.LocalIPAddress) &&
(myEndpoint.Port == State.LocalPortNumber))
{
//If we find a TID update the timestamp
State.Timestamp = DateTime.Now.Ticks;
//ReturnIndex = StateIndex;
//StateIndex = CurrStates.Count;
ReturnVal = true;
State.RemotePortNumber = RemoteEndPoint.Port;
}
else if ((RemoteEndPoint.Address.ToString() == State.RemoteIPAddress) &&
(RemoteEndPoint.Port == State.RemotePortNumber) &&
(myEndpoint.Address.ToString() == State.LocalIPAddress) &&
(myEndpoint.Port == State.LocalPortNumber))
{
//If we find a TID update the timestamp
State.Timestamp = DateTime.Now.Ticks;
//ReturnIndex = StateIndex;
//StateIndex = CurrStates.Count;
ReturnVal = true;
}
}
return ReturnVal;
}
private TFTPState CreateNewState(IPEndPoint RemoteEndPoint, int CurrOpcode, string filename, string type, int BlockSize)
{
TFTPState NewState = new TFTPState(myEndpoint.Address,
myEndpoint.Port,
RemoteEndPoint.Address,
RemoteEndPoint.Port,
CurrOpcode,
filename,
type,
FullPath,
AllowWRQOverwrite,
ResendInterval,
Timeout,
ClientMode,
BlockSize);
return NewState;
}
private void SendCallback(IAsyncResult result)
{
try
{
((Socket)result.AsyncState).EndSendTo(result);
}
catch (ObjectDisposedException)
{ }
}
private string[] FindOptions(byte[] ReceivedBytes, int NumReceivedBytes)
{
ArrayList ReturnList = new ArrayList(5);
ArrayList TempList = new ArrayList(10);
for (int index = 2; index < NumReceivedBytes; index++)
{
if (ReceivedBytes[index] == 0)
{
ReturnList.Add(System.Text.Encoding.ASCII.GetString((byte[])TempList.ToArray(typeof(byte))));
TempList.Clear();
}
else
{
TempList.Add(ReceivedBytes[index]);
}
}
return (string[])ReturnList.ToArray(typeof(string));
}
private bool SendOptions(string[] FoundOptions, TFTPState ChkState, IPEndPoint RemoteEndPoint)
{
bool returnval = false;
if (AllowOptions && (FoundOptions.Length > 2))
{
ArrayList DataBytes = new ArrayList(20);
try
{
DataBytes.Add(Convert.ToByte(0));
DataBytes.Add(Convert.ToByte(6));
for (int i = 2; i < FoundOptions.Length; i++)
{
//I'm not sure if this is the best way to implement setting up option responses but it works
switch (FoundOptions[i].ToLower())
{
case "tsize": //RFC 2349
#region tsize
AddMsg(Level.Verbose, "Found tsize option");
if (State.Filesize > 0)
{
State.TransferSizeOption = true;
DataBytes.Add(Convert.ToByte('t'));
DataBytes.Add(Convert.ToByte('s'));
DataBytes.Add(Convert.ToByte('i'));
DataBytes.Add(Convert.ToByte('z'));
DataBytes.Add(Convert.ToByte('e'));
DataBytes.Add(Convert.ToByte(0));
byte[] sizebytes = System.Text.Encoding.ASCII.GetBytes(State.Filesize.ToString());
foreach (byte SizeByte in sizebytes)
{
DataBytes.Add(SizeByte);
}
DataBytes.Add(Convert.ToByte(0));
if ((ClientMode == false) && (State.TransferType == "write"))
State.FileLength = Convert.ToInt32(FoundOptions[i + 1]);
AddMsg(Level.Debug, "sending tsize = " + State.Filesize.ToString());
AddMsg(Level.Debug, "Setup tsize reponse");
returnval = true;
}
break;
#endregion
case "timeout": //RFC 2349
#region timeout
AddMsg(Level.Verbose, "Found timeout option");
int RecvTimeout = Convert.ToInt32(FoundOptions[i + 1]);
AddMsg(Level.Debug, "Timeout = " + RecvTimeout.ToString());
if ((RecvTimeout >= 1) && (RecvTimeout <= 255))
{
State.ResendIntervalSeconds = RecvTimeout;
State.TimeoutOption = true;
State.TimeoutOptionACK = true;
DataBytes.Add(Convert.ToByte('t'));
DataBytes.Add(Convert.ToByte('i'));
DataBytes.Add(Convert.ToByte('m'));
DataBytes.Add(Convert.ToByte('e'));
DataBytes.Add(Convert.ToByte('o'));
DataBytes.Add(Convert.ToByte('u'));
DataBytes.Add(Convert.ToByte('t'));
DataBytes.Add(Convert.ToByte(0));
byte[] timeoutbytes = System.Text.Encoding.ASCII.GetBytes(RecvTimeout.ToString());
foreach (byte TimeByte in timeoutbytes)
{
DataBytes.Add(TimeByte);
}
DataBytes.Add(Convert.ToByte(0));
AddMsg(Level.Debug, "Setup timeout reponse");
returnval = true;
}
break;
#endregion
case "blksize": //RFC 2348
#region blksize
AddMsg(Level.Verbose, "Found blksize option");
int RecvBlocksize = Convert.ToInt32(FoundOptions[i + 1]);
AddMsg(Level.Debug, "Blksize option = " + RecvBlocksize.ToString());
if ((RecvBlocksize >= 8) && (RecvBlocksize <= 65464))
{
State.BlocksizeOption = true;
State.BlocksizeOptionACK = true;
State.BlockSize = RecvBlocksize;
DataBytes.Add(Convert.ToByte('b'));
DataBytes.Add(Convert.ToByte('l'));
DataBytes.Add(Convert.ToByte('k'));
DataBytes.Add(Convert.ToByte('s'));
DataBytes.Add(Convert.ToByte('i'));
DataBytes.Add(Convert.ToByte('z'));
DataBytes.Add(Convert.ToByte('e'));
DataBytes.Add(Convert.ToByte(0));
byte[] blocksizebytes = System.Text.Encoding.ASCII.GetBytes(RecvBlocksize.ToString());
foreach (byte BlockByte in blocksizebytes)
{
DataBytes.Add(BlockByte);
}
DataBytes.Add(Convert.ToByte(0));
AddMsg(Level.Debug, "Setup blksize reponse");
returnval = true;
}
break;
#endregion
case "multicast": //RFC 2090
#region multicast
AddMsg(Level.Verbose, "Found multicast option");
break;
#endregion
case "sbroadcast": //Saw this in some IBM doc but can't find an RFC
#region sbroadcast
AddMsg(Level.Verbose, "Found sbroadcast option");
break;
#endregion
default:
AddMsg(Level.Debug, "Found unknown option: " + FoundOptions[i]);
break;
}
i++; //Increment because we use them in pairs
}
if (returnval)
{
AddMsg(Level.Verbose, "Sending OACK to " + RemoteEndPoint.Address.ToString() + ":" + RemoteEndPoint.Port.ToString());
Send(RemoteEndPoint, (byte[])DataBytes.ToArray(typeof(byte)));
sentOACK = true;
}
}
catch (Exception ex)
{
returnval = false;
sentOACK = false;
AddMsg(Level.Warning, "A problem occurred when parsing the received packet");
AddMsg(Level.Verbose, "Handled exception in send : " + ex.Message);
AddMsg(Level.Debug, "Handled RRQ file exception: " + ex.StackTrace);
}
}
else
{
//An error did not occur while processing options
returnval = true;
}
return returnval;
}
///
///
///
/// A string representing the DNS hostname or IP address to retrieve the file from
/// The port the Host TFTP server is listening o n
/// The filename to request from the Host
/// Indicates if we should request the file size
///
///
public void GetFile(string Host, int Port, string Filename, bool Filesize, int Timeout, int TransferSize)
{
ClientMode = true;
ClientHost = Host;
IPEndPoint RemoteEndPoint = new IPEndPoint(IPAddress.Parse(Host), Port);
ArrayList DataBytes = new ArrayList(20);
DataBytes.Add(Convert.ToByte(0));
DataBytes.Add(Convert.ToByte(1));
byte[] filebytes = System.Text.Encoding.ASCII.GetBytes(Filename);
foreach (byte FileByte in filebytes)
{
DataBytes.Add(FileByte);
}
DataBytes.Add(Convert.ToByte(0));
DataBytes.Add(Convert.ToByte(Convert.ToChar("o")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("c")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(0));
if (Filesize)
{
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("s")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("i")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("z")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(0));
DataBytes.Add(Convert.ToByte(Convert.ToChar("0")));
DataBytes.Add(Convert.ToByte(0));
}
if (TransferSize != 512)
{
DataBytes.Add(Convert.ToByte(Convert.ToChar("b")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("l")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("k")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("s")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("i")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("z")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(0));
string size = TransferSize.ToString();
foreach (char character in size)
DataBytes.Add(Convert.ToByte(character));
DataBytes.Add(Convert.ToByte(0));
}
if (Timeout != 1)
{
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("i")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("m")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("o")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("u")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(0));
string time = Timeout.ToString();
foreach (char character in time)
DataBytes.Add(Convert.ToByte(character));
DataBytes.Add(Convert.ToByte(0));
}
AddMsg(Level.Info, "Sending get request for file " + Filename + " to " + Host + ":" + Port.ToString());
Send(RemoteEndPoint, (byte[])DataBytes.ToArray(typeof(byte)));
State = CreateNewState(RemoteEndPoint, 2, Filename, "octet", TransferSize);
}
///
///
///
/// A string representing the DNS hostname or IP address of the TFTP server to put the file on
/// The port the Host TFTP server is listening o n
/// The filename to put on the specified Host
/// Indicates if we should send the file size
///
///
public void PutFile(string Host, int Port, string Filename, bool Filesize, int Timeout, int TransferSize)
{
string FixedFilename = Filename;
System.IO.FileInfo info = new System.IO.FileInfo(Filename);
if (info.Exists)
{
int slashLoc = Filename.LastIndexOf('\\');
if (slashLoc >= 0)
{
FullPath = Filename.Substring(0, slashLoc);
FixedFilename = Filename.Substring(slashLoc + 1);
}
ClientMode = true;
PutClientMode = true;
ClientHost = Host;
IPEndPoint RemoteEndPoint = new IPEndPoint(IPAddress.Parse(Host), Port);
ArrayList DataBytes = new ArrayList(20);
DataBytes.Add(Convert.ToByte(0));
DataBytes.Add(Convert.ToByte(2));
byte[] filebytes = System.Text.Encoding.ASCII.GetBytes(FixedFilename);
foreach (byte FileByte in filebytes)
{
DataBytes.Add(FileByte);
}
DataBytes.Add(Convert.ToByte(0));
DataBytes.Add(Convert.ToByte(Convert.ToChar("o")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("c")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(0));
if (Filesize)
{
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("s")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("i")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("z")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(0));
string size = info.Length.ToString();
foreach (char character in size)
DataBytes.Add(Convert.ToByte(character));
DataBytes.Add(Convert.ToByte(0));
}
if (TransferSize != 512)
{
DataBytes.Add(Convert.ToByte(Convert.ToChar("b")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("l")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("k")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("s")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("i")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("z")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(0));
string size = TransferSize.ToString();
foreach (char character in size)
DataBytes.Add(Convert.ToByte(character));
DataBytes.Add(Convert.ToByte(0));
}
if (Timeout != 1)
{
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("i")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("m")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("e")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("o")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("u")));
DataBytes.Add(Convert.ToByte(Convert.ToChar("t")));
DataBytes.Add(Convert.ToByte(0));
string time = Timeout.ToString();
foreach (char character in time)
DataBytes.Add(Convert.ToByte(character));
DataBytes.Add(Convert.ToByte(0));
}
info = null;
AddMsg(Level.Info, "Sending put request for file " + FixedFilename + " to " + Host);
Send(RemoteEndPoint, (byte[])DataBytes.ToArray(typeof(byte)));
State = CreateNewState(RemoteEndPoint, 1, FixedFilename, "octet", TransferSize);
}
else
{
AddMsg(Level.Info, "File " + Filename.ToString() + " does not exist.");
}
}
private void OnTFTPServerEvent(TFTPServerProcessEventArgs e)
{
if (TFTPServerProcessEvent != null)
{
TFTPServerProcessEvent(null, e);
}
else if (logger != null && logger.IsOpen)
{
logger.Log(e.EventLevel, e.EventString);
}
}
private void AddMsg(Level level, string text)
{
//TFTPServer handles actually logging the messages, just send them an event
OnTFTPServerEvent(new TFTPServerProcessEventArgs(text, level));
}
///
/// Gets whether we are listening for TFTP requests
///
public bool IsListening
{
get
{
return Loop;
}
}
///
/// The current TFTPState instance for this TFTP Server Process thread
///
public TFTPState CurrState
{
get
{
return State;
}
}
private void FileAccess(string access)
{
switch (access)
{
case "No Access":
AllowRRQ = false;
AllowWRQ = false;
AllowWRQOverwrite = false;
break;
case "Read Only":
AllowRRQ = true;
AllowWRQ = false;
AllowWRQOverwrite = false;
break;
case "Write":
AllowRRQ = false;
AllowWRQ = true;
AllowWRQOverwrite = false;
break;
case "Read and Write":
AllowRRQ = true;
AllowWRQ = true;
AllowWRQOverwrite = false;
break;
case "Read and Overwrite":
AllowRRQ = true;
AllowWRQ = true;
AllowWRQOverwrite = true;
break;
}
}
}
}