ruby_smb 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.travis.yml +4 -1
  4. data/README.md +35 -47
  5. data/examples/enum_registry_key.rb +28 -0
  6. data/examples/enum_registry_values.rb +30 -0
  7. data/examples/pipes.rb +2 -1
  8. data/examples/read_registry_key_value.rb +32 -0
  9. data/lib/ruby_smb/client/authentication.rb +43 -25
  10. data/lib/ruby_smb/client/echo.rb +20 -2
  11. data/lib/ruby_smb/client/negotiation.rb +27 -12
  12. data/lib/ruby_smb/client/tree_connect.rb +20 -14
  13. data/lib/ruby_smb/client/utils.rb +2 -2
  14. data/lib/ruby_smb/client/winreg.rb +46 -0
  15. data/lib/ruby_smb/client.rb +28 -4
  16. data/lib/ruby_smb/dcerpc/bind.rb +2 -2
  17. data/lib/ruby_smb/dcerpc/bind_ack.rb +2 -2
  18. data/lib/ruby_smb/dcerpc/error.rb +3 -0
  19. data/lib/ruby_smb/dcerpc/ndr.rb +95 -16
  20. data/lib/ruby_smb/dcerpc/pdu_header.rb +1 -1
  21. data/lib/ruby_smb/dcerpc/request.rb +28 -9
  22. data/lib/ruby_smb/dcerpc/rrp_unicode_string.rb +35 -0
  23. data/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all.rb +9 -0
  24. data/lib/ruby_smb/dcerpc/srvsvc.rb +10 -0
  25. data/lib/ruby_smb/dcerpc/winreg/close_key_request.rb +24 -0
  26. data/lib/ruby_smb/dcerpc/winreg/close_key_response.rb +27 -0
  27. data/lib/ruby_smb/dcerpc/winreg/enum_key_request.rb +45 -0
  28. data/lib/ruby_smb/dcerpc/winreg/enum_key_response.rb +42 -0
  29. data/lib/ruby_smb/dcerpc/winreg/enum_value_request.rb +39 -0
  30. data/lib/ruby_smb/dcerpc/winreg/enum_value_response.rb +36 -0
  31. data/lib/ruby_smb/dcerpc/winreg/open_key_request.rb +34 -0
  32. data/lib/ruby_smb/dcerpc/winreg/open_key_response.rb +25 -0
  33. data/lib/ruby_smb/dcerpc/winreg/open_root_key_request.rb +43 -0
  34. data/lib/ruby_smb/dcerpc/winreg/open_root_key_response.rb +35 -0
  35. data/lib/ruby_smb/dcerpc/winreg/query_info_key_request.rb +27 -0
  36. data/lib/ruby_smb/dcerpc/winreg/query_info_key_response.rb +40 -0
  37. data/lib/ruby_smb/dcerpc/winreg/query_value_request.rb +39 -0
  38. data/lib/ruby_smb/dcerpc/winreg/query_value_response.rb +57 -0
  39. data/lib/ruby_smb/dcerpc/winreg/regsam.rb +40 -0
  40. data/lib/ruby_smb/dcerpc/winreg.rb +340 -0
  41. data/lib/ruby_smb/dcerpc.rb +38 -0
  42. data/lib/ruby_smb/dispatcher/socket.rb +3 -1
  43. data/lib/ruby_smb/error.rb +40 -1
  44. data/lib/ruby_smb/generic_packet.rb +33 -4
  45. data/lib/ruby_smb/smb1/file.rb +67 -11
  46. data/lib/ruby_smb/smb1/packet/close_request.rb +2 -5
  47. data/lib/ruby_smb/smb1/packet/close_response.rb +2 -1
  48. data/lib/ruby_smb/smb1/packet/echo_request.rb +2 -4
  49. data/lib/ruby_smb/smb1/packet/echo_response.rb +2 -1
  50. data/lib/ruby_smb/smb1/packet/empty_packet.rb +10 -1
  51. data/lib/ruby_smb/smb1/packet/logoff_request.rb +2 -4
  52. data/lib/ruby_smb/smb1/packet/logoff_response.rb +2 -1
  53. data/lib/ruby_smb/smb1/packet/negotiate_request.rb +2 -5
  54. data/lib/ruby_smb/smb1/packet/negotiate_response.rb +3 -7
  55. data/lib/ruby_smb/smb1/packet/negotiate_response_extended.rb +4 -4
  56. data/lib/ruby_smb/smb1/packet/nt_create_andx_request.rb +2 -4
  57. data/lib/ruby_smb/smb1/packet/nt_create_andx_response.rb +2 -1
  58. data/lib/ruby_smb/smb1/packet/nt_trans/create_request.rb +2 -1
  59. data/lib/ruby_smb/smb1/packet/nt_trans/create_response.rb +2 -1
  60. data/lib/ruby_smb/smb1/packet/nt_trans/request.rb +2 -4
  61. data/lib/ruby_smb/smb1/packet/nt_trans/response.rb +2 -1
  62. data/lib/ruby_smb/smb1/packet/read_andx_request.rb +2 -5
  63. data/lib/ruby_smb/smb1/packet/read_andx_response.rb +2 -1
  64. data/lib/ruby_smb/smb1/packet/session_setup_legacy_request.rb +2 -1
  65. data/lib/ruby_smb/smb1/packet/session_setup_legacy_response.rb +2 -1
  66. data/lib/ruby_smb/smb1/packet/session_setup_request.rb +2 -5
  67. data/lib/ruby_smb/smb1/packet/session_setup_response.rb +2 -1
  68. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_request.rb +0 -1
  69. data/lib/ruby_smb/smb1/packet/trans/peek_nmpipe_response.rb +3 -2
  70. data/lib/ruby_smb/smb1/packet/trans/request.rb +2 -5
  71. data/lib/ruby_smb/smb1/packet/trans/response.rb +2 -1
  72. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_request.rb +1 -1
  73. data/lib/ruby_smb/smb1/packet/trans/transact_nmpipe_response.rb +1 -1
  74. data/lib/ruby_smb/smb1/packet/trans2/find_first2_request.rb +2 -1
  75. data/lib/ruby_smb/smb1/packet/trans2/find_first2_response.rb +8 -2
  76. data/lib/ruby_smb/smb1/packet/trans2/find_next2_request.rb +2 -1
  77. data/lib/ruby_smb/smb1/packet/trans2/find_next2_response.rb +8 -2
  78. data/lib/ruby_smb/smb1/packet/trans2/open2_request.rb +2 -1
  79. data/lib/ruby_smb/smb1/packet/trans2/open2_response.rb +2 -1
  80. data/lib/ruby_smb/smb1/packet/trans2/request.rb +2 -4
  81. data/lib/ruby_smb/smb1/packet/trans2/request_secondary.rb +2 -4
  82. data/lib/ruby_smb/smb1/packet/trans2/response.rb +2 -1
  83. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_request.rb +2 -1
  84. data/lib/ruby_smb/smb1/packet/trans2/set_file_information_response.rb +2 -1
  85. data/lib/ruby_smb/smb1/packet/tree_connect_request.rb +2 -4
  86. data/lib/ruby_smb/smb1/packet/tree_connect_response.rb +13 -3
  87. data/lib/ruby_smb/smb1/packet/tree_disconnect_request.rb +2 -4
  88. data/lib/ruby_smb/smb1/packet/tree_disconnect_response.rb +2 -1
  89. data/lib/ruby_smb/smb1/packet/write_andx_request.rb +2 -5
  90. data/lib/ruby_smb/smb1/packet/write_andx_response.rb +2 -1
  91. data/lib/ruby_smb/smb1/pipe.rb +86 -5
  92. data/lib/ruby_smb/smb1/tree.rb +40 -2
  93. data/lib/ruby_smb/smb2/file.rb +97 -1
  94. data/lib/ruby_smb/smb2/packet/close_request.rb +2 -4
  95. data/lib/ruby_smb/smb2/packet/close_response.rb +2 -1
  96. data/lib/ruby_smb/smb2/packet/create_request.rb +2 -4
  97. data/lib/ruby_smb/smb2/packet/create_response.rb +2 -1
  98. data/lib/ruby_smb/smb2/packet/echo_request.rb +2 -4
  99. data/lib/ruby_smb/smb2/packet/echo_response.rb +2 -1
  100. data/lib/ruby_smb/smb2/packet/error_packet.rb +15 -3
  101. data/lib/ruby_smb/smb2/packet/ioctl_request.rb +2 -5
  102. data/lib/ruby_smb/smb2/packet/ioctl_response.rb +2 -1
  103. data/lib/ruby_smb/smb2/packet/logoff_request.rb +2 -4
  104. data/lib/ruby_smb/smb2/packet/logoff_response.rb +2 -1
  105. data/lib/ruby_smb/smb2/packet/negotiate_request.rb +2 -5
  106. data/lib/ruby_smb/smb2/packet/negotiate_response.rb +2 -1
  107. data/lib/ruby_smb/smb2/packet/query_directory_request.rb +2 -4
  108. data/lib/ruby_smb/smb2/packet/query_directory_response.rb +8 -2
  109. data/lib/ruby_smb/smb2/packet/read_request.rb +2 -4
  110. data/lib/ruby_smb/smb2/packet/read_response.rb +2 -1
  111. data/lib/ruby_smb/smb2/packet/session_setup_request.rb +2 -5
  112. data/lib/ruby_smb/smb2/packet/session_setup_response.rb +2 -1
  113. data/lib/ruby_smb/smb2/packet/set_info_request.rb +2 -4
  114. data/lib/ruby_smb/smb2/packet/set_info_response.rb +2 -1
  115. data/lib/ruby_smb/smb2/packet/tree_connect_request.rb +2 -5
  116. data/lib/ruby_smb/smb2/packet/tree_connect_response.rb +8 -2
  117. data/lib/ruby_smb/smb2/packet/tree_disconnect_request.rb +2 -4
  118. data/lib/ruby_smb/smb2/packet/tree_disconnect_response.rb +2 -1
  119. data/lib/ruby_smb/smb2/packet/write_request.rb +2 -4
  120. data/lib/ruby_smb/smb2/packet/write_response.rb +2 -1
  121. data/lib/ruby_smb/smb2/pipe.rb +98 -11
  122. data/lib/ruby_smb/smb2/tree.rb +44 -6
  123. data/lib/ruby_smb/version.rb +1 -1
  124. data/lib/ruby_smb.rb +0 -1
  125. data/ruby_smb.gemspec +3 -3
  126. data/spec/lib/ruby_smb/client_spec.rb +271 -11
  127. data/spec/lib/ruby_smb/dcerpc/bind_ack_spec.rb +2 -2
  128. data/spec/lib/ruby_smb/dcerpc/bind_spec.rb +2 -2
  129. data/spec/lib/ruby_smb/dcerpc/ndr_spec.rb +410 -0
  130. data/spec/lib/ruby_smb/dcerpc/request_spec.rb +50 -7
  131. data/spec/lib/ruby_smb/dcerpc/rrp_unicode_string_spec.rb +98 -0
  132. data/spec/lib/ruby_smb/dcerpc/srvsvc/net_share_enum_all_spec.rb +13 -0
  133. data/spec/lib/ruby_smb/dcerpc/srvsvc_spec.rb +60 -0
  134. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_request_spec.rb +28 -0
  135. data/spec/lib/ruby_smb/dcerpc/winreg/close_key_response_spec.rb +36 -0
  136. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_request_spec.rb +108 -0
  137. data/spec/lib/ruby_smb/dcerpc/winreg/enum_key_response_spec.rb +97 -0
  138. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_request_spec.rb +94 -0
  139. data/spec/lib/ruby_smb/dcerpc/winreg/enum_value_response_spec.rb +82 -0
  140. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_request_spec.rb +74 -0
  141. data/spec/lib/ruby_smb/dcerpc/winreg/open_key_response_spec.rb +35 -0
  142. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_request_spec.rb +90 -0
  143. data/spec/lib/ruby_smb/dcerpc/winreg/open_root_key_response_spec.rb +38 -0
  144. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_request_spec.rb +39 -0
  145. data/spec/lib/ruby_smb/dcerpc/winreg/query_info_key_response_spec.rb +113 -0
  146. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_request_spec.rb +88 -0
  147. data/spec/lib/ruby_smb/dcerpc/winreg/query_value_response_spec.rb +150 -0
  148. data/spec/lib/ruby_smb/dcerpc/winreg/regsam_spec.rb +32 -0
  149. data/spec/lib/ruby_smb/dcerpc/winreg_spec.rb +710 -0
  150. data/spec/lib/ruby_smb/dcerpc_spec.rb +81 -0
  151. data/spec/lib/ruby_smb/dispatcher/socket_spec.rb +3 -1
  152. data/spec/lib/ruby_smb/generic_packet_spec.rb +52 -4
  153. data/spec/lib/ruby_smb/smb1/file_spec.rb +191 -2
  154. data/spec/lib/ruby_smb/smb1/packet/empty_packet_spec.rb +68 -0
  155. data/spec/lib/ruby_smb/smb1/packet/trans2/find_first2_response_spec.rb +11 -2
  156. data/spec/lib/ruby_smb/smb1/packet/trans2/find_next2_response_spec.rb +11 -2
  157. data/spec/lib/ruby_smb/smb1/packet/tree_connect_response_spec.rb +40 -0
  158. data/spec/lib/ruby_smb/smb1/pipe_spec.rb +272 -149
  159. data/spec/lib/ruby_smb/smb1/tree_spec.rb +44 -7
  160. data/spec/lib/ruby_smb/smb2/file_spec.rb +295 -2
  161. data/spec/lib/ruby_smb/smb2/packet/error_packet_spec.rb +78 -0
  162. data/spec/lib/ruby_smb/smb2/packet/query_directory_response_spec.rb +8 -0
  163. data/spec/lib/ruby_smb/smb2/packet/tree_connect_response_spec.rb +8 -0
  164. data/spec/lib/ruby_smb/smb2/pipe_spec.rb +322 -145
  165. data/spec/lib/ruby_smb/smb2/tree_spec.rb +214 -0
  166. data.tar.gz.sig +0 -0
  167. metadata +70 -11
  168. metadata.gz.sig +0 -0
  169. data/lib/ruby_smb/smb1/dcerpc.rb +0 -67
  170. data/lib/ruby_smb/smb2/dcerpc.rb +0 -70
  171. data/spec/lib/ruby_smb/smb1/packet/error_packet_spec.rb +0 -37
