You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
396 lines
14 KiB
396 lines
14 KiB
/*++
|
|
|
|
Copyright (c) 2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
util.c
|
|
|
|
Abstract:
|
|
|
|
This module contains functions to parse and construct server
|
|
file paths for client requests, request option negotiation,
|
|
and client access security.
|
|
|
|
Author:
|
|
|
|
Jeffrey C. Venable, Sr. (jeffv) 01-Jun-2001
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
|
|
|
|
#define IS_SEPARATOR(c) (((c) == '\\') || ((c) == '/'))
|
|
|
|
|
|
BOOL
|
|
TftpdUtilIsValidString(char *string, unsigned int maxLength) {
|
|
|
|
UINT x;
|
|
|
|
// Make sure 'string' is null-terminated.
|
|
for (x = 0; x < maxLength; x++)
|
|
if (!string[x])
|
|
return (TRUE);
|
|
|
|
return (FALSE);
|
|
|
|
} // TftpdUtilIsValidString()
|
|
|
|
|
|
BOOL
|
|
TftpdUtilCanonicalizeFileName(char *filename) {
|
|
|
|
char *source, *destination, *lastComponent;
|
|
|
|
// The canonicalization is done in place. Initialize the source and
|
|
// destination pointers to point to the same place.
|
|
source = destination = filename;
|
|
|
|
// The lastComponent variable is used as a placeholder when
|
|
// backtracking over trailing blanks and dots. It points to the
|
|
// first character after the last directory separator or the
|
|
// beginning of the pathname.
|
|
lastComponent = filename;
|
|
|
|
// Get rid of leading directory separators.
|
|
while ((*source != 0) && IS_SEPARATOR(*source))
|
|
source++;
|
|
|
|
// Walk through the pathname until we reach the zero terminator. At
|
|
// the start of this loop, source points to the first charaecter
|
|
// after a directory separator or the first character of the
|
|
// pathname.
|
|
while (*source) {
|
|
|
|
if (*source == '.') {
|
|
|
|
// If we see a dot, look at the next character.
|
|
if (IS_SEPARATOR(*(source + 1))) {
|
|
|
|
// If the next character is a directory separator,
|
|
// advance the source pointer to the directory
|
|
// separator.
|
|
source++;
|
|
|
|
} else if ((*(source + 1) == '.') && IS_SEPARATOR(*(source + 2))) {
|
|
|
|
// If the following characters are ".\", we have a "..\".
|
|
// Advance the source pointer to the "\".
|
|
source += 2;
|
|
|
|
// Move the destination pointer to the character before the
|
|
// last directory separator in order to prepare for backing
|
|
// up. This may move the pointer before the beginning of
|
|
// the name pointer.
|
|
destination -= 2;
|
|
|
|
// If destination points before the beginning of the name
|
|
// pointer, fail because the user is attempting to go
|
|
// to a higher directory than the TFTPD root. This is
|
|
// the equivalent of a leading "..\", but may result from
|
|
// a case like "dir\..\..\file".
|
|
if (destination <= filename)
|
|
return (FALSE);
|
|
|
|
// Back up the destination pointer to after the last
|
|
// directory separator or to the beginning of the pathname.
|
|
// Backup to the beginning of the pathname will occur
|
|
// in a case like "dir\..\file".
|
|
while ((destination >= filename) && !IS_SEPARATOR(*destination))
|
|
destination--;
|
|
|
|
// destination points to \ or character before name; we
|
|
// want it to point to character after last \.
|
|
destination++;
|
|
|
|
} else {
|
|
|
|
// The characters after the dot are not "\" or ".\", so
|
|
// so just copy source to destination until we reach a
|
|
// directory separator character. This will occur in
|
|
// a case like ".file" (filename starts with a dot).
|
|
do {
|
|
*destination++ = *source++;
|
|
} while (*source && !IS_SEPARATOR(*source));
|
|
|
|
} // if (IS_SEPARATOR(*(source + 1)))
|
|
|
|
} else {
|
|
|
|
// source does not point to a dot, so copy source to
|
|
// destination until we get to a directory separator.
|
|
while (*source && !IS_SEPARATOR(*source))
|
|
*destination++ = *source++;
|
|
|
|
} // if (*source == '.')
|
|
|
|
// Truncate trailing blanks. destination should point to the last
|
|
// character before the directory separator, so back up over blanks.
|
|
while ((destination > lastComponent) && (*(destination - 1) == ' '))
|
|
destination--;
|
|
|
|
// At this point, source points to a directory separator or to
|
|
// a zero terminator. If it is a directory separator, put one
|
|
// in the destination.
|
|
if (IS_SEPARATOR(*source)) {
|
|
|
|
// If we haven't put the directory separator in the path name,
|
|
// put it in.
|
|
if ((destination != filename) && !IS_SEPARATOR(*(destination - 1)))
|
|
*destination++ = '\\';
|
|
|
|
// It is legal to have multiple directory separators, so get
|
|
// rid of them here. Example: "dir\\\\\\\\file".
|
|
do {
|
|
source++;
|
|
} while (*source && IS_SEPARATOR(*source));
|
|
|
|
// Make lastComponent point to the character after the directory
|
|
// separator.
|
|
lastComponent = destination;
|
|
|
|
} // if (IS_SEPARATOR(*source))
|
|
|
|
} // while (*source)
|
|
|
|
// We're just about done. If there was a trailing .. (example:
|
|
// "file\.."), trailing . ("file\."), or multiple trailing
|
|
// separators ("file\\\\"), then back up one since separators are
|
|
// illegal at the end of a pathname.
|
|
if ((destination != filename) && IS_SEPARATOR(*(destination - 1)))
|
|
destination--;
|
|
|
|
// Terminate the destination string.
|
|
*destination = '\0';
|
|
|
|
return (TRUE);
|
|
|
|
} // TftpdUtilCanonicalizeFileName()
|
|
|
|
|
|
BOOL
|
|
TftpdUtilPrependStringToFileName(char *filename, DWORD maxLength, char *prefix) {
|
|
|
|
DWORD prefixLength = strlen(prefix);
|
|
DWORD filenameLength = strlen(filename);
|
|
BOOL prefixHasSeparater = (prefix[prefixLength - 1] == '\\');
|
|
BOOL filenameHasSeparater = (filename[0] == '\\');
|
|
DWORD separatorLength = 0;
|
|
|
|
if (!prefixHasSeparater && !filenameHasSeparater)
|
|
separatorLength = 1;
|
|
|
|
if (prefixHasSeparater && filenameHasSeparater)
|
|
prefixLength--;
|
|
|
|
if ((prefixLength + separatorLength + filenameLength) > (maxLength - 1))
|
|
return (FALSE);
|
|
|
|
// Move the existing string down to make room for the prefix.
|
|
MoveMemory(filename + prefixLength + separatorLength, filename, filenameLength + 1);
|
|
// Move the prefix into place.
|
|
CopyMemory(filename, prefix, prefixLength);
|
|
// If necessary, insert a backslash between the prefix and the file name.
|
|
if (separatorLength)
|
|
filename[prefixLength] = '\\';
|
|
// Terminate the string.
|
|
filename[prefixLength + separatorLength + filenameLength] = '\0';
|
|
|
|
return (TRUE);
|
|
|
|
} // TftpdUtilPrependStringToFileName()
|
|
|
|
|
|
BOOL
|
|
TftpdUtilGetFileModeAndOptions(PTFTPD_CONTEXT context, PTFTPD_BUFFER buffer) {
|
|
|
|
DWORD remaining = (TFTPD_DEF_DATA - FIELD_OFFSET(TFTPD_BUFFER, message.data));
|
|
char *filename, *mode, *option;
|
|
int length;
|
|
|
|
// Obtain and validate the requested filename.
|
|
filename = buffer->message.rrq.data; // or wrq, same thing
|
|
if (!TftpdUtilIsValidString(filename, remaining)) {
|
|
TftpdIoSendErrorPacket(buffer, TFTPD_ERROR_ILLEGAL_OPERATION,
|
|
"Malformed file name");
|
|
return (FALSE);
|
|
}
|
|
length = (strlen(filename) + 1);
|
|
remaining -= length;
|
|
if (!TftpdUtilCanonicalizeFileName(filename)) {
|
|
TftpdIoSendErrorPacket(buffer, TFTPD_ERROR_ILLEGAL_OPERATION,
|
|
"Malformed file name");
|
|
return (FALSE);
|
|
}
|
|
|
|
// Obtain and validate the mode.
|
|
mode = (char *)(buffer->message.rrq.data + length);
|
|
if (!TftpdUtilIsValidString(mode, remaining))
|
|
return (FALSE);
|
|
length = (strlen(mode) + 1);
|
|
if (!_stricmp(mode, "netascii"))
|
|
context->mode = TFTPD_MODE_TEXT;
|
|
else if (!_stricmp(mode, "octet"))
|
|
context->mode = TFTPD_MODE_BINARY;
|
|
else {
|
|
TftpdIoSendErrorPacket(buffer, TFTPD_ERROR_ILLEGAL_OPERATION,
|
|
"Illegal TFTP operation");
|
|
return (FALSE);
|
|
}
|
|
remaining -= length;
|
|
|
|
// Obtain and validate any requested options.
|
|
option = (char *)(mode + length);
|
|
while (remaining && *option) {
|
|
|
|
char *value;
|
|
|
|
if (!TftpdUtilIsValidString(option, remaining))
|
|
break;
|
|
length = (strlen(option) + 1);
|
|
remaining -= length;
|
|
value = (char *)(option + length);
|
|
if (!remaining || !TftpdUtilIsValidString(value, remaining))
|
|
break;
|
|
length = (strlen(value) + 1);
|
|
remaining -= length;
|
|
|
|
if (!_stricmp(option, "blksize")) {
|
|
if (!(context->options & TFTPD_OPTION_BLKSIZE)) {
|
|
int blksize = atoi(value);
|
|
// Workaround for problem in .98 version of ROM, which
|
|
// doesn't like our OACK response. If the requested blksize is
|
|
// 1456, pretend that the option wasn't specified. In the case
|
|
// of the ROM's TFTP layer, this is the only option specified,
|
|
// so ignoring it will mean that we don't send an OACK, and the
|
|
// ROM will deign to talk to us. Note that our TFTP code uses
|
|
// a blksize of 1432, so this workaround won't affect us.
|
|
if (blksize != 1456) {
|
|
blksize = __max(TFTPD_MIN_DATA, blksize);
|
|
blksize = __min(TFTPD_MAX_DATA, blksize);
|
|
context->blksize = blksize;
|
|
context->options |= TFTPD_OPTION_BLKSIZE;
|
|
}
|
|
}
|
|
} else if (!_stricmp(option, "timeout")) {
|
|
if (!(context->options & TFTPD_OPTION_TIMEOUT)) {
|
|
int seconds = atoi(value);
|
|
if ((seconds >= 1) && (seconds <= 255)) {
|
|
context->timeout = (seconds * 1000);
|
|
context->options |= TFTPD_OPTION_TIMEOUT;
|
|
}
|
|
}
|
|
} else if (!_stricmp(option, "tsize")) {
|
|
if (context->mode != TFTPD_MODE_TEXT) {
|
|
context->options |= TFTPD_OPTION_TSIZE;
|
|
context->filesize.QuadPart = _atoi64(value);
|
|
}
|
|
}
|
|
|
|
// Advance over the option and its value to next option or NUL terminator.
|
|
option += (strlen(option) + 1 + length);
|
|
|
|
} // while (*option)
|
|
|
|
if (!(context->options & TFTPD_OPTION_BLKSIZE))
|
|
context->blksize = TFTPD_DEF_DATA;
|
|
|
|
// Now that we've obtained all the information we need from the buffer, we're
|
|
// free to overwrite it (reuse it) to prepend the filename with its prefix.
|
|
if (!TftpdUtilPrependStringToFileName(filename,
|
|
TFTPD_DEF_BUFFER - FIELD_OFFSET(TFTPD_BUFFER, message.rrq.data),
|
|
globals.parameters.rootDirectory)) {
|
|
TftpdIoSendErrorPacket(buffer, TFTPD_ERROR_ILLEGAL_OPERATION,
|
|
"Malformed file name");
|
|
return (FALSE);
|
|
}
|
|
|
|
length = (strlen(filename) + 1);
|
|
context->filename = (char *)HeapAlloc(globals.hServiceHeap, HEAP_ZERO_MEMORY, length);
|
|
if (context->filename == NULL) {
|
|
TftpdIoSendErrorPacket(buffer, TFTPD_ERROR_UNDEFINED, "Out of memory");
|
|
return (FALSE);
|
|
}
|
|
strcpy(context->filename, filename);
|
|
|
|
return (TRUE);
|
|
|
|
} // TftpdUtilGetFileModeAndOptions()
|
|
|
|
|
|
PTFTPD_BUFFER
|
|
TftpdUtilSendOackPacket(PTFTPD_BUFFER buffer) {
|
|
|
|
PTFTPD_CONTEXT context = buffer->internal.context;
|
|
char *oack;
|
|
int length;
|
|
|
|
// Build the OACK message.
|
|
ZeroMemory(&buffer->message, buffer->internal.datasize);
|
|
buffer->message.opcode = htons(TFTPD_OACK);
|
|
oack = (char *)&buffer->message.oack.data;
|
|
buffer->internal.io.bytes = (FIELD_OFFSET(TFTPD_BUFFER, message.oack.data) -
|
|
FIELD_OFFSET(TFTPD_BUFFER, message.opcode));
|
|
if (context->options & TFTPD_OPTION_BLKSIZE) {
|
|
strcpy(oack, "blksize");
|
|
oack += 8;
|
|
_itoa(context->blksize, oack, 10);
|
|
length = (strlen(oack) + 1);
|
|
oack += length;
|
|
buffer->internal.io.bytes += (8 + length);
|
|
}
|
|
if (context->options & TFTPD_OPTION_TIMEOUT) {
|
|
strcpy(oack, "timeout");
|
|
oack += 8;
|
|
_itoa((context->timeout / 1000), oack, 10);
|
|
length = (strlen(oack) + 1);
|
|
oack += length;
|
|
buffer->internal.io.bytes += (8 + length);
|
|
}
|
|
if (context->options & TFTPD_OPTION_TSIZE) {
|
|
strcpy(oack, "tsize");
|
|
oack += 6;
|
|
_itoa((int)context->filesize.QuadPart, oack, 10);
|
|
length = (strlen(oack) + 1);
|
|
oack += length;
|
|
buffer->internal.io.bytes += (6 + length);
|
|
}
|
|
|
|
TFTPD_DEBUG((TFTPD_TRACE_IO,
|
|
"TftpdUtilSendOackPacket(buffer = %p, context = %p): Issuing OACK, %d bytes. "
|
|
"[blksize = %d, timeout = %d, tsize = %d]\n",
|
|
buffer, context, buffer->internal.io.bytes,
|
|
context->blksize, context->timeout, context->filesize));
|
|
|
|
if (!TftpdProcessComplete(buffer))
|
|
return (buffer);
|
|
|
|
return (TftpdIoSendPacket(buffer));
|
|
|
|
} // TftpdUtilSendOackPacket()
|
|
|
|
|
|
BOOL
|
|
TftpdUtilMatch(const char *const p, const char *const q) {
|
|
|
|
switch (*p) {
|
|
|
|
case '\0' :
|
|
return (!(*q));
|
|
|
|
case '*' :
|
|
return (TftpdUtilMatch(p + 1, q) || (*q && TftpdUtilMatch(p, q + 1)));
|
|
|
|
case '?' :
|
|
return (*q && TftpdUtilMatch(p + 1, q + 1));
|
|
|
|
default :
|
|
return ((*p == *q) && TftpdUtilMatch(p + 1, q + 1));
|
|
|
|
} // switch (*p)
|
|
|
|
} // TftpdUtilMatch()
|