2012-11-19 16:46:07 -06:00
|
|
|
//TFTP server in C++
|
|
|
|
#include <algorithm>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include "TFTPserv.h"
|
|
|
|
|
|
|
|
const int bufferSize = 4096;
|
|
|
|
const int tftpServPort = 69;
|
|
|
|
|
|
|
|
const unsigned char OpRead = 1;
|
|
|
|
const unsigned char OpWrite = 2;
|
|
|
|
const unsigned char OpData = 3;
|
|
|
|
const unsigned char OpAck = 4;
|
|
|
|
const unsigned char OpError = 5;
|
|
|
|
const unsigned char OpOptAck = 6;
|
|
|
|
|
|
|
|
const unsigned char ErrFileNotFound = 1;
|
|
|
|
const unsigned char ErrAccessViolation = 2;
|
|
|
|
const unsigned char ErrDiskFull = 3;
|
|
|
|
const unsigned char ErrIllegalOperation = 4;
|
|
|
|
const unsigned char ErrUnknownTransferId = 5;
|
|
|
|
const unsigned char ErrFileExists = 6;
|
|
|
|
const unsigned char ErrNoSuchUser = 7;
|
|
|
|
const unsigned char ErrFailedOptNegotiation = 8;
|
|
|
|
|
|
|
|
// Creates the TFTP server, passing pointer to C caller
|
|
|
|
extern "C" void* createTFTPServer(){
|
|
|
|
return new TFTPserv();
|
|
|
|
}
|
|
|
|
// Destroys the given TFTP server
|
|
|
|
extern "C" void destroyTFTPServer(void * server){
|
|
|
|
delete (TFTPserv *)(server);
|
|
|
|
}
|
|
|
|
// Adds a file to the TFTP server, from C caller
|
|
|
|
extern "C" int addTFTPFile(void * server, char* filename, unsigned int filenamelen, char* file, unsigned int filelen){
|
|
|
|
string filenamestr(filename,filenamelen);
|
|
|
|
string filestr(file,filelen);
|
|
|
|
((TFTPserv*)server)->addFile(filenamestr,filestr);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// Starts the TFTP server (in new thread)
|
|
|
|
extern "C" int startTFTPServer(void * server){
|
|
|
|
return ((TFTPserv*)server)->start();
|
|
|
|
}
|
|
|
|
// Stops the TFTP server
|
|
|
|
extern "C" int stopTFTPServer(void * server){
|
|
|
|
return ((TFTPserv*)server)->stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
//constructor
|
|
|
|
TFTPserv::TFTPserv(): fileIndexes(), files(), transfers(){
|
|
|
|
index = 0;
|
|
|
|
thread = NULL;
|
|
|
|
shuttingDown = false;
|
|
|
|
smellySock = 0;
|
|
|
|
}
|
|
|
|
//htons except returns a binary string
|
|
|
|
string TFTPserv::htonstring(unsigned short input){
|
|
|
|
unsigned short output = htons(input);
|
|
|
|
string res((const char*)&output,2);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
//adds a "file" based on name, contents
|
|
|
|
void TFTPserv::addFile(string filename, string data){
|
|
|
|
fileIndexes[filename] = index;
|
|
|
|
files[index] = data;
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//checks whether a transfer needs to be marked for resend
|
|
|
|
void TFTPserv::checkRetransmission(map<string,unsigned int> & transfer){
|
|
|
|
unsigned int elapsed = clock() / CLOCKS_PER_SEC - transfer["lastSent"];
|
|
|
|
if ( elapsed <= transfer["timeout"] )
|
|
|
|
return;
|
|
|
|
if (transfer["retries"] >= 3){
|
|
|
|
transfers.erase(&transfer);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
transfer["lastSent"] = 0;
|
|
|
|
transfer["retries"] = transfer["retries"] + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Gets a request packet, figures out what to do, and does it
|
2013-11-06 16:19:10 +10:00
|
|
|
void TFTPserv::dispatchRequest(sockaddr_in &from, string buf)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
unsigned short op = ntohs(*((unsigned short*)buf.c_str()));
|
2013-11-06 16:19:10 +10:00
|
|
|
size_t currentSpot = 2;
|
|
|
|
|
|
|
|
switch (op)
|
|
|
|
{
|
|
|
|
case OpRead:
|
|
|
|
{
|
|
|
|
currentSpot = buf.find('\x00',2);
|
|
|
|
if (currentSpot == string::npos)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
string fn(buf.substr(2,currentSpot - 2)); //get filename
|
|
|
|
if (fileIndexes.count(fn) == 0) //nonexistant file
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t newSpot = buf.find('\x00',currentSpot + 1);
|
|
|
|
if (newSpot == string::npos) //invalid packet format
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//string mode(buf.substr(currentSpot + 1, newSpot - currentSpot - 1)); //don't really need this
|
|
|
|
|
|
|
|
//New transfer!
|
|
|
|
map<string,unsigned int> *transfer = new map<string,unsigned int>();
|
|
|
|
(*transfer)["type"] = OpRead;
|
|
|
|
(*transfer)["fromIp"] = *((unsigned int *)&from.sin_addr);
|
|
|
|
(*transfer)["fromPort"] = from.sin_port;
|
|
|
|
(*transfer)["file"] = fileIndexes[fn];
|
|
|
|
(*transfer)["block"] = 1;
|
|
|
|
(*transfer)["blksize"] = 512;
|
|
|
|
(*transfer)["offset"] = 0;
|
|
|
|
(*transfer)["timeout"] = 3;
|
|
|
|
(*transfer)["lastSent"] = 0;
|
|
|
|
(*transfer)["retries"] = 0;
|
|
|
|
|
|
|
|
//process_options
|
|
|
|
processOptions((struct sockaddr *)&from, sizeof(sockaddr_in), buf, *transfer, (unsigned int)(newSpot + 1));
|
|
|
|
|
|
|
|
transfers.insert(transfer);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case OpAck:
|
|
|
|
{
|
|
|
|
//Got an ack
|
|
|
|
unsigned short block = ntohs(*((unsigned short*)(buf.c_str() + 2)));
|
|
|
|
map<string,unsigned int> *transfer = NULL;
|
|
|
|
//Find transfer
|
|
|
|
for (set<map<string, unsigned int> *>::iterator it = transfers.begin(); it != transfers.end(); ++it)
|
|
|
|
{
|
|
|
|
if ((*(*it))["fromIp"] == *((unsigned int *)&from.sin_addr)
|
2012-11-19 16:46:07 -06:00
|
|
|
&& (*(*it))["fromPort"] == from.sin_port
|
|
|
|
&& (*(*it))["block"] == block)
|
2013-11-06 16:19:10 +10:00
|
|
|
{
|
|
|
|
transfer = *it;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transfer == NULL)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
(*transfer)["offset"] = (*transfer)["offset"] + (*transfer)["blksize"];
|
|
|
|
(*transfer)["block"] = (*transfer)["block"] + 1;
|
|
|
|
(*transfer)["lastSent"] = 0;
|
|
|
|
(*transfer)["retries"] = 0;
|
|
|
|
|
|
|
|
if ((*transfer)["offset"] <= files[(*transfer)["file"]].length())
|
|
|
|
{
|
|
|
|
return; //not complete
|
|
|
|
}
|
|
|
|
|
|
|
|
transfers.erase(transfer); // we're done!
|
|
|
|
delete transfer;
|
|
|
|
}
|
2012-11-19 16:46:07 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Extracts an int option in ascii form; if in range saves it and appends an option ack to replyPacket
|
2013-11-06 16:19:10 +10:00
|
|
|
void TFTPserv::checkIntOption(const char * optName, int min, int max, string & opt, string & val, map<string, unsigned int> & transfer, string & replyPacket)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
if (opt.compare(optName) != 0)
|
2013-11-06 16:19:10 +10:00
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
return;
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
//convert ascii to integer value
|
|
|
|
int intval = 0;
|
2013-11-06 16:19:10 +10:00
|
|
|
for (unsigned int i = 0; i < val.length(); i++)
|
|
|
|
{
|
|
|
|
if (val[i] >= '0' && val[i] <= '9')
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
intval = intval * 10 + val[i] - '0';
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
//Validate it
|
|
|
|
if (intval > max)
|
2013-11-06 16:19:10 +10:00
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
intval = max;
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
2012-11-19 16:46:07 -06:00
|
|
|
if (intval < min)
|
2013-11-06 16:19:10 +10:00
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
intval = min;
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
//Save it
|
|
|
|
transfer[optName] = intval;
|
|
|
|
//append ack
|
2013-11-06 16:19:10 +10:00
|
|
|
replyPacket.append(opt).append(1, (char)0).append(val).append(1, (char)0);
|
2012-11-19 16:46:07 -06:00
|
|
|
}
|
2013-11-06 16:19:10 +10:00
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
//Parses all options from received packet
|
2013-11-06 16:19:10 +10:00
|
|
|
void TFTPserv::processOptions(struct sockaddr * from, unsigned int fromlen, string buf, map<string, unsigned int> & transfer, unsigned int spot)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
//Start with optack (two byte)
|
|
|
|
string data = htonstring(OpOptAck);
|
|
|
|
|
|
|
|
//Loop over options
|
2013-11-06 16:19:10 +10:00
|
|
|
size_t currentSpot = spot;
|
|
|
|
while (currentSpot < buf.length() - 4)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
//Get option
|
2013-11-06 16:19:10 +10:00
|
|
|
size_t nextSpot = buf.find('\x00', currentSpot);
|
|
|
|
|
|
|
|
if (nextSpot == string::npos)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
return;
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
string opt(buf.substr(currentSpot, nextSpot - currentSpot));
|
|
|
|
|
|
|
|
//Get value
|
|
|
|
currentSpot = nextSpot + 1;
|
2013-11-06 16:19:10 +10:00
|
|
|
nextSpot = buf.find('\x00', currentSpot);
|
|
|
|
|
|
|
|
if (nextSpot == string::npos)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
return;
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
string val(buf.substr(currentSpot, nextSpot - currentSpot));
|
|
|
|
currentSpot = nextSpot + 1;
|
2013-11-06 16:19:10 +10:00
|
|
|
for (string::iterator it = opt.begin(); it < opt.end(); it++)
|
2012-11-19 16:46:07 -06:00
|
|
|
*it = tolower(*it);
|
2013-11-06 16:19:10 +10:00
|
|
|
checkIntOption("blksize", 8, 65464, opt, val, transfer, data);
|
|
|
|
checkIntOption("timeout", 1, 255, opt, val, transfer, data);
|
2012-11-19 16:46:07 -06:00
|
|
|
|
2013-11-06 16:19:10 +10:00
|
|
|
if (opt.compare("tsize") == 0)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
//get length
|
2013-11-06 16:19:10 +10:00
|
|
|
size_t flen = files[transfer["file"]].length();
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
//convert to ascii
|
|
|
|
string strlen;
|
2013-11-06 16:19:10 +10:00
|
|
|
while (flen > 0)
|
|
|
|
{
|
|
|
|
strlen.insert(0, 1, (char)((flen % 10) + '0'));
|
2012-11-19 16:46:07 -06:00
|
|
|
flen = flen / 10;
|
|
|
|
}
|
2013-11-06 16:19:10 +10:00
|
|
|
|
|
|
|
data.append(opt).append(1, (char)0).append(strlen).append(1, (char)0);
|
2012-11-19 16:46:07 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
//Send packet
|
2013-11-06 16:19:10 +10:00
|
|
|
sendto(smellySock, data.c_str(), (int)data.length(), 0, from, (int)fromlen);
|
2012-11-19 16:46:07 -06:00
|
|
|
}
|
2013-11-06 16:19:10 +10:00
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
// Asks server to stop
|
2013-11-06 16:19:10 +10:00
|
|
|
int TFTPserv::stop()
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
shuttingDown = true;
|
|
|
|
DWORD res = 0xffffffff;
|
2013-11-06 16:19:10 +10:00
|
|
|
|
|
|
|
if (thread != NULL)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
res = WaitForSingleObject(thread, 5000);
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
thread = NULL;
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Method to pass to CreateThread
|
2013-11-06 16:19:10 +10:00
|
|
|
DWORD WINAPI runTFTPServer(void* server)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
return ((TFTPserv*)server)->run();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Starts server
|
2013-11-06 16:19:10 +10:00
|
|
|
int TFTPserv::start()
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
//get socket
|
|
|
|
smellySock = socket(AF_INET, SOCK_DGRAM, 0);
|
2013-11-06 16:19:10 +10:00
|
|
|
|
|
|
|
if (smellySock == INVALID_SOCKET)
|
|
|
|
{
|
|
|
|
return -1;
|
|
|
|
}
|
2012-11-19 16:46:07 -06:00
|
|
|
|
|
|
|
//Se up server socket address
|
|
|
|
struct sockaddr_in server;
|
|
|
|
server.sin_family = AF_INET;
|
|
|
|
server.sin_port = htons(tftpServPort);
|
|
|
|
server.sin_addr.s_addr = 0; //a.k.a. INADDR_ANY
|
|
|
|
|
|
|
|
// Bind address to socket
|
|
|
|
if (bind(smellySock, (struct sockaddr *)&server, sizeof(server)) != 0)
|
2013-11-06 16:19:10 +10:00
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
return GetLastError();
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
2012-11-19 16:46:07 -06:00
|
|
|
|
2013-11-06 16:19:10 +10:00
|
|
|
thread = CreateThread(NULL, 0, &runTFTPServer, this, 0, NULL);
|
|
|
|
|
|
|
|
if (thread != NULL)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
return ERROR_SUCCESS;
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
return GetLastError();
|
|
|
|
}
|
|
|
|
|
|
|
|
//Internal run method that does all the hard work
|
2013-11-06 16:19:10 +10:00
|
|
|
int TFTPserv::run()
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
// Setup timeout
|
|
|
|
fd_set recvSet;
|
|
|
|
fd_set sendSet;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
//Main packet-handling loop
|
|
|
|
shuttingDown = false;
|
|
|
|
while (shuttingDown == false){
|
|
|
|
FD_ZERO(&recvSet);
|
|
|
|
FD_SET(smellySock, &recvSet);
|
|
|
|
FD_ZERO(&sendSet);
|
2013-11-06 16:19:10 +10:00
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
//Do we need to check for sent items? Let's see
|
2013-11-06 16:19:10 +10:00
|
|
|
for (set<map<string, unsigned int> *>::iterator it = transfers.begin(); it != transfers.end(); ++it){
|
|
|
|
if ((*(*it))["lastSent"] != 0){
|
2012-11-19 16:46:07 -06:00
|
|
|
FD_SET(smellySock, &recvSet);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-11-06 16:19:10 +10:00
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
struct timeval tv;
|
|
|
|
tv.tv_sec = 1;
|
|
|
|
tv.tv_usec = 0;
|
2013-11-06 16:19:10 +10:00
|
|
|
n = select((int)(smellySock + 1), &recvSet, &sendSet, NULL, &tv);
|
|
|
|
|
|
|
|
if (n == -1)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
break; //Error
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
2012-11-19 16:46:07 -06:00
|
|
|
|
|
|
|
//Get request
|
|
|
|
char receiveBuf[bufferSize];
|
|
|
|
struct sockaddr client;
|
|
|
|
int clientLength = sizeof(client);
|
2013-11-06 16:19:10 +10:00
|
|
|
int receiveSize = 0;
|
|
|
|
|
|
|
|
if (n != 0)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
receiveSize = recvfrom(smellySock, receiveBuf, bufferSize, 0, &client, &clientLength);
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
if (receiveSize > 0)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
string data(receiveBuf, receiveSize);
|
2013-11-06 16:19:10 +10:00
|
|
|
dispatchRequest(*((sockaddr_in *)&client), data);
|
2012-11-19 16:46:07 -06:00
|
|
|
}
|
2013-11-06 16:19:10 +10:00
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
//Now see if we need to transmit/retransmit another block
|
2013-11-06 16:19:10 +10:00
|
|
|
for (set<map<string, unsigned int> *>::iterator it = transfers.begin(); it != transfers.end(); ++it)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
map<string, unsigned int> & transfer = *(*it);
|
2013-11-06 16:19:10 +10:00
|
|
|
if (transfer["type"] != OpRead)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
continue;
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
if (transfer["lastSent"] != 0)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
checkRetransmission(transfer);
|
2013-11-06 16:19:10 +10:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
string block = files[transfer["file"]].substr(transfer["offset"], transfer["blksize"]);
|
|
|
|
if (block.size() > 0)
|
|
|
|
{
|
2012-11-19 16:46:07 -06:00
|
|
|
string packet(htonstring(OpData));
|
2013-11-06 16:19:10 +10:00
|
|
|
packet.append(htonstring(transfer["block"]));
|
2012-11-19 16:46:07 -06:00
|
|
|
packet.append(block);
|
|
|
|
//Send packet
|
|
|
|
//first get address
|
|
|
|
sockaddr_in client;
|
2013-11-06 16:19:10 +10:00
|
|
|
memset((void*)&client, 0, sizeof(client));
|
2012-11-19 16:46:07 -06:00
|
|
|
client.sin_family = AF_INET;
|
|
|
|
*((unsigned int *)&client.sin_addr) = transfer["fromIp"];
|
|
|
|
client.sin_port = (unsigned short)transfer["fromPort"];
|
|
|
|
//whew. now send
|
2013-11-06 16:19:10 +10:00
|
|
|
sendto(smellySock, packet.c_str(), (int)packet.size(), 0, (sockaddr*)&client, (int)sizeof(sockaddr_in));
|
|
|
|
|
2012-11-19 16:46:07 -06:00
|
|
|
transfer["lastSent"] = clock() / CLOCKS_PER_SEC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef WIN32
|
|
|
|
closesocket(smellySock);
|
|
|
|
#else
|
|
|
|
close(smellySock);
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|