@@ -1,15 +1,12 @@
1
1
  RSpec.describe RubySMB::SMB1::Pipe do
2
2
 
3
+ it { expect(described_class).to be < RubySMB::SMB1::File }
4
+
3
5
  let(:peek_nmpipe_response) {
4
6
  packet = RubySMB::SMB1::Packet::Trans::PeekNmpipeResponse.new
5
7
  packet.data_block.trans_parameters.read("\x10\x20\x00\x00\x03\x00")
6
8
  packet
7
9
  }
8
-
9
- describe RubySMB::SMB1::Pipe do
10
- it { expect(described_class).to be < RubySMB::SMB1::File }
11
- end
12
-
13
10
  let(:dispatcher) { RubySMB::Dispatcher::Socket.new(double('socket')) }
14
11
  let(:client) { RubySMB::Client.new(dispatcher, username: 'msfadmin', password: 'msfadmin') }
15
12
  let(:connect_response) {
@@ -37,6 +34,68 @@ RSpec.describe RubySMB::SMB1::Pipe do
37
34
  described_class.new(tree: tree, response: nt_create_andx_response, name: filename)
38
35
  }
39
36
 
37
+ describe '#peek' do
38
+ let(:request) { RubySMB::SMB1::Packet::Trans::PeekNmpipeRequest.new }
39
+ let(:raw_response) { double('Raw response') }
40
+ let(:response) { double('Response') }
41
+
42
+ before :example do
43
+ allow(RubySMB::SMB1::Packet::Trans::PeekNmpipeRequest).to receive(:new).and_return(request)
44
+ allow(client).to receive(:send_recv).and_return(raw_response)
45
+ allow(RubySMB::SMB1::Packet::Trans::PeekNmpipeResponse).to receive(:read).and_return(response)
46
+ allow(response).to receive(:valid?).and_return(true)
47
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
48
+ end
49
+
50
+ it 'creates a PeekNmpipeRequest'do
51
+ expect(RubySMB::SMB1::Packet::Trans::PeekNmpipeRequest).to receive(:new)
52
+ pipe.peek
53
+ end
54
+
55
+ it 'sets the request #fid field' do
56
+ expect(request).to receive(:fid=).with(pipe.fid)
57
+ pipe.peek
58
+ end
59
+
60
+ it 'sets the request #max_data_count fieldto the peek_size argument' do
61
+ peek_size = 5
62
+ pipe.peek(peek_size: peek_size)
63
+ expect(request.parameter_block.max_data_count).to eq(peek_size)
64
+ end
65
+
66
+ it 'calls Tree #set_header_fields' do
67
+ expect(tree).to receive(:set_header_fields).with(request)
68
+ pipe.peek
69
+ end
70
+
71
+ it 'calls Client #send_recv' do
72
+ expect(client).to receive(:send_recv).with(request)
73
+ pipe.peek
74
+ end
75
+
76
+ it 'parses the response as a SMB1 PeekNmpipeResponse packet' do
77
+ expect(RubySMB::SMB1::Packet::Trans::PeekNmpipeResponse).to receive(:read).with(raw_response)
78
+ pipe.peek
79
+ end
80
+
81
+ it 'raises an InvalidPacket exception if the response is not valid' do
82
+ allow(response).to receive(:valid?).and_return(false)
83
+ smb_header = double('SMB Header')
84
+ allow(response).to receive(:smb_header).and_return(smb_header)
85
+ allow(smb_header).to receive_messages(:protocol => nil, :command => nil)
86
+ expect { pipe.peek }.to raise_error(RubySMB::Error::InvalidPacket)
87
+ end
88
+
89
+ it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
90
+ allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
91
+ expect { pipe.peek }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
92
+ end
93
+
94
+ it 'returns the expected response' do
95
+ expect(pipe.peek).to eq(response)
96
+ end
97
+ end
98
+
40
99
  describe '#peek_available' do
