singrdk/base/Windows/bootd/tftpd.cpp

1419 lines
40 KiB
C++
Raw Normal View History

2008-03-05 09:52:00 -05:00
//------------------------------------------------------------------------------
//
// Microsoft Research
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: tftpd.cpp
//
// Contents: Simple TFTP Daemon
//
// Owner:
//
// History: 03/30/2000 Created.
//
//------------------------------------------------------------------------------
// #define DEBUG 1
#include <winlean.h>
#include <winsock.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "socksr.h"
//////////////////////////////////////////////////////////////////////////////
//
// Debugging
//
#define VERBOSE(x) if (s_fVerboseOutput) x
//#pragma warning(disable: 4127)
#define ARRAYOF(x) (sizeof(x)/sizeof(x[0]))
#define OFFSETOF(s,m) ((unsigned)&(((s *)0)->m))
#define assert(x) if (!(x)) __asm int 3
#define WM_SOCKET_EVENT (WM_USER+101)
typedef USHORT UINT16;
#include "hashtab.h"
#include "tftpd.h"
#include "hashtab.cpp"
//////////////////////////////////////////////////////////////////////////////
//
//
// Trivial File Transfer Protocol (TFTP) as per RFCs 1350, 1782, 1783 & 1784.
//
//
// Packet types and their opcodes.
//
#define TFTP_RRQ 1 // read request
#define TFTP_WRQ 2 // write request
#define TFTP_DATA 3 // data
#define TFTP_ACK 4 // acknowledgment
#define TFTP_ERROR 5 // error
#define TFTP_OACK 6 // option acknowledgment
//
// Error codes.
//
#define EUNDEF 0 // not defined, see error message (if any)
#define ENOTFOUND 1 // file not found
#define EACCESS 2 // access violation
#define ENOSPACE 3 // disk full or allocation exceeded
#define EBADOP 4 // illegal TFTP operation
//
// TFTP header structure.
//
struct TftpHdr {
UINT16 Opcode; // packet type
union {
CHAR Options[1]; // options we accept (for OACK packets)
UINT16 Block; // block # (for DATA and ACK packets)
UINT16 ErrorCode; // error code (for ERROR packets)
};
CHAR Data[1]; // data/error string
PCSTR Dump(UINT cbData) {
static CHAR szBuffer[2048];
PCHAR pszBuffer = szBuffer;
// In network order
switch (ntohs(Opcode)) {
default:
sprintf(pszBuffer, "??? %04x", ntohs(Opcode));
__asm int 3;
return szBuffer;
case TFTP_DATA:
sprintf(pszBuffer, "DATA%5d %d bytes", ntohs(Block), cbData - 4);
return szBuffer;
case TFTP_ACK:
sprintf(pszBuffer, "ACK %5d", ntohs(Block));
return szBuffer;
case TFTP_ERROR:
sprintf(pszBuffer, "ERROR%4d %s", ntohs(ErrorCode), Data);
return szBuffer;
case TFTP_RRQ:
pszBuffer += sprintf(pszBuffer, "RRQ [");
break;
case TFTP_WRQ:
pszBuffer += sprintf(pszBuffer, "WRQ [");
break;
case TFTP_OACK:
pszBuffer += sprintf(pszBuffer, "OACK [");
break;
}
PCHAR pszTmp = Options;
PCHAR pszEnd = ((PCHAR)this) + cbData;
for (; pszTmp < pszEnd; pszTmp++) {
if (*pszTmp) {
*pszBuffer++ = *pszTmp;
}
else {
*pszBuffer++ = '^';
}
}
*pszBuffer++ = ']';
*pszBuffer = '\0';
return szBuffer;
}
};
//
// With TFTP, all DATA packets except for the last one contain the same
// number of data bytes (plus four bytes of header). Barring a "blksize"
// option in the request packet, this number is 512.
//
#define DEFAULT_BLOCKSIZE 512
#define MAX_BLOCKSIZE 8192
//
// The TFTP spec says nothing about default timeout values or retransmission
// algorithms. RFC 1123, however, says me MUST use an adaptive timeout and
// recommends doing an exponential backoff. To keep things simple, we start
// with DEFAULT_TIMEOUT (which client can override via "timeout" option) and
// double it for each retry up to MAX_RETRIES. Each time we need to retransmit
// a packet to get it through, we double the initial timeout for the next and
// all successive packets.
//
// Keep DEFAULT_TIMEOUT and especially MAX_RETRIES to fairly small values
// (order 100 and order 10, respectively) or you'll break the implementation
// (and really try your patience).
//
#define DEFAULT_TIMEOUT 2 // in seconds
#define MAX_RETRIES 5
//////////////////////////////////////////////////////////////////////////////
//
CHAR s_rszRenames[64][64];
UINT s_nRenames = 0;
UINT s_nRenamesBase = 0;
BOOL s_fRenames = TRUE;
CHAR s_rszReadPaths[16][MAX_PATH];
CHAR s_szWritePath[MAX_PATH];
UINT s_nPaths = 0;
BOOL s_fExitOnENotFound = FALSE;
BOOL s_fVerboseOutput = FALSE;
//////////////////////////////////////////////////////////////////////////////
//
// Structure for handing incoming requests off to per-connection threads.
//
class CTftpNode : public ITimerSink,
public ISessionSink
{
public:
CTftpNode(ISessionSource *pSource, ITftpSink *pEventSink,
UINT32 nAddr, UINT16 nPort, UINT32 nSession);
~CTftpNode();
UINT CheckAndOpenFile(PCHAR pszName, HANDLE *phFile);
UINT CheckAndCreateFile(PCHAR pszName, BOOL fWrite, HANDLE *phFile);
// Events:
VOID ResendPacket(BOOL fTimeout);
VOID ReadFinished(DWORD dwErrorCode, DWORD dwDone);
// ISessionSink
public:
virtual VOID OnSessionCreate(ISessionSource *pSource, PVOID pvContext);
virtual VOID OnSessionRecv(UINT dwError, PBYTE pbData, UINT cbRcvd);
virtual VOID OnSessionClose(UINT dwError);
public:
// ITimerSink:
virtual VOID OnTimerFired();
protected:
// Subroutines:
VOID Ack(UINT16 nBlock);
VOID Nak(UINT16 nErrorCode);
VOID OAck();
VOID Data(UINT16 nBlock, PVOID pvPacket, UINT cbData);
VOID SetTimeout();
VOID ClrTimeout();
UINT SocketSend(PVOID pbData, UINT cbData);
BOOL ReadBlock(UINT nBlock);
BOOL WriteBlock(INT cbData);
VOID WriteFinished(DWORD dwErrorCode, DWORD dwDone);
BOOL IsActiveSession() { return m_szFilename[0] != '\0'; }
VOID DeactivateSession();
VOID Log(PCSTR pszMsg, ...);
BOOL ValidateName(PCHAR pszDst, PCHAR pszSrc);
static VOID CALLBACK ReadCallback(DWORD dwErr, DWORD dwDone, LPOVERLAPPED lpOvrlap);
static VOID CALLBACK WriteCallback(DWORD dwErrorCode, DWORD dwDone, LPOVERLAPPED lpOverlap);
public:
UINT32 m_nSession;
protected:
ISessionSource * m_pSource;
ITftpSink * m_pEventSink;
UINT32 m_nAddr;
UINT16 m_nPort;
HANDLE m_hFile;
CHAR m_szFilename[MAX_PATH];
BOOL m_fWrite;
BOOL m_fDone;
BOOL m_fUseOack;
BOOL m_fHadTsize;
UINT m_cbFileSize;
UINT m_cbBlock;
UINT m_cbBlockOpt;
UINT m_cbBlockRead;
UINT m_nTimeout;
UINT m_nTimeoutOpt;
UINT m_nTimeoutCount;
UINT m_nTimeoutValue;
// n_nBlock the next block to read and wait for ACK.
UINT16 m_nBlock;
UINT32 m_nBlockLogLow;
UINT32 m_nBlockLogHigh;
BOOL m_fIoDone;
UINT m_nIoOffset;
BYTE m_bIoBuffer[MAX_BLOCKSIZE + OFFSETOF(TftpHdr, Data)];
OVERLAPPED m_Overlapped;
};
//////////////////////////////////////////////////////////////////////////////
//
CTftpNode::CTftpNode(ISessionSource *pSource, ITftpSink *pEventSink,
UINT32 nAddr, UINT16 nPort, UINT32 nSession)
: m_pSource(pSource),
m_pEventSink(pEventSink),
m_nAddr(nAddr),
m_nPort(nPort),
m_nSession(nSession)
{
m_szFilename[0] = '\0';
m_hFile = INVALID_HANDLE_VALUE;
m_nTimeout = DEFAULT_TIMEOUT;
m_fUseOack = FALSE;
}
CTftpNode::~CTftpNode()
{
ClrTimeout();
}
UINT CTftpNode::CheckAndCreateFile(PCHAR pszName, BOOL fWrite, HANDLE *phFile)
{
*phFile = INVALID_HANDLE_VALUE;
// Set the access and creation flags.
//
DWORD dwAccess;
DWORD dwShare;
DWORD dwCreation;
PCHAR pszOp;
if (fWrite) {
dwAccess = GENERIC_WRITE;
dwShare = 0 /*[: don't let others see inconsistent file */
/*FILE_SHARE_READ | FILE_SHARE_WRITE*/;
dwCreation = CREATE_ALWAYS;
pszOp = "write";
if (s_szWritePath[0] == '\0') {
// Writes not allowed
goto fail;
}
// Writes are only allowed to the write path.
CHAR szPath[MAX_PATH];
strcpy(szPath, s_szWritePath);
strcat(szPath, pszName);
*phFile = CreateFile(szPath, dwAccess, dwShare, NULL, dwCreation,
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if (*phFile != INVALID_HANDLE_VALUE) {
Log(" %02d Write `%s'", m_nSession, szPath);
return 0;
}
}
else {
dwAccess = GENERIC_READ;
dwShare = FILE_SHARE_READ;
dwCreation = OPEN_EXISTING;
pszOp = "read";
// Reads are allowed for any path.
for (UINT n = 0; n < s_nPaths; n++)
{
CHAR szPath[MAX_PATH];
strcpy(szPath, s_rszReadPaths[n]);
strcat(szPath, pszName);
*phFile = CreateFile(szPath, dwAccess, dwShare, NULL, dwCreation,
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if (*phFile != INVALID_HANDLE_VALUE) {
Log(" %02d Read `%s'", m_nSession, szPath);
return 0;
}
}
}
fail:
Log(" %02d Failed %s `%s'", m_nSession, pszOp, pszName);
return EACCESS;
}
UINT CTftpNode::CheckAndOpenFile(PCHAR pszName, HANDLE *phFile)
{
*phFile = INVALID_HANDLE_VALUE;
for (UINT n = 0; n < s_nPaths; n++)
{
CHAR szPath[MAX_PATH * 2];
strcpy(szPath, s_rszReadPaths[n]);
strcat(szPath, pszName);
*phFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
if (*phFile != INVALID_HANDLE_VALUE) {
Log(" %02d Read `%s'", m_nSession, szPath);
return 0;
}
}
Log(" %02d Failed read `%s'", m_nSession, pszName);
return EACCESS;
}
VOID CTftpNode::OnSessionCreate(ISessionSource *pSource, PVOID pvContext)
{
(void)pvContext;
m_pSource = pSource;
}
VOID CTftpNode::OnSessionClose(UINT dwError)
{
(void)dwError;
VERBOSE(Log(" %02d close session: %d.", m_nSession, dwError));
delete this;
}
VOID CTftpNode::Log(PCSTR pszMsg, ...)
{
if (m_pEventSink) {
CHAR szBuffer[2048];
va_list args;
va_start(args, pszMsg);
vsprintf(szBuffer, pszMsg, args);
va_end(args);
m_pEventSink->OnTftpMessage(m_nAddr, "%s", szBuffer);
}
}
UINT CTftpNode::SocketSend(PVOID pbData, UINT cbData)
{
TftpHdr *pPacket = (TftpHdr*)pbData;
if (ntohs(pPacket->Opcode) == TFTP_DATA) {
UINT16 nBlock = ntohs(pPacket->Block);
if (nBlock <= m_nBlockLogLow || nBlock >= m_nBlockLogHigh ||
nBlock % 250 == 0) {
Log("<= %02d %s", m_nSession, pPacket->Dump(cbData),
cbData - 4, m_cbBlockOpt);
}
}
else {
Log("<= %02d %s", m_nSession, pPacket->Dump(cbData));
}
return m_pSource->SessionSend((PBYTE)pbData, cbData);
}
VOID CTftpNode::DeactivateSession()
{
if (m_szFilename[0]) {
VERBOSE(Log("-- %02d DeactivateSession", m_nSession));
m_szFilename[0] = 0;
if (m_hFile != INVALID_HANDLE_VALUE) {
CancelIo(m_hFile);
CloseHandle(m_hFile);
m_hFile = INVALID_HANDLE_VALUE;
}
SetTimeout();
}
}
BOOL CTftpNode::ReadBlock(UINT nBlock)
{
(void)nBlock;
VERBOSE(Log(" %02d ReadBlock(%d at %d)",
m_nSession, nBlock, m_nIoOffset));
m_fIoDone = FALSE;
m_Overlapped.hEvent = (HANDLE)this;
m_Overlapped.Offset = m_nIoOffset;
m_Overlapped.OffsetHigh = 0;
if (!ReadFileEx(m_hFile, m_bIoBuffer + OFFSETOF(TftpHdr, Data)
, m_cbBlock, &m_Overlapped, ReadCallback)) {
ReadFinished(GetLastError(), 0);
return FALSE;
}
return TRUE;
}
VOID CTftpNode::ReadFinished(DWORD dwErrorCode, DWORD dwDone)
{
VERBOSE(Log(" %02d ReadFinished(%d, %d)",
m_nSession, dwErrorCode, dwDone));
m_fIoDone = TRUE;
m_nIoOffset += dwDone;
m_cbBlockRead = dwDone;
if (dwErrorCode == ERROR_HANDLE_EOF) {
dwErrorCode = 0;
}
if (dwErrorCode != 0) {
VERBOSE(Log(" %02d Read failed: %d", m_nSession, dwErrorCode));
Nak(ENOSPACE);
}
else {
ResendPacket(FALSE);
}
}
VOID CTftpNode::ReadCallback(DWORD dwErrorCode, DWORD dwDone, LPOVERLAPPED lpOverlap)
{
CTftpNode *pInfo = (CTftpNode *)lpOverlap->hEvent;
pInfo->ReadFinished(dwErrorCode, dwDone);
}
BOOL CTftpNode::WriteBlock(INT cbData)
{
m_fIoDone = FALSE;
m_Overlapped.hEvent = (HANDLE)this;
m_Overlapped.Offset = m_nIoOffset;
m_Overlapped.OffsetHigh = 0;
if (cbData == 0) {
WriteFinished(0, 0);
}
else if (!WriteFileEx(m_hFile, m_bIoBuffer, cbData, &m_Overlapped, WriteCallback)) {
WriteFinished(GetLastError(), 0);
return FALSE;
}
return TRUE;
}
VOID CTftpNode::WriteFinished(DWORD dwErrorCode, DWORD dwDone)
{
VERBOSE(Log("%08x WriteFinished(%d, %d)\n", this, dwErrorCode, dwDone));
m_fIoDone = TRUE;
m_nIoOffset += dwDone;
if (dwErrorCode != 0) {
VERBOSE(Log("%08x Write failed: %d\n", this, dwErrorCode));
Nak(ENOSPACE);
}
else {
m_nBlock++;
}
if (dwDone < 512) {
m_fDone = TRUE;
}
ResendPacket(FALSE);
}
VOID CTftpNode::WriteCallback(DWORD dwErrorCode, DWORD dwDone, LPOVERLAPPED lpOverlap)
{
CTftpNode *pInfo = (CTftpNode *)lpOverlap->hEvent;
pInfo->WriteFinished(dwErrorCode, dwDone);
}
VOID CTftpNode::OnTimerFired()
{
if (!m_fDone) {
ResendPacket(TRUE);
}
}
VOID CTftpNode::SetTimeout()
{
if (m_nTimeoutValue == 0) {
m_nTimeoutCount = 0;
m_nTimeoutValue = m_nTimeout;
}
TimerSet(this, m_nTimeoutValue * 1000);
}
VOID CTftpNode::ClrTimeout()
{
m_nTimeoutValue = 0;
TimerClr(this);
}
//
// Send an TFTP_ACK packet.
//
VOID CTftpNode::Ack(UINT16 nBlock)
{
BYTE Buffer[8];
TftpHdr *pPacket = (TftpHdr *)Buffer;
pPacket->Opcode = htons((UINT16)TFTP_ACK);
pPacket->Block = htons(nBlock);
SocketSend(pPacket, OFFSETOF(TftpHdr, Data));
SetTimeout();
}
//
// Send a TFTP_DATA packet
//
VOID CTftpNode::Data(UINT16 nBlock, PVOID pvPacket, UINT cbData)
{
TftpHdr *pPacket = (TftpHdr *)pvPacket;
pPacket->Opcode = htons((UINT16)TFTP_DATA);
pPacket->Block = htons(nBlock);
SocketSend(pPacket, cbData + OFFSETOF(TftpHdr, Data));
SetTimeout();
}
//
// Send a TFTP_NAK packet (i.e. an error message).
// Error code passed in is one of the standard TFTP codes,
//
VOID CTftpNode::Nak(UINT16 nErrorCode)
{
BYTE Buffer[512];
TftpHdr *pPacket = (TftpHdr *)Buffer;
pPacket->Opcode = htons((UINT16)TFTP_ERROR);
pPacket->ErrorCode = htons(nErrorCode);
pPacket->Data[0] = '\0';
//
// Convert error code into the appropriate ASCII string message.
//
PCHAR pszError = "Undetermined error";
switch (nErrorCode) {
case ENOTFOUND: pszError = "File not found"; break;
case EACCESS: pszError = "Access violation"; break;
case ENOSPACE: pszError = "Disk full or allocation exceeded"; break;
case EBADOP: pszError = "Illegal TFTP operation"; break;
}
strcpy(pPacket->Data, pszError);
INT cbData = OFFSETOF(TftpHdr, Data) + strlen(pPacket->Data) + 1;
SocketSend(pPacket, cbData);
VERBOSE(Log(" %02d Nak'd with errorcode %d (%s).",
m_nSession, nErrorCode, pPacket->Data));
if (m_pEventSink) {
m_pEventSink->OnTftpAccessEnd(m_nAddr, m_nPort, 0);
}
// We don't call SetTimeout here because DeactivateSession does.
DeactivateSession();
if (s_fExitOnENotFound && nErrorCode == ENOTFOUND) {
ExitProcess(ENOTFOUND);
}
}
//
// Send a oack (option acknowledgment) packet.
// Non-zero options passed in are valid.
//
VOID CTftpNode::OAck()
{
BYTE Buffer[2048];
TftpHdr *pPacket;
int cbData = 0;
pPacket = (TftpHdr *)Buffer;
pPacket->Opcode = htons((UINT16)TFTP_OACK);
//
// Convert Option/Value pairs into the appropriate ASCII string messages.
//
if (m_cbBlockOpt) {
cbData += sprintf(pPacket->Options + cbData, "blksize") + 1;
cbData += sprintf(pPacket->Options + cbData, "%d", m_cbBlockOpt) + 1;
}
if (m_nTimeoutOpt) {
cbData += sprintf(pPacket->Options + cbData, "timeout") + 1;
cbData += sprintf(pPacket->Options + cbData, "%d", m_nTimeoutOpt) + 1;
}
if (m_fHadTsize) {
cbData += sprintf(pPacket->Options + cbData, "tsize") + 1;
cbData += sprintf(pPacket->Options + cbData, "%d", m_cbFileSize) + 1;
}
SocketSend(Buffer, cbData + OFFSETOF(TftpHdr, Options));
SetTimeout();
VERBOSE(Log(" %02d OAck sent (%d bytes)", m_nSession, cbData));
}
//////////////////////////////////////////////////////////////////////////////
//
VOID CTftpNode::ResendPacket(BOOL fTimeout)
{
if (fTimeout) {
VERBOSE(Log(" %02d timeout (count=%d, value=%d)",
m_nSession, m_nTimeoutCount, m_nTimeoutValue));
m_nTimeoutValue *= 2;
if (m_fDone || ++m_nTimeoutCount > MAX_RETRIES) {
if (IsActiveSession()) {
VERBOSE(Log(" %02d too many retries, aborting session",
m_nSession));
Nak(EUNDEF);
}
// Close the session!
m_pSource->SessionClose(0);
return;
}
}
#if 0
if (!IsActiveSession()) {
SetTimeout();
return;
}
else if (m_nBlock == 0 && m_nIoOffset == 0) {
if (m_cbBlockOpt || m_nTimeoutOpt) {
//
// Need to send OACK packet to acknowledge options we accept.
//
// If client is making a read request, wait for an ack to our oack
// to arrive (it should acknowledge block 0). If client is making
// a write request, wait for the first data packet to arrive. In
// either case, if we timeout before anything arrives, resend oack.
//
OAck(); //m_cbBlockOpt, m_nTimeoutOpt);
}
else {
//
// Need to acknowledge request with an ack for block 0
// since we didn't send an oack. RecvData() will handle
// any retransmission that may be required of this ack.
//
Ack(0);
}
}
else {
if (m_fWrite) {
printf(" Write: acking block %d\n",m_nBlock);
Ack(m_nBlock);
}
else {
Data(m_nBlock, m_bIoBuffer, m_cbBlockRead);
}
}
#endif
#if 1
//printf(" m_nBlock=%d, m_nIoOffset=%d,m_fUseOack=%d,m_fWrite=%d\n",
// m_nBlock, m_nIoOffset, m_fUseOack,m_fWrite);
if (!IsActiveSession()) {
SetTimeout();
return;
}
else if (m_nBlock == 0 && m_nIoOffset == 0)
{
if ( m_fUseOack ) {
//printf("Oacking\n");
OAck();
}
else if (m_fWrite)
Ack(m_nBlock);
}
else if (m_fWrite)
Ack(m_nBlock);
else if (m_fIoDone) {
//printf("m_fIoDone\n");
Data(m_nBlock, m_bIoBuffer, m_cbBlockRead);
}
#endif
}
BOOL CTftpNode::ValidateName(PCHAR pszDst, PCHAR pszSrc)
{
PCHAR pszBeg = pszDst;
// Remove leading '/'.
//
while (*pszSrc == '/' || *pszSrc == '\\')
{
// pszSrc++;
return FALSE;
}
// Check for invalid characters, and copy string.
//
while (*pszSrc)
{
// Don't allow drive letters followed by colons.
//
if (*pszSrc == ':') {
return FALSE;
}
// Don't allow "./" or "../" in file name.
//
if (pszDst == pszBeg || pszDst[-1] == '\\')
{
if ((pszSrc[0] == '.' && pszSrc[1] == '/') ||
(pszSrc[0] == '.' && pszSrc[1] == '\\') ||
(pszSrc[0] == '.' && pszSrc[1] == '.' && pszSrc[2] == '/') ||
(pszSrc[0] == '.' && pszSrc[1] == '.' && pszSrc[2] == '\\'))
{
return FALSE;
}
}
if (*pszSrc == '/')
{
*pszDst++ = '\\';
pszSrc++;
}
else
{
*pszDst++ = *pszSrc++;
}
}
*pszDst++ = '\0';
if (s_fRenames) {
if (strncmp(pszBeg, "pxe.com.", 8) == 0) {
UINT n = atoi(pszBeg + 8);
if (n != 0 && n >= s_nRenamesBase && n < s_nRenamesBase + s_nRenames) {
strcpy(pszBeg, s_rszRenames[n - s_nRenamesBase]);
}
}
}
return TRUE;
}
VOID CTftpNode::OnSessionRecv(UINT dwError, PBYTE pbData, UINT cbData)
{
(void)dwError;
TftpHdr *pPacket = (TftpHdr *)pbData;
UINT16 nOpcode = ntohs(pPacket->Opcode);
UINT16 nBlock = ntohs(pPacket->Block);
if (nOpcode == TFTP_ACK) {
if (nBlock <= m_nBlockLogLow || nBlock >= m_nBlockLogHigh ||
nBlock % 250 == 0) {
Log("=> %02d %s", m_nSession, pPacket->Dump(cbData));
}
}
else {
Log("=> %02d %s", m_nSession, pPacket->Dump(cbData));
}
if (nOpcode == TFTP_RRQ || nOpcode == TFTP_WRQ) {
//
// Abort an existing session if there is one.
//
DeactivateSession();
//
// Set initial variables...
//
m_fWrite = (nOpcode == TFTP_WRQ);
m_fDone = FALSE;
m_szFilename[0] = '\0';
m_cbBlock = DEFAULT_BLOCKSIZE;
m_cbBlockOpt = 0;
m_nTimeout = DEFAULT_TIMEOUT;
m_nTimeoutOpt = 0;
m_fHadTsize = FALSE;
m_cbFileSize = 0;
//
// Parse request fields (filename mode [option1 value1[opt2 val2 [...]]]).
// Note: all matches (including filename!) are case-insensitive.
//
PCHAR pszVal;
PCHAR pszEnd = (PCHAR)pbData + cbData;
PCHAR pszOpt = pPacket->Options;
PCHAR pszTmp = pszOpt + strlen(pszOpt) + 1;
if (pszTmp > pszEnd || !ValidateName(m_szFilename, pszOpt))
{
if (m_pEventSink) {
m_pEventSink->OnTftpAccessBegin(m_nAddr, m_nPort, m_fWrite, "<illegal>");
}
VERBOSE(Log(" %02d formatted filename packet", m_nSession));
Nak(ENOTFOUND);
return;
}
else {
if (m_pEventSink) {
m_pEventSink->OnTftpAccessBegin(m_nAddr, m_nPort, m_fWrite, pszOpt);
}
}
pszOpt = pszTmp;
pszTmp += strlen(pszTmp) + 1;
if (pszTmp <= pszEnd) {
// Note: we always use binary.
if (_stricmp("octet", pszOpt) != 0 && _stricmp("netascii", pszOpt) != 0) {
// Unknown recognized mode.
Nak(EBADOP);
return;
}
}
while (pszTmp < pszEnd) {
pszOpt = pszTmp;
pszTmp += strlen(pszTmp) + 1;
if (pszTmp >= pszEnd) {
break;
}
pszVal = pszTmp;
pszTmp += strlen(pszTmp) + 1;
if (pszTmp > pszEnd) {
break;
}
if (_stricmp(pszOpt, "blksize") == 0) {
//
// "blksize" option: (#octets).
//
m_cbBlockOpt = min(max(atoi(pszVal), 8), MAX_BLOCKSIZE);
m_cbBlock = m_cbBlockOpt;
m_fUseOack = TRUE;
VERBOSE(Log(" %02d blksize: %d", m_nSession, m_cbBlockOpt));
}
else if (_stricmp(pszOpt, "timeout") == 0) {
//
// End of "timeout" option value string (#seconds).
//
m_nTimeoutOpt = min(max(atoi(pszVal), 1), 10);
m_nTimeout = m_nTimeoutOpt;
m_fUseOack = TRUE;
VERBOSE(Log(" %02d timeout: %d", m_nSession, m_nTimeoutOpt));
}
else if (_stricmp(pszOpt, "tsize") == 0) {
//
// "tsize" transmit option value string (#size).
//
m_fHadTsize = 1;
m_fUseOack = TRUE;
VERBOSE(Log(" %02d tsize: %d", m_nSession, atoi(pszVal)));
}
else {
// Just ignore the unknown option.
VERBOSE(Log(" %02d unknown option: `%s'",
m_nSession, pszOpt));
}
}
if (pszTmp > pszEnd) {
//
// Buffer overrun on packet.
//
VERBOSE(Log(" %02d badly formatted request packet", m_nSession));
Nak(EBADOP);
return;
}
//
// Open the file.
//
UINT nErr = CheckAndCreateFile(m_szFilename, m_fWrite, &m_hFile);
if (nErr != 0)
{
Nak((UINT16)nErr);
return;
}
m_cbFileSize = GetFileSize(m_hFile, NULL);
if (m_cbFileSize < 0)
{
m_cbFileSize = 0;
}
VERBOSE(Log(" %02d File is %d bytes or %d blocks.",
m_nSession, m_cbFileSize,
(m_cbFileSize + m_cbBlock - 1) / m_cbBlock));
//
// Set final variables...
//
m_nTimeoutValue = 0;
m_nTimeoutCount = 0;
m_cbBlockRead = m_cbBlock;
m_fIoDone = TRUE;
m_nIoOffset = 0;
m_nBlockLogLow = 3;
m_nBlockLogHigh = (m_cbFileSize / m_cbBlockRead) - 3;
m_nBlockLogHigh = m_nBlockLogHigh <= 0 ? 0 : m_nBlockLogHigh;
if (m_fUseOack) {
m_nBlock = 0;
ResendPacket(FALSE);
}
else if (m_fWrite) {
m_nBlock = 0;
ResendPacket(FALSE);
}
else {
m_nBlock = 1;
ReadBlock(0);
}
}
else if (nOpcode == TFTP_DATA ||
nOpcode == TFTP_ACK ||
nOpcode == TFTP_ERROR ||
nOpcode == TFTP_OACK) {
if (!IsActiveSession()) {
return;
}
if (m_szFilename[0] == '\0') {
// Ignore packets if we don't have an active session.
return;
}
if (nOpcode == TFTP_ERROR) {
VERBOSE(Log(" %02d Client reports error (code %d): %s",
m_nSession, nBlock, pPacket->Data));
DeactivateSession();
return;
}
if (m_fWrite) {
// Write requests...
//
printf("WRITE\n");
if (nOpcode == TFTP_DATA) {
printf("DATA\n");
if (nBlock == (UINT16)(m_nBlock + 1)) {
printf("NEXT\n");
VERBOSE(Log(" %02d got data block %d (%d bytes)\n",
m_nSession, m_nBlock,
cbData - OFFSETOF(TftpHdr, Data)));
printf(" %02d got data block %d (%d bytes)\n",
m_nSession, m_nBlock,
cbData - OFFSETOF(TftpHdr, Data));
if (m_fIoDone) {
m_cbBlockRead = cbData - OFFSETOF(TftpHdr, Data);
//printf("WRITEBLOCK\n");
// Buffer is available, initiate the write.
CopyMemory(m_bIoBuffer, pPacket->Data, m_cbBlockRead);
WriteBlock(m_cbBlockRead);
}
else {
// Client will either re-send the packet either on timeout,
// or when we ACK the last packet.
//printf("NOWRITE\n");
}
}
else if (nBlock == m_nBlock) {
//printf("SAME\n");
// If we got the same block, then we resend our ACK.
ResendPacket(FALSE);
}
else {
VERBOSE(Log(" %02d Strange ACK %d to %d\n",
m_nSession, nBlock, m_nBlock));
Nak(EBADOP);
return;
}
}
else {
// Write streams from clients only contain TFTP_DATA packets.
VERBOSE(Log(" %02d aborting due to weird packet when expecting data.\n",
m_nSession));
Nak(EBADOP);
return;
}
}
else {
// Read requests...
//
if (nOpcode == TFTP_ACK) {
if (nBlock == m_nBlock) {
// Client ACK'd our packet, read the next block.
if (m_cbBlockRead == m_cbBlock) {
ReadBlock(++m_nBlock);
}
else {
// Client ACK'd our last packet, we're out of here.
if (m_pEventSink) {
m_pEventSink->OnTftpAccessEnd(m_nAddr, m_nPort, TRUE);
}
DeactivateSession();
}
}
else if (nBlock == (UINT16)(m_nBlock - 1)) {
// Client ACK'd the previous block, resend it.
ResendPacket(FALSE);
}
else {
VERBOSE(Log(" %02d Strange ACK %d to %d",
m_nSession, nBlock, m_nBlock));
Nak(EBADOP);
return;
}
}
else {
// Read streams from clients only contain TFTP_ACK packets.
VERBOSE(Log(" %02d aborting due to weird packet when expecting data.",
m_nSession));
Nak(EBADOP);
return;
}
}
}
else {
VERBOSE(Log(" %02d bogus following opcode (%d)",
m_nSession, nOpcode));
DeactivateSession();
return;
}
}
//////////////////////////////////////////////////////////////////////////////
//
UINT32 CTftp::s_nSession = 1;
CTftp::CTftp(CTftp *pNext)
{
m_pNext = pNext;
m_pSink = NULL;
m_nAddr = 0;
m_nPort = 0;
}
CTftp::~CTftp()
{
if (m_pNext) {
delete m_pNext;
m_pNext = NULL;
}
if (m_pSink) {
m_pSink->OnTftpMessage(m_nAddr, "Server stopped.");
}
m_pSink = NULL;
}
static BOOL CheckDir(PCHAR pszDir)
{
if (pszDir == NULL || pszDir[0] == '\0') {
return FALSE;
}
PCHAR pszBeg = pszDir;
while (*pszDir) {
pszDir++;
}
if (pszDir > pszBeg && pszDir[-1] != '\\') {
*pszDir++ = '\\';
*pszDir = '\0';
}
return TRUE;
}
VOID CTftp::OnNetworkChange(UINT32 nAddr, UINT32 nMask, UINT32 nGate)
{
if (m_nAddr == nAddr) {
}
if (m_pNext) {
m_pNext->OnNetworkChange(nAddr, nMask, nGate);
}
}
VOID CTftp::OnNetworkDelete(UINT32 nAddr, CTftp **ppNext)
{
if (m_nAddr == nAddr) {
*ppNext = m_pNext;
if (m_pNext) {
m_pNext->OnNetworkDelete(nAddr, ppNext);
}
m_pNext = NULL;
delete this;
}
else {
if (m_pNext) {
m_pNext->OnNetworkDelete(nAddr, &m_pNext);
}
}
}
VOID CTftp::ConfigureFiles(ITftpSink *pSink, INT argc, PCHAR *argv)
{
for (INT arg = 0; arg < argc; arg++) {
if (argv[arg][0] == '-' || argv[arg][0] == '/') {
CHAR args[1024];
strcpy(args, argv[arg]);
char *argn = args+1; // Argument name
char *argp = argn; // Argument parameter
while (*argp && *argp != ':') {
argp++;
}
if (*argp == ':') {
*argp++ = '\0';
}
switch (argn[0]) {
case 'e': // Exit on first ENOTFOUND
case 'E':
s_fExitOnENotFound = TRUE;
break;
case 'r': // Disable rename.
case 'R':
s_fRenames = FALSE;
break;
case 'v':
case 'V':
s_fVerboseOutput = TRUE;
break;
case 'w': // Allow writes.
case 'W':
strcpy(s_szWritePath, argp);
if (pSink) {
pSink->OnTftpMessage(0, "Write: %s", argp);
}
if (s_nPaths < ARRAYOF(s_rszReadPaths)) {
strcpy(s_rszReadPaths[s_nPaths], argv[arg]);
if (CheckDir(s_rszReadPaths[s_nPaths])) {
s_nPaths++;
}
}
break;
}
}
else {
if (s_nPaths < ARRAYOF(s_rszReadPaths)) {
strcpy(s_rszReadPaths[s_nPaths], argv[arg]);
if (CheckDir(s_rszReadPaths[s_nPaths])) {
if (pSink) {
pSink->OnTftpMessage(0, "Read: %s",
s_rszReadPaths[s_nPaths]);
}
s_nPaths++;
}
}
}
}
if (s_fRenames) {
OnFilesChange(pSink);
}
}
VOID CTftp::OnFilesChange(ITftpSink *pSink)
{
s_nRenames = 0;
s_nRenamesBase = 0;
// First, could the pxe.com files:
for (UINT nPath = 0; nPath < s_nPaths; nPath++) {
CHAR szPath[MAX_PATH];
WIN32_FIND_DATA wfd;
strcpy(szPath, s_rszReadPaths[nPath]);
strcat(szPath, "pxe.com.*");
HANDLE hFind = FindFirstFile(szPath, &wfd);
while (hFind != INVALID_HANDLE_VALUE) {
if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
if (strlen(wfd.cFileName) > 8) {
UINT num = atoi(wfd.cFileName + 8);
if (s_nRenamesBase <= num) {
s_nRenamesBase = num + 1;
}
}
}
if (!FindNextFile(hFind, &wfd)) {
FindClose(hFind);
break;
}
}
}
// Then see what files we can rename.
for (UINT nPath = 0; nPath < s_nPaths; nPath++) {
CHAR szPath[MAX_PATH];
WIN32_FIND_DATA wfd;
strcpy(szPath, s_rszReadPaths[nPath]);
strcat(szPath, "*.x86");
HANDLE hFind = FindFirstFile(szPath, &wfd);
while (hFind != INVALID_HANDLE_VALUE) {
if ((wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
goto next;
}
strcpy(szPath, s_rszReadPaths[nPath]);
strcat(szPath, wfd.cFileName);
if (s_nRenames >= ARRAYOF(s_rszRenames)) {
goto next;
}
HANDLE hFile = CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
goto next;
}
CHAR rbBlock[512];
DWORD dwRead = 0;
if (ReadFile(hFile, rbBlock, sizeof(rbBlock), &dwRead, NULL) &&
dwRead == sizeof(rbBlock)) {
if (rbBlock[0] = 'M' && rbBlock[1] == 'Z' &&
*(DWORD*)&rbBlock[64] == 0) {
sprintf(s_rszRenames[s_nRenames], "%ls", &rbBlock[68]);
if (pSink) {
pSink->OnTftpMessage(0, "Rename pxe.com.%d to %s",
s_nRenamesBase + s_nRenames,
s_rszRenames[s_nRenames]);
}
s_nRenames++;
}
}
CloseHandle(hFile);
next:
if (!FindNextFile(hFind, &wfd)) {
FindClose(hFind);
break;
}
}
}
}
VOID CTftp::Configure(UINT32 nAddr, UINT16 nPort,
ITftpSink *pSink, INT argc, PCHAR *argv)
{
m_pSink = pSink;
m_nAddr = nAddr;
m_nPort = nPort;
for (INT arg = 0; arg < argc; arg++) {
if (argv[arg][0] == '-' || argv[arg][0] == '/') {
CHAR args[1024];
strcpy(args, argv[arg]);
char *argn = args+1; // Argument name
char *argp = argn; // Argument parameter
while (*argp && *argp != ':') {
argp++;
}
if (*argp == ':') {
*argp++ = '\0';
}
switch (argn[0]) {
case '?':
break;
}
}
}
if (m_pSink) {
m_pSink->OnTftpMessage(0,
"TFTP Server: %s:%d",
SocketToString(m_nAddr), m_nPort);
}
}
//////////////////////////////////////////////////////////////////////////////
//
VOID CTftp::OnSessionCreate(UINT32 nPeerAddr,
UINT16 nPeerPort,
ISessionSource *pSource,
ISessionSink **ppSink,
PVOID pvContext)
{
(void)pvContext;
*ppSink = new CTftpNode(pSource, m_pSink, nPeerAddr, nPeerPort, s_nSession++);
}
VOID CTftp::OnFactoryRecv(UINT dwError,
UINT32 nAddr, UINT16 nPort, PBYTE pbData, UINT cbData)
{
if (dwError) {
m_pSink->OnTftpSocketError(dwError);
return;
}
// Drop anything that clearly isn't a valid TFTP request packet.
//
if (cbData < OFFSETOF(TftpHdr, Data)) {
VERBOSE(Log(nAddr, "<= packet port %d too small (%d bytes)",
nPort, cbData));
return;
}
// If we receive then their wasn't a client for this packet.
TftpHdr *pPacket = (TftpHdr *)pbData;
UINT16 nOpcode = ntohs(pPacket->Opcode);
if (nOpcode != TFTP_RRQ) {
VERBOSE(Log(nAddr, "<= packet from port %d wrong initial opcode (%d)",
nPort, nOpcode));
return;
}
// New request.
ISessionSink *pSession = NULL;
FactorySessionCreate(nAddr, nPort, &pSession, NULL);
CTftpNode *pClient = static_cast<CTftpNode *>(pSession);
if (pClient != NULL) {
VERBOSE(Log(nAddr, "** %02d session opened port %d",
pClient->m_nSession, nPort));
pClient->OnSessionRecv(0, pbData, cbData);
}
else {
Log(nAddr, "Out of memory!");
}
}
VOID CTftp::OnFactoryClose(UINT dwError)
{
if (dwError) {
m_pSink->OnTftpSocketError(dwError);
}
}
VOID CTftp::Log(ULONG nAddr, PCSTR pszMsg, ...)
{
if (m_pSink) {
CHAR szBuffer[2048];
va_list args;
va_start(args, pszMsg);
vsprintf(szBuffer, pszMsg, args);
va_end(args);
m_pSink->OnTftpMessage(nAddr, "%s", szBuffer);
}
}
//
///////////////////////////////////////////////////////////////// End of File.