1419 lines
40 KiB
C++
1419 lines
40 KiB
C++
|
//------------------------------------------------------------------------------
|
||
|
//
|
||
|
// 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.
|