41
100
  it 'reads the correct number of bytes available' do
42
101
  allow(pipe).to receive(:peek) { peek_nmpipe_response }
@@ -62,202 +121,266 @@ RSpec.describe RubySMB::SMB1::Pipe do
62
121
  end
63
122
  end
64
123
 
65
- context 'with DCERPC' do
66
- describe '#net_share_enum_all' do
67
- let(:host) { '1.2.3.4' }
68
- let(:dcerpc_response) { RubySMB::Dcerpc::Response.new }
69
-
70
- before :example do
71
- allow(pipe).to receive(:bind)
72
- allow(pipe).to receive(:request).and_return(dcerpc_response)
73
- allow(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).and_return([])
124
+ describe '#initialize' do
125
+ context 'when name is not provided' do
126
+ it 'raises an ArgumentError' do
127
+ expect {
128
+ described_class.new(tree: tree, response: nt_create_andx_response, name: nil)
129
+ }.to raise_error(ArgumentError)
74
130
  end
131
+ end
75
132
 
76
- it 'calls #bind with the expected arguments' do
77
- expect(pipe).to receive(:bind).with(endpoint: RubySMB::Dcerpc::Srvsvc)
78
- pipe.net_share_enum_all(host)
79
- end
133
+ it 'calls the superclass with the expected arguments' do
134
+ expect(pipe.tree).to eq(tree)
135
+ expect(pipe.name).to eq(filename)
136
+ expect(pipe.attributes).to eq(nt_create_andx_response.parameter_block.ext_file_attributes)
137
+ expect(pipe.fid).to eq(nt_create_andx_response.parameter_block.fid)
138
+ expect(pipe.last_access).to eq(nt_create_andx_response.parameter_block.last_access_time.to_datetime)
139
+ expect(pipe.last_change).to eq(nt_create_andx_response.parameter_block.last_change_time.to_datetime)
140
+ expect(pipe.last_write).to eq(nt_create_andx_response.parameter_block.last_write_time.to_datetime)
141
+ expect(pipe.size).to eq(nt_create_andx_response.parameter_block.end_of_file)
142
+ expect(pipe.size_on_disk).to eq(nt_create_andx_response.parameter_block.allocation_size)
143
+ end
80
144
 
