From 53d716efaad2f26ad7bbfd473ceda46a9a7658f0 Mon Sep 17 00:00:00 2001 From: Christopher Shepherd Date: Sun, 24 May 2015 22:38:55 -0400 Subject: [PATCH] SMB_Open_ANDX support - Open a file for reading... --- README.md | 2 +- src/SMBDEMO.S | 215 +++++++++++++++++++++++++++++++++++++++++++++++++- src/smbdemo | Bin 39134 -> 39700 bytes 3 files changed, 212 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 51dbaa5..938a699 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ CIFS / SMB2 navel gazing, in 65816 assembly. -5/24/2015 - Current status: Connects on port 445, completes Protocol Negotiation, successfully sends login (on Setup_ANDX message), obsolete LM (DES) style password. sends successful Tree_ANDX message, thus connecting to a remote share. +5/24/2015 - Current status: Connects on port 445, completes Protocol Negotiation, successfully sends login (on Setup_ANDX message), obsolete LM (DES) style password. Sends successful Tree_ANDX message, thus connecting to a remote share. Sends Open_ANDX message message, opening a file. Build 'CMD.S' with Merlin32 and the included Library directory. diff --git a/src/SMBDEMO.S b/src/SMBDEMO.S index 97c8d14..ce357c5 100644 --- a/src/SMBDEMO.S +++ b/src/SMBDEMO.S @@ -8,6 +8,7 @@ * Saturday, May 9, 2015 - Receive and interpret NEG_PROT reply and start login * Sunday, May 24, 2015 - Some bugfixes, Tool128 and Tool129 requirement for hashing and DES, LM password hashing support * Also introducing successful SMB_Tree_ANDX message. We connect to remote shares now. +* Also introducing SMB_Open_ANDX message. We open a file now. * * REFERENCES * smb.c / smb.h from libOGC @@ -689,7 +690,7 @@ noevent4 PushLong MySMBHandle jsr SMB_Setup_Poll pla ; get negotiation status bcc sendloop3 - + ; ; SMB_Tree_ANDX ; @@ -722,7 +723,39 @@ noevent5 PushLong MySMBHandle jsr SMB_TreeX_Poll pla bcc sendloop4 + +; +; SMB_OpenFile +; + +openf PushLong CTSWinPtr + PushWord #1350 + PushLong #CTSTextC + _SetIText + PushLong MySMBHandle + PushLong #SMB_target_file + jsr SMB_OpenFile + +sendloop5 PushWord #0000 + PushWord #$0006 + PushLong #EventRec + _EventAvail + pla + beq noevent6 + PushWord #0000 + PushLong #00000000 + _ModalDialog + pla + cmp #2 + bne noevent6 + jmp breakndie + +noevent6 PushLong MySMBHandle + jsr SMB_OpenFile_Poll + bcc sendloop5 + +breakndie jmp SMB_staging_brk ; @@ -946,6 +979,7 @@ CTSText9 str 'Status: Connected, CIFS negotiating' CTSTextA str 'Status: Connect Failed' CTSTextB str 'Status: Logging In' CTSTextC str 'Status: Mounting Share' +CTSTextD str 'Status: Opening \\testfile' CTSItem6 dw 1360 ; id dw 77,120,87,300 ; bounds @@ -1177,6 +1211,7 @@ SMB_lm_hash ds 21 ; LM Hash, actually 16 bytes but the extra zeroes make SMB_lm_response ds 24 ; LM Response SMB_target_tree asc '\\LIVINGROOM\GSFILES'00 ; remote tree to connect to SMB_target_svc asc '?????'00 ; service type (wildcard) +SMB_target_file asc '\\TESTFILE'00 ; file to download * SMB packet staging area * TODO these will probably be dynamically allocated too? @@ -1906,6 +1941,10 @@ tft_far cmp #0000 bne tf_trampoline ; they returned an error, kbye + lda SMB_input+SMB_offset_tid + ldy #SMB_sess_tid-SMB_sess_begin + sta [SMB_sessid],y ; save returned TID + * TODO save remote servicetype or filesystem type? treex_finished plx ; our return address @@ -1932,12 +1971,11 @@ treex_proceeding * Arguments: * SMB session handle (two words, on stack) * Long pointer to filename (two words, on stack) -* Access flags (one word, on stack) -* Creation flags (one word, on stack) * Things I return on stack: * A = SMB filehandle id * Carry flag set if error SMB_OpenFile plx ; return address + PullLong SMB_tmp1 ; filename PullLong SMB_sessid phx ; saved return address @@ -1946,10 +1984,179 @@ SMB_OpenFile plx ; return address PushWord #CIFS_flags2 ; flags2 jsr _InitSMBHeader ; make an SMB header with this information -* TODO + sep $30 + mx %11 + lda #15 + sta SMB_staging+SMB_header_size ; word count + + lda #$ff + sta SMB_staging+SMB_header_size+1 ; next AndX + + lda #$00 + sta SMB_staging+SMB_header_size+2 ; AndX Reserved + + rep $30 + mx %00 + + lda #$0000 + sta SMB_staging+SMB_header_size+3 ; AndX Offset + + lda #$0000 + sta SMB_staging+SMB_header_size+5 ; Flags + + lda #SMB_open_reading + sta SMB_staging+SMB_header_size+7 ; Access Mode + + lda #0006 + sta SMB_staging+SMB_header_size+9 ; ?? + + lda #0000 + sta SMB_staging+SMB_header_size+11 ; type of file + + lda #0000 + sta SMB_staging+SMB_header_size+13 ; file attributes + + lda #0000 + sta SMB_staging+SMB_header_size+15 ; file time (don't care, let server decide) + sta SMB_staging+SMB_header_size+17 + + lda #0000 + sta SMB_staging+SMB_header_size+19 ; creation flags + + lda #0000 + sta SMB_staging+SMB_header_size+21 ; creation flags2? + + lda #0000 + sta SMB_staging+SMB_header_size+23 ; allocation size + sta SMB_staging+SMB_header_size+25 ; allocation size + + lda #0000 + sta SMB_staging+SMB_header_size+27 ; reserved[0] must be zero + lda #0000 + sta SMB_staging+SMB_header_size+31 ; reserved[1] must be zero + + lda #0000 + sta SMB_staging+SMB_header_size+35 ; byte count + + lda #0004 + sta SMB_staging+SMB_header_size+37 ; BufferFormat (8-bit) + lda #0 + sta SMB_tmp5 ; initialize pointer + + ; Target File + PushLong #SMB_tmp1 ; source + pea #^SMB_staging ; destination + lda #SMB_staging+SMB_header_size+38 + clc + adc SMB_tmp5 + pha + jsr _strcpy + tya + clc + adc SMB_tmp5 + sta SMB_tmp5 + + sta SMB_staging+SMB_header_size+35 ; update byte count + + clc + adc #SMB_header_size+33 + pha ; 'length' parameter for _SMB_Send + dec + dec + dec + dec + xba + sta SMB_staging+SMB_offset_tcplength+1 ; save length for naked-TCP dgram + + jsr _SMB_Send ; send our reply! + clc rts +* SMB_OpenFile_Poll - Call me until I tell you to stop, to receive and complete SMB Tree_ANDX +* Arguments: +* SMB session handle (two words, on stack) +* Things I return on stack: +* Setup status (word) +* $0000 - Setup proceeding +* $0001 - Setup finished +* $0002 - Setup failed +* Carry flag set means you can stop calling me +SMB_OpenFile_Poll + plx ; our return address + PullLong SMB_sessid ; your smb sessid + phx + + _TCPIPPoll + + PushWord #0000 ; space for result + ldy #SMB_sess_ipid-SMB_sess_begin + lda [SMB_sessid],y + pha ; push Marinetti IPID for this SMB_sessid + PushLong #statbuf + _TCPIPStatusTCP ; see if marinetti has anything for us + pla + cmp #terrNOCONNECTION + beq of_trampoline + cmp #terrBADIPID + beq of_trampoline + lda statbuf+8 ; get recvq size, low word + cmp #0000 ; yeah i know. for clarity. + beq op_trampoline ; poll us again later, marinetti got nothing + + PushWord #0000 ; space for result + ldy #SMB_sess_ipid-SMB_sess_begin + lda [SMB_sessid],y + pha ; push Marinetti IPID for this SMB_sessid + PushWord #0000 ; bufftype: static pre-allocated buffer + PushLong #SMB_input ; where it's all goin + PushLong #SMB_max_net_read_size + PushLong #readbuf + _TCPIPReadTCP + + pla + cmp #terrNOCONNECTION + beq of_trampoline + cmp #terrBADIPID + beq of_trampoline + + jsr _SMB_Check ; do basic check to make sure we received SMB data + bcs op_trampoline ; if not, wait for them to send again i guess + + bra oft_far +of_trampoline jmp openx_failed +op_trampoline jmp openx_proceeding +oft_far + lda SMB_input+SMB_offset_cmd + cmp #SMB_open_ANDX + bne op_trampoline ; punt if not setup_ANDX reply + + lda SMB_input+SMB_offset_eclass + cmp #0000 + bne of_trampoline ; they returned an error, kbye + + lda SMB_input+SMB_header_size+5 ; saved returned sfid + +openx_finished plx ; our return address + PushWord #0001 ; finished! + phx + sec + rts + +openx_failed plx ; our return address + PushWord #0002 ; failure + phx + sec + rts + +openx_proceeding + plx ; our return address + PushWord #0000 ; in progress + phx + clc + rts + + * * SMB_CloseFile - Close an open file on the remote share * Arguments: diff --git a/src/smbdemo b/src/smbdemo index fb4d20fd1529a8f6e217e26f1bf757d453a136ec..ea2221eb936b580c21f428cf115030cabd7ff6d4 100644 GIT binary patch literal 39700 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 Zns8_}jnp?%pRtkprZrOEo;AC8@=xC<41NFr literal 39134 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