mirror of
https://github.com/ksherlock/gopher.git
synced 2024-06-02 02:41:33 +00:00
648 lines
15 KiB
C
648 lines
15 KiB
C
#pragma optimize 79
|
|
#pragma noroot
|
|
|
|
#include <GSOS.h>
|
|
#include <Memory.h>
|
|
#include <MiscTool.h>
|
|
#include <tcpip.h>
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <unistd.h>
|
|
|
|
#include "url.h"
|
|
#include "connection.h"
|
|
#include "readline2.h"
|
|
#include "options.h"
|
|
#include "s16debug.h"
|
|
|
|
#include "prototypes.h"
|
|
#include "smb.h"
|
|
#include "smb.errors.h"
|
|
|
|
static struct smb2_header_sync header;
|
|
|
|
|
|
typedef struct smb_response {
|
|
|
|
smb2_header_sync header;
|
|
union {
|
|
smb2_error_response error;
|
|
smb2_negotiate_response negotiate;
|
|
smb2_session_setup_response setup;
|
|
smb2_tree_connect_response tree_connect;
|
|
smb2_logoff_response logoff;
|
|
} body;
|
|
|
|
} smb_response;
|
|
|
|
|
|
// 3.3.4.4 Sending an Error Response
|
|
// http://msdn.microsoft.com/en-us/library/cc246722.aspx
|
|
static int is_error(const smb_response *msg)
|
|
{
|
|
uint32_t status = msg->header.status;
|
|
uint16_t command = msg->header.command;
|
|
|
|
if (status == 0) return false;
|
|
|
|
switch (command)
|
|
{
|
|
case SMB2_SESSION_SETUP:
|
|
if (status == STATUS_MORE_PROCESSING_REQUIRED)
|
|
return false;
|
|
break;
|
|
|
|
case SMB2_QUERY_INFO:
|
|
case SMB2_READ:
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
return false;
|
|
break;
|
|
|
|
case SMB2_CHANGE_NOTIFY:
|
|
if (status == STATUS_NOTIFY_ENUM_DIR)
|
|
return false;
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void dump_header(const smb2_header_sync *header)
|
|
{
|
|
fprintf(stdout, " protocol_id: %08lx\n", header->protocol_id);
|
|
fprintf(stdout, "structure_size: %04x\n", header->structure_size);
|
|
fprintf(stdout, " credit_charge: %04x\n", header->credit_charge);
|
|
fprintf(stdout, " status: %08lx\n", header->status);
|
|
fprintf(stdout, " command: %04x\n", header->command);
|
|
fprintf(stdout, " credit: %04x\n", header->credit);
|
|
fprintf(stdout, " flags: %08lx\n", header->flags);
|
|
fprintf(stdout, " next_command: %08lx\n", header->next_command);
|
|
fprintf(stdout, " message_id: %08lx%08lx\n", header->message_id[1], header->message_id[0]);
|
|
fprintf(stdout, " reserved: %08lx\n", header->reserved);
|
|
fprintf(stdout, " tree_id: %08lx\n", header->tree_id);
|
|
fprintf(stdout, " session_id: %08lx%08lx\n", header->session_id[1], header->session_id[0]);
|
|
|
|
fprintf(stdout, " signature:\n");
|
|
hexdump(header->signature, 16);
|
|
|
|
}
|
|
|
|
|
|
static void dump_error(const smb2_error_response *msg)
|
|
{
|
|
fprintf(stdout, "structure_size: %04x\n", msg->structure_size);
|
|
fprintf(stdout, " reserved: %04x\n", msg->reserved);
|
|
fprintf(stdout, " bytecount: %08lx\n", msg->bytecount);
|
|
|
|
fprintf(stdout, " error_data:\n");
|
|
hexdump((const char *)msg + sizeof(smb2_error_response) , msg->bytecount);
|
|
}
|
|
|
|
|
|
static void dump_negotiate(const smb2_negotiate_response *msg)
|
|
{
|
|
fprintf(stdout, " structure_size: %04x\n", msg->structure_size);
|
|
fprintf(stdout, " security_mode: %04x\n", msg->security_mode);
|
|
fprintf(stdout, " dialect_revision: %04x\n", msg->dialect_revision);
|
|
fprintf(stdout, " reserved: %04x\n", msg->reserved);
|
|
fprintf(stdout, " server_guid:\n");
|
|
hexdump(msg->server_guid, 16);
|
|
|
|
fprintf(stdout, " capabilities: %08lx\n", msg->capabilities);
|
|
fprintf(stdout, " max_transact_size: %08lx\n", msg->max_transact_size);
|
|
fprintf(stdout, " max_read_size: %08lx\n", msg->max_read_size);
|
|
fprintf(stdout, " max_write_size: %08lx\n", msg->max_write_size);
|
|
|
|
fprintf(stdout, " system_time: %08lx%08lx\n",
|
|
msg->system_time[1], msg->system_time[0]);
|
|
|
|
fprintf(stdout, " server_start_time: %08lx%08lx\n",
|
|
msg->server_start_time[1], msg->server_start_time[0]);
|
|
|
|
fprintf(stdout, "security_buffer_offset: %04x\n", msg->security_buffer_offset);
|
|
fprintf(stdout, "security_buffer_length: %04x\n", msg->security_buffer_length);
|
|
fprintf(stdout, " reserved2: %08lx\n", msg->reserved2);
|
|
|
|
fprintf(stdout, " buffer:\n");
|
|
hexdump((const char *)msg - sizeof(smb2_header_sync) + msg->security_buffer_offset,
|
|
msg->security_buffer_length);
|
|
}
|
|
|
|
static void dump_setup(const smb2_session_setup_response *msg)
|
|
{
|
|
fprintf(stdout, " structure_size: %04x\n", msg->structure_size);
|
|
fprintf(stdout, " session_flags: %04x\n", msg->session_flags);
|
|
fprintf(stdout, "security_buffer_offset: %04x\n", msg->security_buffer_offset);
|
|
fprintf(stdout, "security_buffer_length: %04x\n", msg->security_buffer_length);
|
|
|
|
fprintf(stdout, " buffer:\n");
|
|
hexdump((const char *)msg - sizeof(smb2_header_sync) + msg->security_buffer_offset,
|
|
msg->security_buffer_length);
|
|
}
|
|
|
|
|
|
static void dump_tree_connect(const smb2_tree_connect_response *msg)
|
|
{
|
|
fprintf(stdout, " structure_size: %04x\n", msg->structure_size);
|
|
fprintf(stdout, " share_type: %02x\n", msg->share_type);
|
|
fprintf(stdout, " reserved: %02x\n", msg->reserved);
|
|
fprintf(stdout, " share_flags: %08lx\n", msg->share_flags);
|
|
fprintf(stdout, " capabilities: %08lx\n", msg->capabilities);
|
|
fprintf(stdout, " maximal_access: %08lx\n", msg->maximal_access);
|
|
}
|
|
|
|
static void dump_error(const smb2_error_response *msg)
|
|
{
|
|
fprintf(stdout, " structure_size: %04x\n", msg->structure_size);
|
|
fprintf(stdout, " reserved: %04x\n", msg->reserved);
|
|
fprintf(stdout, " bytecount: %08lx\n", msg->bytecount);
|
|
|
|
fprintf(stdout, " error_data:\n");
|
|
hexdump((const char *)msg + sizeof(smb2_error_response), msg->bytecount);
|
|
}
|
|
|
|
static void dump_response(const smb_response *msg)
|
|
{
|
|
if (!msg) return;
|
|
dump_header(&msg->header);
|
|
|
|
if (is_error(msg))
|
|
{
|
|
dump_error(&msg->body.error);
|
|
return;
|
|
}
|
|
|
|
switch (msg->header.command)
|
|
{
|
|
case SMB2_NEGOTIATE:
|
|
dump_negotiate(&msg->body.negotiate);
|
|
break;
|
|
|
|
case SMB2_SESSION_SETUP:
|
|
dump_setup(&msg->body.setup);
|
|
break;
|
|
|
|
case SMB2_TREE_CONNECT:
|
|
dump_tree_connect(&msg->body.tree_connect);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void write_message(Word ipid, const void *data1, unsigned size1, const void *data2, unsigned size2)
|
|
{
|
|
uint8_t nbthead[4];
|
|
uint32_t size;
|
|
|
|
size = sizeof(header) + size1 + size2;
|
|
|
|
// http://support.microsoft.com/kb/204279
|
|
nbthead[0] = 0;
|
|
nbthead[1] = size >> 16;
|
|
nbthead[2] = size >> 8;
|
|
nbthead[3] = size;
|
|
|
|
if (++header.message_id[0] == 0)
|
|
++header.message_id[1];
|
|
|
|
TCPIPWriteTCP(ipid, (dataPtr)nbthead, sizeof(nbthead), false, false);
|
|
TCPIPWriteTCP(ipid, (dataPtr)header, sizeof(header), false, false);
|
|
TCPIPWriteTCP(ipid, (dataPtr)data1, size1, false, false);
|
|
TCPIPWriteTCP(ipid, (dataPtr)data2, size2, true, false);
|
|
}
|
|
|
|
static Handle read_response(Word ipid)
|
|
{
|
|
static srBuff sr;
|
|
static rrBuff rb;
|
|
|
|
// read an smb response. Check the first 4 bytes for the message length.
|
|
uint32_t size = 0;
|
|
uint8_t nbthead[4];
|
|
LongWord qtick;
|
|
Word terr;
|
|
|
|
qtick = GetTick() + 30 * 60;
|
|
for(;;)
|
|
{
|
|
|
|
|
|
TCPIPPoll();
|
|
|
|
terr = TCPIPStatusTCP(ipid, &sr);
|
|
|
|
if (sr.srRcvQueued >= 4) break;
|
|
|
|
|
|
/*
|
|
* the only reasonable error is if the connection is closing.
|
|
* that's ok if there's still pending data (handled above)
|
|
*/
|
|
if (terr) return (Handle)0;
|
|
|
|
|
|
if (GetTick() >= qtick)
|
|
{
|
|
fprintf(stderr, "Read timed out.\n");
|
|
return (Handle)0;
|
|
}
|
|
|
|
}
|
|
|
|
terr = TCPIPReadTCP(ipid, 0, (Ref)&nbthead, 4, &rb);
|
|
|
|
if (nbthead[0] != 0) {
|
|
fprintf(stderr, "Bad NBT Header\n");
|
|
return (Handle)0;
|
|
}
|
|
size = nbthead[3];
|
|
size |= nbthead[2] << 8;
|
|
size |= nbthead[1] << 16;
|
|
|
|
for(;;)
|
|
{
|
|
|
|
|
|
TCPIPPoll();
|
|
|
|
terr = TCPIPStatusTCP(ipid, &sr);
|
|
|
|
if (sr.srRcvQueued >= size) break;
|
|
|
|
if (GetTick() >= qtick)
|
|
{
|
|
fprintf(stderr, "Read timed out.\n");
|
|
return (Handle)0;
|
|
}
|
|
|
|
}
|
|
terr = TCPIPReadTCP(ipid, 2, (Ref)0, size, &rb);
|
|
|
|
return rb.rrBuffHandle;
|
|
|
|
}
|
|
|
|
|
|
static uint16_t *cstring_to_unicode(const char *str)
|
|
{
|
|
unsigned length = strlen(str);
|
|
uint16_t *path;
|
|
unsigned i;
|
|
|
|
path = (uint16_t *)malloc(length * 2 + 4);
|
|
|
|
for (i = 0; i < length; ++i)
|
|
{
|
|
path[i + 1] = str[i];
|
|
}
|
|
path[0] = length;
|
|
path[length + 1] = 0;
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
static uint16_t *pstring_to_unicode(const char *str)
|
|
{
|
|
unsigned length = str[0];
|
|
uint16_t *path;
|
|
unsigned i;
|
|
|
|
path = (uint16_t *)malloc(length * 2 + 4);
|
|
|
|
for (i = 0; i < length; ++i)
|
|
{
|
|
path[i + 1] = str[i + 1];
|
|
}
|
|
path[0] = length;
|
|
path[length + 1] = 0;
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
static uint16_t *gsstring_to_unicode(const GSString255 *str)
|
|
{
|
|
unsigned length = str->length;
|
|
uint16_t *path;
|
|
unsigned i;
|
|
|
|
path = (uint16_t *)malloc(length * 2 + 4);
|
|
|
|
for (i = 0; i < length; ++i)
|
|
{
|
|
path[i + 1] = str->text[i];
|
|
}
|
|
path[0] = length;
|
|
path[length + 1] = 0;
|
|
|
|
return path;
|
|
}
|
|
|
|
|
|
unsigned has_spnego = 0;
|
|
unsigned has_mech_ntlmssp = 0;
|
|
|
|
// returns the new offset.
|
|
unsigned scan_asn1(const uint8_t *data, unsigned offset, unsigned length)
|
|
{
|
|
// '1.3.6.1.5.5.2'
|
|
static uint8_t kSPNEGO[] = {0x2B, 0x06, 0x01, 0x05, 0x05, 0x02};
|
|
|
|
// '1.3.6.1.4.1.311.2.2.10'
|
|
static uint8_t kNTLMSSP[] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0A};
|
|
unsigned tag;
|
|
unsigned len;
|
|
|
|
restart:
|
|
|
|
tag = data[offset++];
|
|
len = data[offset++];
|
|
|
|
if (offset >= length) return offset;
|
|
|
|
if (len >= 0x80) {
|
|
switch (len & 0x7f)
|
|
{
|
|
case 1:
|
|
len = data[offset++];
|
|
break;
|
|
case 2:
|
|
len = data[offset++] << 8;
|
|
len += data[offset++];
|
|
break;
|
|
|
|
default:
|
|
// oops, too big.
|
|
return length;
|
|
}
|
|
}
|
|
|
|
//fprintf(stdout, "%02x %02x\n", tag, len);
|
|
|
|
switch(tag)
|
|
{
|
|
case 0x30: // sequence
|
|
case 0x60: // application
|
|
while (offset < length)
|
|
offset = scan_asn1(data, offset, length);
|
|
break;
|
|
|
|
case 0xa0:
|
|
case 0xa1:
|
|
case 0xa2:
|
|
case 0xa3:
|
|
// jump back to the start?
|
|
//return scan_asn1(data, offset, length);
|
|
goto restart;
|
|
break;
|
|
|
|
case 0x06: // oid
|
|
if (len == 6 && memcmp(data + offset, kSPNEGO, 6) == 0)
|
|
{
|
|
//fprintf(stdout, "spnego!\n");
|
|
has_spnego = true;
|
|
break;
|
|
}
|
|
if (len == 0x0a && memcmp(data + offset, kNTLMSSP, 0x0a) == 0)
|
|
{
|
|
//fprintf(stdout, "ntlmssp!\n");
|
|
has_mech_ntlmssp = true;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return offset + len;
|
|
}
|
|
|
|
int negotiate(Word ipid, uint16_t *path)
|
|
{
|
|
static struct smb2_negotiate_request negotiate_req;
|
|
static struct smb2_session_setup_request setup_req;
|
|
static struct smb2_tree_connect_request tree_req;
|
|
|
|
static uint16_t dialects[] = { 0x0202 };
|
|
|
|
uint16_t tmp;
|
|
|
|
|
|
|
|
smb_response *responsePtr;
|
|
|
|
|
|
Handle h;
|
|
|
|
uint32_t size = 0;
|
|
|
|
|
|
|
|
memset(&header, 0, sizeof(header));
|
|
memset(&negotiate_req, 0, sizeof(negotiate_req));
|
|
memset(&setup_req, 0, sizeof(setup_req));
|
|
memset(&tree_req, 0, sizeof(tree_req));
|
|
|
|
|
|
header.protocol_id = SMB2_MAGIC; // '\xfeSMB';
|
|
header.structure_size = 64;
|
|
header.command = SMB2_NEGOTIATE;
|
|
|
|
negotiate_req.structure_size = 36;
|
|
negotiate_req.dialect_count = 1; // ?
|
|
negotiate_req.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
|
|
negotiate_req.capabilities = 0;
|
|
|
|
//negotiate_req.dialects[0] = 0x202; // smb 2.002
|
|
|
|
|
|
write_message(ipid, &negotiate_req, sizeof(negotiate_req), dialects, sizeof(dialects));
|
|
|
|
// read a response...
|
|
|
|
h = read_response(ipid);
|
|
if (!h) return -1;
|
|
HLock(h);
|
|
|
|
//hexdump(*h, GetHandleSize(h));
|
|
|
|
responsePtr = *(smb_response **)h;
|
|
|
|
dump_response(responsePtr);
|
|
|
|
if (responsePtr->header.protocol_id != SMB2_MAGIC || responsePtr->header.structure_size != 64)
|
|
{
|
|
DisposeHandle(h);
|
|
fprintf(stderr, "Invalid SMB2 header\n");
|
|
return -1;
|
|
}
|
|
|
|
if (responsePtr->header.command != SMB2_NEGOTIATE)
|
|
{
|
|
DisposeHandle(h);
|
|
fprintf(stderr, "Unexpected SMB2 command\n");
|
|
return -1;
|
|
}
|
|
|
|
// the security buffer is asn.1. This checks for spnego/ntlmssp.
|
|
// probably not necessary... what else are we going to do?
|
|
|
|
|
|
has_spnego = false;
|
|
has_mech_ntlmssp = false;
|
|
|
|
tmp = responsePtr->body.negotiate.security_buffer_length;
|
|
if (tmp)
|
|
{
|
|
|
|
scan_asn1((const char *)responsePtr + responsePtr->body.negotiate.security_buffer_offset,
|
|
0, tmp);
|
|
|
|
if (flags._v)
|
|
{
|
|
fprintf(stdout, "has_spnego: %d\n", has_spnego);
|
|
fprintf(stdout, "has_mech_ntlmssp: %d\n", has_mech_ntlmssp);
|
|
}
|
|
|
|
}
|
|
|
|
DisposeHandle(h);
|
|
|
|
|
|
//
|
|
|
|
//
|
|
header.command = SMB2_SESSION_SETUP;
|
|
|
|
setup_req.structure_size = 25;
|
|
setup_req.flags = 0;
|
|
setup_req.security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
|
|
setup_req.capabilities = 0;
|
|
setup_req.security_buffer_length = 0;
|
|
setup_req.security_buffer_offset = sizeof(smb2_header_sync) + sizeof(smb2_session_setup_request);
|
|
|
|
write_message(ipid, &setup_req, sizeof(setup_req), NULL, 0);
|
|
|
|
|
|
h = read_response(ipid);
|
|
if (!h) return -1;
|
|
HLock(h);
|
|
|
|
responsePtr = *(smb_response **)h;
|
|
|
|
dump_response(responsePtr);
|
|
|
|
if (responsePtr->header.protocol_id != SMB2_MAGIC || responsePtr->header.structure_size != 64)
|
|
{
|
|
DisposeHandle(h);
|
|
fprintf(stderr, "Invalid SMB2 header\n");
|
|
return -1;
|
|
}
|
|
|
|
if (responsePtr->header.command != SMB2_SESSION_SETUP)
|
|
{
|
|
DisposeHandle(h);
|
|
fprintf(stderr, "Unexpected SMB2 command\n");
|
|
return -1;
|
|
}
|
|
|
|
header.session_id[0] = responsePtr->header.session_id[0];
|
|
header.session_id[1] = responsePtr->header.session_id[1];
|
|
|
|
DisposeHandle(h);
|
|
|
|
|
|
// send a second session_setup if STATUS_MORE_PROCESSING_REQUIRED
|
|
|
|
|
|
header.command = SMB2_TREE_CONNECT;
|
|
|
|
tree_req.structure_size = 9;
|
|
tree_req.path_offset = sizeof(smb2_header_sync) + sizeof(smb2_tree_connect_request);
|
|
tree_req.path_length = path[0] * 2;
|
|
|
|
write_message(ipid, &tree_req, sizeof(tree_req), path + 1, path[0] * 2);
|
|
|
|
|
|
h = read_response(ipid);
|
|
if (!h) return -1;
|
|
HLock(h);
|
|
|
|
responsePtr = *(smb_response **)h;
|
|
|
|
dump_response(responsePtr);
|
|
|
|
if (responsePtr->header.protocol_id != SMB2_MAGIC || responsePtr->header.structure_size != 64)
|
|
{
|
|
DisposeHandle(h);
|
|
fprintf(stderr, "Invalid SMB2 header\n");
|
|
return -1;
|
|
}
|
|
|
|
if (responsePtr->header.command != SMB2_TREE_CONNECT)
|
|
{
|
|
DisposeHandle(h);
|
|
fprintf(stderr, "Unexpected SMB2 command\n");
|
|
return -1;
|
|
}
|
|
DisposeHandle(h);
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int do_smb(char *url, URLComponents *components)
|
|
{
|
|
static Connection connection;
|
|
LongWord qtick;
|
|
|
|
char *host;
|
|
uint16_t *path;
|
|
Word err;
|
|
Word terr;
|
|
Word ok;
|
|
FILE *file;
|
|
|
|
if (!components->portNumber) components->portNumber = 445;
|
|
|
|
host = URLComponentGetCMalloc(url, components, URLComponentHost);
|
|
|
|
if (!host)
|
|
{
|
|
fprintf(stderr, "URL `%s': no host.", url);
|
|
return -1;
|
|
}
|
|
|
|
ok = ConnectLoop(host, components->portNumber, &connection);
|
|
|
|
if (!ok)
|
|
{
|
|
free(host);
|
|
if (file != stdout) fclose(file);
|
|
return -1;
|
|
}
|
|
|
|
|
|
path = cstring_to_unicode("\\\\192.168.1.254\\public");
|
|
ok = negotiate(connection.ipid, path);
|
|
if (ok) return ok;
|
|
|
|
|
|
|
|
CloseLoop(&connection);
|
|
|
|
free(host);
|
|
return 0;
|
|
}
|