81
- it 'calls #request with the expected arguments' do
82
- expect(pipe).to receive(:request).with(RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL, host: host)
83
- pipe.net_share_enum_all(host)
145
+ context 'with \'srvsvc\' filename' do
146
+ it 'extends Srvsvc class' do
147
+ pipe = described_class.new(tree: tree, response: nt_create_andx_response, name: 'srvsvc')
148
+ expect(pipe.respond_to?(:net_share_enum_all)).to be true
84
149
  end
150
+ end
85
151
 
86
- it 'parse the response with NetShareEnumAll #parse_response method' do
87
- stub = 'ABCD'
88
- dcerpc_response.alloc_hint = stub.size
89
- dcerpc_response.stub = stub
90
- expect(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).with(stub)
91
- pipe.net_share_enum_all(host)
152
+ context 'with \'winreg\' filename' do
153
+ it 'extends Winreg class' do
154
+ pipe = described_class.new(tree: tree, response: nt_create_andx_response, name: 'winreg')
155
+ expect(pipe.respond_to?(:has_registry_key?)).to be true
92
156
  end
157
+ end
158
+ end
93
159
 
94
- it 'returns the remote shares' do
95
- shares = [
96
- ["C$", "DISK", "Default share"],
97
- ["Shared", "DISK", ""],
98
- ["IPC$", "IPC", "Remote IPC"],
99
- ["ADMIN$", "DISK", "Remote Admin"]
100
- ]
101
- output = [
102
- {:name=>"C$", :type=>"DISK", :comment=>"Default share"},
103
- {:name=>"Shared", :type=>"DISK", :comment=>""},
104
- {:name=>"IPC$", :type=>"IPC", :comment=>"Remote IPC"},
105
- {:name=>"ADMIN$", :type=>"DISK", :comment=>"Remote Admin"},
106
- ]
107
- allow(RubySMB::Dcerpc::Srvsvc::NetShareEnumAll).to receive(:parse_response).and_return(shares)
108
- expect(pipe.net_share_enum_all(host)).to eq(output)
109
- end
160
+ describe '#dcerpc_request' do
161
+ let(:options) { { host: '1.2.3.4' } }
162
+ let(:stub_packet ) { RubySMB::Dcerpc::Winreg::OpenKeyRequest.new }
163
+ let(:dcerpc_request) { double('DCERPC Request') }
164
+ let(:request_stub) { double('Request stub') }
165
+ let(:binary_dcerpc_request) { double('Binary DCERPC Request') }
166
+ let(:trans_nmpipe_request) { double('TransactNmpipeRequest') }
167
+ let(:trans_data) { double('Trans data') }
168
+ let(:trans_nmpipe_raw_response) { double('Trans nmpipe raw response') }
169
+ let(:trans_nmpipe_response) { double('TransactNmpipeResponse') }
170
+ let(:raw_data) { double('Raw data') }
171
+ let(:dcerpc_response) { double('DCERPC Response') }
172
+ let(:result) { 'Result' }
173
+
174
+ before :example do
175
+ allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(dcerpc_request)
176
+ allow(dcerpc_request).to receive_messages(
177
+ :stub => request_stub,
178
+ :to_binary_s => binary_dcerpc_request
179
+ )
180
+ allow(request_stub).to receive(:read)
181
+ allow(RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest).to receive(:new).and_return(trans_nmpipe_request)
182
+ allow(tree).to receive(:set_header_fields)
183
+ allow(trans_nmpipe_request).to receive_message_chain(:data_block, :trans_data => trans_data)
184
+ allow(trans_nmpipe_request).to receive(:set_fid)
185
+ allow(trans_data).to receive(:write_data=)
186
+ allow(client).to receive(:send_recv).and_return(trans_nmpipe_raw_response)
187
+ allow(RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse).to receive(:read).and_return(trans_nmpipe_response)
188
+ allow(trans_nmpipe_response).to receive_messages(
189
+ :valid? => true,
190
+ :status_code => WindowsError::NTStatus::STATUS_SUCCESS
191
+ )
192
+ allow(trans_nmpipe_response).to receive_message_chain(:data_block, :trans_data, :read_data, :to_binary_s => raw_data)
193
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(dcerpc_response)
194
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::RESPONSE)
195
+ allow(dcerpc_response).to receive(:stub).and_return(result)
110
196
  end
