@@ -18,8 +18,18 @@ def initialize(info = {})
18
18
deregister_options ( 'SSL' , 'SSLCert' )
19
19
register_options (
20
20
[
21
- OptPort . new ( 'SRVPORT' , [ true , " The local port to listen on." , 445 ] )
21
+ OptPort . new ( 'SRVPORT' , [ true , ' The local port to listen on.' , 445 ] )
22
22
] , self . class )
23
+
24
+ register_advanced_options (
25
+ [
26
+ OptInt . new ( 'SMBServerMaximumBuffer' , [ true , 'The maximum number of data in megabytes to buffer' , 2 ] ) ,
27
+ OptInt . new ( 'SMBServerIdleTimeout' , [ true , 'The maximum amount of time to keep an idle session open in seconds' , 120 ] )
28
+ ] , self . class )
29
+
30
+ @smb_server_last_pool_sweep = Time . now . to_f
31
+ @smb_server_pool_mutex = Mutex . new
32
+ @smb_server_request_counter = 0
23
33
end
24
34
25
35
def setup
@@ -44,16 +54,58 @@ def on_client_close(client)
44
54
45
55
def smb_conn ( c )
46
56
@state [ c ] = { :name => "#{ c . peerhost } :#{ c . peerport } " , :ip => c . peerhost , :port => c . peerport }
57
+ smb_pool_update ( c )
47
58
end
48
59
49
60
def smb_stop ( c )
61
+ # Make sure the socket is closed
62
+ begin
63
+ c . close
64
+ # Handle any number of errors that a double-close or failed shutdown can trigger
65
+ rescue ::IOError , ::EOFError ,
66
+ ::Errno ::ECONNRESET , ::Errno ::ENOTCONN , ::Errno ::ECONNABORTED ,
67
+ ::Errno ::ETIMEDOUT , ::Errno ::ENETRESET , ::Errno ::ESHUTDOWN
68
+ end
69
+
70
+ # Delete the state table entry
50
71
@state . delete ( c )
51
72
end
52
73
53
74
def smb_recv ( c )
54
75
smb = @state [ c ]
55
76
smb [ :data ] ||= ''
56
- smb [ :data ] << c . get_once
77
+
78
+ buff = ''
79
+ begin
80
+ buff = c . get_once ( -1 , 0.25 )
81
+ # Handle any number of errors that a read can trigger depending on socket state
82
+ rescue ::IOError , ::EOFError ,
83
+ ::Errno ::ECONNRESET , ::Errno ::ENOTCONN , ::Errno ::ECONNABORTED ,
84
+ ::Errno ::ETIMEDOUT , ::Errno ::ENETRESET , ::Errno ::ESHUTDOWN
85
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to exception: #{ $!. class } #{ $!} " )
86
+ smb_stop ( c )
87
+ return
88
+ end
89
+
90
+ # The client said it had data, but lied, kill the session
91
+ unless buff and buff . length > 0
92
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to empty payload..." )
93
+ smb_stop ( c )
94
+ return
95
+ end
96
+
97
+ # Append the new data to the buffer
98
+ smb [ :data ] << buff
99
+
100
+ # Prevent a simplistic DoS if the buffer is too big
101
+ if smb [ :data ] . length > ( 1024 *1024 *datastore [ 'SMBServerMaximumBuffer' ] )
102
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to oversized buffer of #{ smb [ :data ] . length } bytes..." )
103
+ smb_stop ( c )
104
+ return
105
+ end
106
+
107
+ # Update the last-seen timestamp and purge old entries
108
+ smb_pool_update ( c )
57
109
58
110
while ( smb [ :data ] . length > 0 )
59
111
@@ -95,10 +147,11 @@ def smb_recv(c)
95
147
pkt = CONST ::SMB_BASE_PKT . make_struct
96
148
pkt . from_s ( buff )
97
149
98
- # Only response to requests, ignore server replies
150
+ # Only respond to requests, ignore server replies
99
151
if ( pkt [ 'Payload' ] [ 'SMB' ] . v [ 'Flags1' ] & 128 != 0 )
100
- print_status ( "Ignoring server response from #{ smb [ :name ] } " )
101
- next
152
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to missing client request flag" )
153
+ smb_stop ( c )
154
+ return
102
155
end
103
156
104
157
cmd = pkt [ 'Payload' ] [ 'SMB' ] . v [ 'Command' ]
@@ -149,6 +202,38 @@ def smb_error(cmd, c, errorclass, esn = false)
149
202
pkt [ 'Payload' ] [ 'SMB' ] . v [ 'ErrorClass' ] = errorclass
150
203
c . put ( pkt . to_s )
151
204
end
205
+
206
+ # Update the last-seen timestamp and purge old entries
207
+ def smb_pool_update ( c )
208
+
209
+ @state [ c ] [ :last_action ] = Time . now . to_f
210
+ @smb_server_request_counter += 1
211
+
212
+ unless @smb_server_request_counter % 100 == 0 ||
213
+ @smb_server_last_pool_sweep + datastore [ 'SMBServerIdleTimeout' ] . to_f < Time . now . to_f
214
+ return
215
+ end
216
+
217
+ # Synchronize pool sweeps in case we move to threaded services
218
+ @smb_server_pool_mutex . synchronize do
219
+ purge_list = [ ]
220
+
221
+ @smb_server_last_pool_sweep = Time . now . to_f
222
+
223
+ @state . keys . each do |sc |
224
+ if @state [ sc ] [ :last_action ] + datastore [ 'SMBServerIdleTimeout' ] . to_f < Time . now . to_f
225
+ purge_list << sc
226
+ end
227
+ end
228
+
229
+ # Purge any idle connections to rescue file descriptors
230
+ purge_list . each do |sc |
231
+ vprint_status ( "Dropping connection from #{ @state [ sc ] [ :name ] } due to idle timeout..." )
232
+ smb_stop ( sc )
233
+ end
234
+ end
235
+ end
236
+
152
237
end
153
238
end
154
239
0 commit comments