@@ -661,6 +661,16 @@ def initialize(info = {})
661
661
[
662
662
OptPort . new ( 'SRVPORT' , [ true , "The local port to listen on." , 445 ] )
663
663
] , self . class )
664
+
665
+ register_advanced_options (
666
+ [
667
+ OptInt . new ( 'SMBServerMaximumBuffer' , [ true , "The maximum number of data in megabytes to buffer" , 2 ] ) ,
668
+ OptInt . new ( 'SMBServerIdleTimeout' , [ true , "The maximum amount of time to keep an idle session open in seconds" , 120 ] ) ,
669
+ ] , self . class )
670
+
671
+ @smb_server_last_pool_sweep = Time . now . to_f
672
+ @smb_server_pool_mutex = Mutex . new
673
+ @smb_server_request_counter = 0
664
674
end
665
675
666
676
def setup
@@ -685,16 +695,59 @@ def on_client_close(client)
685
695
686
696
def smb_conn ( c )
687
697
@state [ c ] = { :name => "#{ c . peerhost } :#{ c . peerport } " , :ip => c . peerhost , :port => c . peerport }
698
+ smb_pool_update ( c )
688
699
end
689
700
690
701
def smb_stop ( c )
702
+
703
+ # Make sure the socket is closed
704
+ begin
705
+ c . close
706
+ # Handle any number of errors that a double-close or failed shutdown can trigger
707
+ rescue ::IOError , ::EOFError ,
708
+ ::Errno ::ECONNRESET , ::Errno ::ENOTCONN , ::Errno ::ECONNABORTED ,
709
+ ::Errno ::ETIMEDOUT , ::Errno ::ENETRESET , ::Errno ::ESHUTDOWN
710
+ end
711
+
712
+ # Delete the state table entry
691
713
@state . delete ( c )
692
714
end
693
715
694
716
def smb_recv ( c )
695
717
smb = @state [ c ]
696
718
smb [ :data ] ||= ''
697
- smb [ :data ] << c . get_once
719
+
720
+ buff = ''
721
+ begin
722
+ buff = c . get_once ( -1 , 0.25 )
723
+ # Handle any number of errors that a read can trigger depending on socket state
724
+ rescue ::IOError , ::EOFError ,
725
+ ::Errno ::ECONNRESET , ::Errno ::ENOTCONN , ::Errno ::ECONNABORTED ,
726
+ ::Errno ::ETIMEDOUT , ::Errno ::ENETRESET , ::Errno ::ESHUTDOWN
727
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to exception: #{ $!. class } #{ $!} " )
728
+ smb_stop ( c )
729
+ return
730
+ end
731
+
732
+ # The client said it had data, but lied, kill the session
733
+ unless buff and buff . length > 0
734
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to empty payload..." )
735
+ smb_stop ( c )
736
+ return
737
+ end
738
+
739
+ # Append the new data to the buffer
740
+ smb [ :data ] << buff
741
+
742
+ # Prevent a simplistic DoS if the buffer is too big
743
+ if smb [ :data ] . length > ( 1024 *1024 *datastore [ 'SMBServerMaximumBuffer' ] )
744
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to oversized buffer of #{ smb [ :data ] . length } bytes..." )
745
+ smb_stop ( c )
746
+ return
747
+ end
748
+
749
+ # Update the last-seen timestamp and purge old entries
750
+ smb_pool_update ( c )
698
751
699
752
while ( smb [ :data ] . length > 0 )
700
753
@@ -736,10 +789,11 @@ def smb_recv(c)
736
789
pkt = CONST ::SMB_BASE_PKT . make_struct
737
790
pkt . from_s ( buff )
738
791
739
- # Only response to requests, ignore server replies
792
+ # Only respond to requests, ignore server replies
740
793
if ( pkt [ 'Payload' ] [ 'SMB' ] . v [ 'Flags1' ] & 128 != 0 )
741
- print_status ( "Ignoring server response from #{ smb [ :name ] } " )
742
- next
794
+ vprint_status ( "Dropping connection from #{ smb [ :name ] } due to missing client request flag" )
795
+ smb_stop ( c )
796
+ return
743
797
end
744
798
745
799
cmd = pkt [ 'Payload' ] [ 'SMB' ] . v [ 'Command' ]
@@ -784,6 +838,37 @@ def smb_error(cmd, c, errorclass, esn = false)
784
838
c . put ( pkt . to_s )
785
839
end
786
840
841
+ # Update the last-seen timestamp and purge old entries
842
+ def smb_pool_update ( c )
843
+
844
+ @state [ c ] [ :last_action ] = Time . now . to_f
845
+ @smb_server_request_counter += 1
846
+
847
+ unless @smb_server_request_counter % 100 == 0 ||
848
+ @smb_server_last_pool_sweep + datastore [ 'SMBServerIdleTimeout' ] . to_f < Time . now . to_f
849
+ return
850
+ end
851
+
852
+ # Synchronize pool sweeps in case we move to threaded services
853
+ @smb_server_pool_mutex . synchronize do
854
+ purge_list = [ ]
855
+
856
+ @smb_server_last_pool_sweep = Time . now . to_f
857
+
858
+ @state . keys . each do |sc |
859
+ if @state [ sc ] [ :last_action ] + datastore [ 'SMBServerIdleTimeout' ] . to_f < Time . now . to_f
860
+ purge_list << sc
861
+ end
862
+ end
863
+
864
+ # Purge any idle connections to rescue file descriptors
865
+ purge_list . each do |sc |
866
+ vprint_status ( "Dropping connection from #{ @state [ sc ] [ :name ] } due to idle timeout..." )
867
+ smb_stop ( sc )
868
+ end
869
+ end
870
+ end
871
+
787
872
end
788
873
789
874
0 commit comments