111
197
 
112
- describe '#bind' do
113
- let(:options) { { endpoint: RubySMB::Dcerpc::Srvsvc } }
114
- let(:bind_packet) { RubySMB::Dcerpc::Bind.new(options) }
115
- let(:bind_ack_packet) { RubySMB::Dcerpc::BindAck.new }
198
+ it 'creates a Request packet with the expected arguments' do
199
+ pipe.dcerpc_request(stub_packet, options)
200
+ expect(options).to eq( { host: '1.2.3.4', endpoint: 'Winreg' })
201
+ expect(RubySMB::Dcerpc::Request).to have_received(:new).with({ opnum: stub_packet.opnum }, options)
202
+ end
116
203
 
117
- before :example do
118
- allow(RubySMB::Dcerpc::Bind).to receive(:new).and_return(bind_packet)
119
- allow(pipe).to receive(:write)
120
- allow(pipe).to receive(:read)
121
- bind_ack_packet.p_result_list.n_results = 1
122
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::ACCEPTANCE
123
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(bind_ack_packet)
124
- end
204
+ it 'sets DCERPC request stub to the stub packet passed as argument' do
205
+ pipe.dcerpc_request(stub_packet, options)
206
+ expect(request_stub).to have_received(:read).with(stub_packet.to_binary_s)
207
+ end
125
208
 
126
- it 'creates a Bind packet' do
127
- expect(RubySMB::Dcerpc::Bind).to receive(:new).with(options).and_return(bind_packet)
128
- pipe.bind(options)
129
- end
209
+ it 'creates a Trans TransactNmpipeRequest packet' do
210
+ pipe.dcerpc_request(stub_packet, options)
211
+ expect(RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest).to have_received(:new).with(options)
212
+ end
130
213
 
131
- it 'writes to the named pipe' do
132
- expect(pipe).to receive(:write).with(data: bind_packet.to_binary_s)
133
- pipe.bind(options)
134
- end
214
+ it 'calls Tree #set_header_fields' do
215
+ pipe.dcerpc_request(stub_packet, options)
216
+ expect(tree).to have_received(:set_header_fields).with(trans_nmpipe_request)
217
+ end
135
218
 
