1- module Thrift
2- class SaslClientTransport < BufferedTransport
3- attr_reader :challenge
1+ require 'gssapi'
42
3+ module Thrift
4+ class SaslClientTransport < FramedTransport
55 STATUS_BYTES = 1
66 PAYLOAD_LENGTH_BYTES = 4
7- AUTH_MECHANISM = 'PLAIN'
87 NEGOTIATION_STATUS = {
98 START : 0x01 ,
109 OK : 0x02 ,
@@ -15,76 +14,122 @@ class SaslClientTransport < BufferedTransport
1514
1615 def initialize ( transport , sasl_params = { } )
1716 super ( transport )
18- @challenge = nil
17+ @sasl_complete = nil
1918 @sasl_username = sasl_params . fetch ( :username , 'anonymous' )
2019 @sasl_password = sasl_params . fetch ( :password , 'anonymous' )
20+ @sasl_mechanism = sasl_params . fetch ( :mechanism , 'PLAIN' )
21+
22+ unless [ 'PLAIN' , 'GSSAPI' ] . include? @sasl_mechanism
23+ raise "Unknown SASL mechanism: #{ @sasl_mechanism } "
24+ end
25+
26+ if @sasl_mechanism == 'GSSAPI'
27+ @sasl_remote_principal = sasl_params [ :remote_principal ]
28+ @sasl_remote_host = sasl_params [ :remote_host ]
29+ @gsscli = GSSAPI ::Simple . new ( @sasl_remote_host , @sasl_remote_principal )
30+ end
2131 end
2232
2333 def read ( sz )
24- len , = @transport . read ( PAYLOAD_LENGTH_BYTES ) . unpack ( 'l>' ) if @rbuf . nil?
25- sz = len if len && sz > len
26- @index += sz
27- ret = @rbuf . slice ( @index - sz , sz ) || Bytes . empty_byte_buffer
28- if ret . length == 0
29- @rbuf = @transport . read ( len ) rescue Bytes . empty_byte_buffer
30- @index = sz
31- ret = @rbuf . slice ( 0 , sz ) || Bytes . empty_byte_buffer
32- end
33- ret
34+ handshake! unless @sasl_complete
35+ super ( sz )
3436 end
3537
3638 def read_byte
37- reset_buffer! if @index >= @rbuf . size
38- @index += 1
39- Bytes . get_string_byte ( @rbuf , @index - 1 )
39+ handshake! unless @sasl_complete
40+ super
4041 end
4142
42- def read_into_buffer ( buffer , size )
43- i = 0
44- while i < size
45- reset_buffer! if @index >= @rbuf . size
46- byte = Bytes . get_string_byte ( @rbuf , @index )
47- Bytes . set_string_byte ( buffer , i , byte )
48- @index += 1
49- i += 1
50- end
51- i
43+ def read_into_buffer ( buf , size )
44+ handshake! unless @sasl_complete
45+ super ( buf , size )
5246 end
5347
5448 def write ( buf )
55- initiate_hand_shake if @challenge . nil?
56- header = [ buf . length ] . pack ( 'l>' )
57- @wbuf << ( header + Bytes . force_binary_encoding ( buf ) )
49+ handshake! unless @sasl_complete
50+ super ( buf )
5851 end
5952
60- protected
53+ def flush
54+ handshake! unless @sasl_complete
55+ super
56+ end
6157
62- def initiate_hand_shake
63- header = [ NEGOTIATION_STATUS [ :START ] , AUTH_MECHANISM . length ] . pack ( 'cl>' )
64- @transport . write header + AUTH_MECHANISM
65- message = "[#{ AUTH_MECHANISM } ]\u0000 #{ @sasl_username } \u0000 #{ @sasl_password } "
66- header = [ NEGOTIATION_STATUS [ :OK ] , message . length ] . pack ( 'cl>' )
67- @transport . write header + message
68- status , len = @transport . read ( STATUS_BYTES + PAYLOAD_LENGTH_BYTES ) . unpack ( 'cl>' )
58+ private
59+
60+ def handshake!
61+ case @sasl_mechanism
62+ when 'PLAIN'
63+ handshake_plain!
64+ when 'GSSAPI'
65+ handshake_gssapi!
66+ end
67+ end
68+
69+ def handshake_plain!
70+ token = "[#{ @sasl_mechanism } ]\u0000 #{ @sasl_username } \u0000 #{ @sasl_password } "
71+ write_handshake_message ( NEGOTIATION_STATUS [ :START ] , @sasl_mechanism )
72+ write_handshake_message ( NEGOTIATION_STATUS [ :OK ] , token )
73+
74+ status , msg = read_handshake_message
6975 case status
70- when NEGOTIATION_STATUS [ :BAD ] , NEGOTIATION_STATUS [ :ERROR ]
71- raise @transport . to_io . read ( len )
7276 when NEGOTIATION_STATUS [ :COMPLETE ]
73- @challenge = @transport . to_io . read len
77+ @sasl_complete = true
7478 when NEGOTIATION_STATUS [ :OK ]
7579 raise "Failed to complete challenge exchange: only NONE supported currently"
7680 end
7781 end
7882
79- private
83+ def handshake_gssapi!
84+ token = @gsscli . init_context
85+ write_handshake_message ( NEGOTIATION_STATUS [ :START ] , @sasl_mechanism )
86+ write_handshake_message ( NEGOTIATION_STATUS [ :OK ] , token )
87+
88+ status , msg = read_handshake_message
89+ case status
90+ when NEGOTIATION_STATUS [ :COMPLETE ]
91+ raise "Unexpected COMPLETE from server"
92+ when NEGOTIATION_STATUS [ :OK ]
93+ unless @gsscli . init_context ( msg )
94+ raise "GSSAPI: challenge provided by server could not be verified"
95+ end
96+
97+ write_handshake_message ( NEGOTIATION_STATUS [ :OK ] , "" )
98+
99+ status , msg = read_handshake_message
100+ case status
101+ when NEGOTIATION_STATUS [ :COMPLETE ]
102+ raise "Unexpected COMPLETE from server"
103+ when NEGOTIATION_STATUS [ :OK ]
104+ unwrapped = @gsscli . unwrap_message ( msg )
105+ rewrapped = @gsscli . wrap_message ( unwrapped )
106+
107+ write_handshake_message ( NEGOTIATION_STATUS [ :COMPLETE ] , rewrapped )
80108
81- def reset_buffer!
82- len , = @transport . read ( PAYLOAD_LENGTH_BYTES ) . unpack ( 'l>' )
83- @rbuf = @transport . read ( len )
84- while @rbuf . size < len
85- @rbuf << @transport . read ( len - @rbuf . size )
109+ status , msg = read_handshake_message
110+ case status
111+ when NEGOTIATION_STATUS [ :COMPLETE ]
112+ @sasl_complete = true
113+ when NEGOTIATION_STATUS [ :OK ]
114+ raise "Failed to complete GSS challenge exchange"
115+ end
116+ end
86117 end
87- @index = 0
118+ end
119+
120+ def read_handshake_message
121+ status , len = @transport . read ( STATUS_BYTES + PAYLOAD_LENGTH_BYTES ) . unpack ( 'cl>' )
122+ body = @transport . to_io . read ( len )
123+ if [ NEGOTIATION_STATUS [ :BAD ] , NEGOTIATION_STATUS [ :ERROR ] ] . include? ( status )
124+ raise "Exception from server: #{ body } "
125+ end
126+
127+ [ status , body ]
128+ end
129+
130+ def write_handshake_message ( status , message )
131+ header = [ status , message . length ] . pack ( 'cl>' )
132+ @transport . write ( header + message )
88133 end
89134 end
90135
@@ -93,5 +138,4 @@ def get_transport(transport)
93138 return SaslClientTransport . new ( transport )
94139 end
95140 end
96-
97141end
0 commit comments