136
- it 'reads the socket' do
137
- expect(pipe).to receive(:read)
138
- pipe.bind(options)
219
+ it 'calls TransactNmpipeRequest #set_fid' do
220
+ pipe.dcerpc_request(stub_packet, options)
221
+ expect(trans_nmpipe_request).to have_received(:set_fid).with(pipe.fid)
222
+ end
223
+
224
+ it 'sets the expected #write_data request property' do
225
+ pipe.dcerpc_request(stub_packet, options)
226
+ expect(trans_data).to have_received(:write_data=).with(binary_dcerpc_request)
227
+ end
228
+
229
+ it 'sends the expected request' do
230
+ pipe.dcerpc_request(stub_packet, options)
231
+ expect(client).to have_received(:send_recv).with(trans_nmpipe_request)
232
+ end
233
+
234
+ it 'creates a Trans TransactNmpipeResponse packet from the response' do
235
+ pipe.dcerpc_request(stub_packet, options)
236
+ expect(RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse).to have_received(:read).with(trans_nmpipe_raw_response)
237
+ end
238
+
239
+ context 'when the response is not a Trans packet' do
240
+ it 'raises an InvalidPacket exception' do
241
+ allow(trans_nmpipe_response).to receive_message_chain(:smb_header, :protocol)
242
+ allow(trans_nmpipe_response).to receive_message_chain(:smb_header, :command)
243
+ allow(trans_nmpipe_response).to receive(:valid?).and_return(false)
244
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Error::InvalidPacket)
139
245
  end
246
+ end
140
247
 
141
- it 'creates a BindAck packet from the response' do
142
- raw_response = RubySMB::Dcerpc::BindAck.new.to_binary_s
143
- allow(pipe).to receive(:read).and_return(raw_response)
144
- expect(RubySMB::Dcerpc::BindAck).to receive(:read).with(raw_response).and_return(bind_ack_packet)
145
- pipe.bind(options)
248
+ context 'when the response status code is not STATUS_SUCCESS or STATUS_BUFFER_OVERFLOW' do
249
+ it 'raises an UnexpectedStatusCode exception' do
250
+ allow(trans_nmpipe_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_INVALID_HANDLE)
251
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
146
252
  end
253
+ end
147
254
 
148
- it 'raises the expected exception when an invalid packet is received' do
149
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_raise(IOError)
150
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
255
+ context 'when the response status code is STATUS_SUCCESS' do
256
+ it 'does not raise any exception' do
257
+ expect { pipe.dcerpc_request(stub_packet, options) }.not_to raise_error
151
258
  end
152
259
 
153
- it 'raises the expected exception when it is not a BindAck packet' do
154
- response = RubySMB::Dcerpc::Bind.new
155
- allow(RubySMB::Dcerpc::BindAck).to receive(:read).and_return(response)
156
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
260
+ it 'creates a DCERPC Response packet from the response' do
261
+ pipe.dcerpc_request(stub_packet, options)
262
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
157
263
  end
158
264
 
159
- it 'raises an exception when no result is returned' do
160
- bind_ack_packet.p_result_list.n_results = 0
161
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
265
+ context 'when an IOError occurs while parsing the DCERPC response' do
266
+ it 'raises an InvalidPacket exception' do
267
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
268
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
269
+ end
162
270
  end
163
271
 
164
- it 'raises an exception when result is not ACCEPTANCE' do
165
- bind_ack_packet.p_result_list.p_results[0].result = RubySMB::Dcerpc::BindAck::USER_REJECTION
166
- expect { pipe.bind(options) }.to raise_error(RubySMB::Dcerpc::Error::BindError)
272
+ context 'when the response is not a DCERPC Response packet' do
273
+ it 'raises an InvalidPacket exception' do
274
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
275
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
276
+ end
167
277
  end
168
278
 
169
- it 'returns the expected BindAck packet' do
170
- expect(pipe.bind(options)).to eq(bind_ack_packet)
279
+ it 'returns the expected stub data' do
280
+ expect(pipe.dcerpc_request(stub_packet, options)).to eq(result)
171
281
  end
172
282
  end
173
283
 
174
- describe '#request' do
175
- let(:options) { { host: '1.2.3.4' } }
176
- let(:opnum) { RubySMB::Dcerpc::Srvsvc::NET_SHARE_ENUM_ALL }
177
- let(:req_packet) { RubySMB::Dcerpc::Request.new({ :opnum => opnum }, options) }
178
- let(:nmpipe_packet) { RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest.new(options) }
179
- let(:nmpipe_response) { RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse.new }
180
- let(:res_packet) { RubySMB::Dcerpc::Response.new }
181
-
284
+ context 'when the response status code is STATUS_BUFFER_OVERFLOW' do
285
+ let(:data_count) { 100 }
286
+ let(:added_raw_data) { double('Added raw data') }
182
287
  before :example do
183
- allow(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
184
- allow(RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest).to receive(:new).and_return(nmpipe_packet)
185
- allow(client).to receive(:send_recv)
186
- allow(RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse).to receive(:read).and_return(nmpipe_response)
187
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(res_packet)
288
+ allow(trans_nmpipe_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_BUFFER_OVERFLOW)
289
+ allow(trans_nmpipe_response).to receive_message_chain(:parameter_block, :data_count => data_count)
290
+ allow(pipe).to receive(:read).and_return(added_raw_data)
291
+ allow(raw_data).to receive(:<<)
292
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 1)
293
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
188
294
  end
189
295
 
190
- it 'creates a Request packet' do
191
- expect(RubySMB::Dcerpc::Request).to receive(:new).and_return(req_packet)
192
- pipe.request(opnum, options)
296
+ it 'does not raise any exception' do
297
+ expect { pipe.dcerpc_request(stub_packet, options) }.not_to raise_error
193
298
  end
194
299
 
195
- it 'creates a Trans TransactNmpipeRequest packet' do
196
- expect(RubySMB::SMB1::Packet::Trans::TransactNmpipeRequest).to receive(:new).and_return(nmpipe_packet)
197
- pipe.request(opnum, options)
300
+ it 'reads the expected number of bytes and concatenate it the first response raw data' do
301
+ pipe.dcerpc_request(stub_packet, options)
302
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size - data_count)
303
+ expect(raw_data).to have_received(:<<).with(added_raw_data)
198
304
  end
199
305
 
200
- it 'calls Tree #set_header_fields' do
201
- expect(tree).to receive(:set_header_fields).with(nmpipe_packet)
202
- pipe.request(opnum, options)
306
+ it 'creates a DCERPC Response packet from the updated raw data' do
307
+ pipe.dcerpc_request(stub_packet, options)
308
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data)
203
309
  end
204
310
 
205
- it 'calls TransactNmpipeRequest #set_fid' do
206
- expect(nmpipe_packet).to receive(:set_fid).with(pipe.fid)
207
- pipe.request(opnum, options)
311
+ context 'when an IOError occurs while parsing the DCERPC response' do
312
+ it 'raises an InvalidPacket exception' do
313
+ allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
314
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
315
+ end
208
316
  end
209
317
 
210
- it 'sets the expected data on the request' do
211
- expect(client).to receive(:send_recv) do
212
- expect(nmpipe_packet.data_block.trans_data.write_data).to eq(req_packet.to_binary_s)
318
+ context 'when the response is not a DCERPC Response packet' do
319
+ it 'raises an InvalidPacket exception' do
320
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
321
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
213
322
  end
214
- pipe.request(opnum, options)
215
323
  end
216
324
 
217
- it 'sends the expected request' do
218
- expect(client).to receive(:send_recv).with(nmpipe_packet)
219
- pipe.request(opnum, options)
325
+ context 'when the response is not the first fragment' do
326
+ it 'raises an InvalidPacket exception' do
327
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :first_frag => 0)
328
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
329
+ end
220
330
  end
221
331
 
222
- it 'creates a Trans TransactNmpipeResponse packet from the response' do
223
- raw_response = double('Raw response')
224
- allow(client).to receive(:send_recv).and_return(raw_response)
225
- expect(RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse).to receive(:read).with(raw_response).and_return(nmpipe_response)
226
- pipe.request(opnum, options)
227
- end
332
+ context 'when the response is the last fragment' do
333
+ it 'only reads the pipe once' do
334
+ pipe.dcerpc_request(stub_packet, options)
335
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).once
336
+ end
228
337
 
229
- it 'raises the expected exception when it is not a Trans packet' do
230
- response = RubySMB::SMB1::Packet::Trans2::Response.new
231
- allow(RubySMB::SMB1::Packet::Trans::TransactNmpipeResponse).to receive(:read).and_return(response)
232
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Error::InvalidPacket)
338
+ it 'returns the expected stub data' do
339
+ expect(pipe.dcerpc_request(stub_packet, options)).to eq(result)
340
+ end
233
341
  end
234
342
 
235
- it 'raises the expected exception when the status code is not STATUS_SUCCESS' do
236
- nmpipe_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
237
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
238
- end
343
+ context 'when the response is not the last fragment' do
344
+ let(:raw_data2) { double('Raw data #2') }
345
+ let(:dcerpc_response2) { double('DCERPC Response #2') }
346
+ let(:result2) { 'Result #2' }
347
+ before :example do
348
+ allow(dcerpc_response).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 0)
349
+ allow(pipe).to receive(:read).with(bytes: tree.client.max_buffer_size).and_return(raw_data2)
350
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_return(dcerpc_response2)
351
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::RESPONSE)
352
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :pfc_flags, :last_frag => 1)
353
+ allow(dcerpc_response2).to receive(:stub).and_return(result2)
354
+ end
239
355
 
240
- it 'creates a DCERPC Response packet from the response' do
241
- nmpipe_response.data_block.trans_data.read_data = "test"
242
- expect(RubySMB::Dcerpc::Response).to receive(:read).with(nmpipe_response.data_block.trans_data.read_data)
243
- pipe.request(opnum, options)
244
- end
356
+ it 'reads the expected number of bytes' do
357
+ pipe.dcerpc_request(stub_packet, options)
358
+ expect(pipe).to have_received(:read).with(bytes: tree.client.max_buffer_size)
359
+ end
245
360
 
246
- it 'raises the expected exception when an invalid packet is received' do
247
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_raise(IOError)
248
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
249
- end
361
+ it 'creates a DCERPC Response packet from the new raw data' do
362
+ pipe.dcerpc_request(stub_packet, options)
363
+ expect(RubySMB::Dcerpc::Response).to have_received(:read).with(raw_data2)
364
+ end
250
365
 
251
- it 'raises the expected exception when it is not a Response packet' do
252
- response = RubySMB::Dcerpc::Request.new
253
- allow(RubySMB::Dcerpc::Response).to receive(:read).and_return(response)
254
- expect { pipe.request(opnum, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
255
- end
366
+ context 'when an IOError occurs while parsing the new DCERPC response' do
367
+ it 'raises an InvalidPacket exception' do
368
+ allow(RubySMB::Dcerpc::Response).to receive(:read).with(raw_data2).and_raise(IOError)
369
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
370
+ end
371
+ end
256
372
 
257
- it 'returns the expected DCERPC Response' do
258
- expect(pipe.request(opnum, options)).to eq(res_packet)
373
+ context 'when the new response is not a DCERPC Response packet' do
374
+ it 'raises an InvalidPacket exception' do
375
+ allow(dcerpc_response2).to receive_message_chain(:pdu_header, :ptype => RubySMB::Dcerpc::PTypes::FAULT)
376
+ expect { pipe.dcerpc_request(stub_packet, options) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
377
+ end
378
+ end
379
+
380
+ it 'returns the expected stub data' do
381
+ expect(pipe.dcerpc_request(stub_packet, options)).to eq(result)
382
+ end
259
383
  end
260
384
  end
261
385
  end
262
-
263
386
  end
@@ -46,25 +46,34 @@ RSpec.describe RubySMB::SMB1::Tree do
46
46
  end
47
47
 
48
48
  describe '#disconnect!' do
49
- it 'calls #set_header_fields' do
49
+ let(:disco_response) { double('Response') }
50
+
51
+ before :example do
50
52
  allow(RubySMB::SMB1::Packet::TreeDisconnectRequest).to receive(:new).and_return(disco_req)
51
- allow(client).to receive(:send_recv).and_return(disco_resp.to_binary_s)
53
+ allow(RubySMB::SMB1::Packet::TreeDisconnectResponse).to receive(:read).and_return(disco_resp)
54
+ allow(client).to receive(:send_recv)
55
+ end
56
+
57
+ it 'calls #set_header_fields' do
52
58
  expect(tree).to receive(:set_header_fields).with(disco_req)
53
59
  tree.disconnect!
54
60
  end
55
61
 
56
62
  it 'sends a TreeDisconnectRequest with the Tree ID in the header' do
57
- allow(RubySMB::SMB1::Packet::TreeDisconnectRequest).to receive(:new).and_return(disco_req)
58
63
  modified_req = disco_req
59
64
  modified_req.smb_header.tid = tree.id
60
- expect(client).to receive(:send_recv).with(modified_req).and_return(disco_resp.to_binary_s)
65
+ expect(client).to receive(:send_recv).with(modified_req)
61
66
  tree.disconnect!
62
67
  end
63
68
 
64
69
  it 'returns the NTStatus code from the response' do
65
- allow(client).to receive(:send_recv).and_return(disco_resp.to_binary_s)
66
70
  expect(tree.disconnect!).to eq disco_resp.status_code
67
71
  end
72
+
73
+ it 'raises an InvalidPacket exception if the response is not valid' do
74
+ allow(disco_resp).to receive(:valid?).and_return(false)
75
+ expect { tree.disconnect! }.to raise_error(RubySMB::Error::InvalidPacket)
76
+ end
68
77
  end
69
78
 
70
79
  describe '#open_file' do
@@ -239,14 +248,14 @@ RSpec.describe RubySMB::SMB1::Tree do
239
248
  end
240
249
 
241
250
  context 'when the response is not a NtCreateAndxResponse packet' do
242
- it 'raise an InvalidPacket exception' do
251
+ it 'raises an InvalidPacket exception' do
243
252
  nt_create_andx_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
244
253
  expect { tree.open_file(filename: filename) }.to raise_error(RubySMB::Error::InvalidPacket)
245
254
  end
246
255
  end
247
256
 
248
257
  context 'when the response status code is not STATUS_SUCCESS' do
249
- it 'raise an UnexpectedStatusCode exception' do
258
+ it 'raises an UnexpectedStatusCode exception' do
250
259
  nt_create_andx_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
251
260
  expect { tree.open_file(filename: filename) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
252
261
  end
@@ -342,6 +351,20 @@ RSpec.describe RubySMB::SMB1::Tree do
342
351
  expect(tree.list).to eq([file_info1, file_info2])
343
352
  end
344
353
 
354
+ context 'when the response is not a valid Trans2 FindFirst2Response' do
355
+ it 'raises an InvalidPacket exception' do
356
+ find_first2_res.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
357
+ expect { tree.list }.to raise_error(RubySMB::Error::InvalidPacket)
358
+ end
359
+ end
360
+
361
+ context 'when the response status code is not STATUS_SUCCESS' do
362
+ it 'raises an UnexpectedStatusCode exception' do
363
+ find_first2_res.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
364
+ expect { tree.list }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
365
+ end
366
+ end
367
+
345
368
  context 'when more requests are needed to get all the information' do
346
369
  let(:find_next2_req) { RubySMB::SMB1::Packet::Trans2::FindNext2Request.new }
347
370
  let(:file_info2) do
@@ -414,6 +437,20 @@ RSpec.describe RubySMB::SMB1::Tree do
414
437
  it 'returns the expected FindFileFullDirectoryInfo structures' do
415
438
  expect(tree.list).to eq([file_info1, file_info2])
416
439
  end
440
+
441
+ context 'when the response is not a valid Trans2 FindNext2Response' do
442
+ it 'raises an InvalidPacket exception' do
443
+ find_next2_res.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
444
+ expect { tree.list }.to raise_error(RubySMB::Error::InvalidPacket)
445
+ end
446
+ end
447
+
448
+ context 'when the response status code is not STATUS_SUCCESS' do
449
+ it 'raises an UnexpectedStatusCode exception' do
450
+ find_next2_res.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
451
+ expect { tree.list }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
452
+ end
453
+ end
417
454
  end
418
455
  end
419
456