diff --git a/lib/nsca.rb b/lib/nsca.rb index 766c1a0..a60ffc6 100644 --- a/lib/nsca.rb +++ b/lib/nsca.rb @@ -18,6 +18,35 @@ module NSCA clname[0] = clname[0].upcase clname.to_sym end + + def xor_stream key + key = case key + when Array then key + when String then key.bytes.to_a + when Enumerable then key.to_a + end + return lambda{|x|x} if [nil, '', []].include? key + length = key.length + i = 0 + lambda do |str| + r = '' + str.bytes.each_with_index do |c, j| + r[j] = (c ^ key[i]).chr + i = (i + 1) % length + end + r + end + end + + def crc32_stream + sum = 0xFFFFFFFF + lambda do |str| + sum = str.bytes.inject sum do |r, b| + 8.times.inject( r^b) {|r,_i| (r>>1) ^ (0xEDB88320 * (r&1)) } + end if str + sum ^ 0xFFFFFFFF + end + end end end @@ -28,6 +57,27 @@ module NSCA NSCA.destinations.each {|server| server.send *results } self end + + def xor key, msg = nil, key_a = nil + NSCA::Helper.xor_stream( key_a || key)[ msg] + end + + def crc32 msg + NSCA::Helper.crc32_stream[ msg] + end + + # Builds a null terminated, null padded string of length maxlen + def str2cstr( str, maxlen = nil) + str = str.to_s + str = str.to_s[0..(maxlen-2)] if maxlen + "#{str}\x00" + end + def str2nstr( str, maxlen = nil) + str = str.to_s.gsub( ' ', "\x00") + "#{str} " + end + def cstr2str( str, maxlen = nil) str[ 0, str.index( ?\0) || ((maxlen||str.length+1)-1)] end + def nstr2str( str, maxlen = nil) str[ 0, str.index( ' ') || ((maxlen||str.length+1)-1)].gsub( "\x00", ' ') end end end diff --git a/lib/nsca/check.rb b/lib/nsca/check.rb index 23250de..f305785 100644 --- a/lib/nsca/check.rb +++ b/lib/nsca/check.rb @@ -163,6 +163,11 @@ module NSCA {timestamp: timestamp, return_code: retcode, hostname: hostname, server: service, status: text} end + def to_packet version = nil + version ||= PacketV3 + version.new timestamp, retcode, hostname, service, text + end + class <] results def send *results - results.flatten.each do |r| - send_packet r.timestamp, r.retcode, r.hostname, r.service, r.text - end + results.flatten.each &method(:send_packet) end # Closes connection to NSCA. @@ -85,7 +92,7 @@ module NSCA @hostname, @port, @password = hostname, port, password end - def open( &e) Connection.open @hostname, @port, @password, &e end + def open( &e) Connection.open hostname: @hostname, port: @port, password: @password, &e end def send( *results) open {|conn| conn.send results } end end end diff --git a/lib/nsca/packet.rb b/lib/nsca/packet.rb index 9dca72b..204863f 100644 --- a/lib/nsca/packet.rb +++ b/lib/nsca/packet.rb @@ -1,29 +1,4 @@ module NSCA - class <>1) ^ (0xEDB88320 * (r&1)) } - end) ^ 0xFFFFFFFF - end - - # Builds a null terminated, null padded string of length maxlen - def str2cstr( str, maxlen = nil) - str = str.to_s - str = str.to_s[0..(maxlen-2)] if maxlen - "#{str}\x00" - end - def cstr2str( str, maxlen = nil) str[ 0, x.index( ?\0) || ((maxlen||0)-1)] end - end - class Packet class CSC32CheckFailed ') + @xor_password = NSCA::Helper.xor_stream @password + @xor_iv_key = NSCA::Helper.xor_stream @iv_key end def fetch - data = read - @packet_version.parse data, @iv_key, @password if data + iv_key = NSCA::Helper.xor_stream @iv_key + password = NSCA::Helper.xor_stream @password + packet_version = iv_key[ password[ read PacketV3::PACKET_VERSION]] + v = packet_version.unpack( 's>').first + case v + when 3 + data = packet_version + iv_key[ password[ read( PacketV3::PACK_LENGTH - PacketV3::PACKET_VERSION)]] + begin + return PacketV3.parse( data) + rescue NSCA::Packet::CSC32CheckFailed + x = read( PacketV3__2_9::PACK_LENGTH - data.length) + raise if x.nil? + return PacketV3__2_9.parse( data + iv_key[ password[ x]]) + end + else raise "Unknown Version #{v.inspect}" + end end def each &block return Enumerator.new( self) unless block_given? - while data = fetch - yield data - end + yield fetch until eof? end def eof?() @socket.eof? end - def read() @socket.read @packet_length end + def read( len = nil) @socket.read( len || @packet_length) end def close() @socket.close end end end diff --git a/test/test_nsca.rb b/test/test_nsca.rb index 46d5f38..54690e4 100644 --- a/test/test_nsca.rb +++ b/test/test_nsca.rb @@ -18,20 +18,34 @@ end class TestNSCACommunication < Test::Unit::TestCase Port = 5787 + def dummy_server *args + server = Thread.new do + begin + NSCA.dummy_server *args + rescue Object + #STDERR.puts "#{$!.class}: #{$!}", $!.backtrace.map{|bt|" #{bt}"} + raise + ensure + #STDERR.puts "Dummy Server Shutdown" + end + end + sleep 1 # server needs time to start... + server + end include NSCA::Checks context "our dummy test server on localhost:#{Port} with random password" do should 'receive data' do - password = SecureRandom.random_bytes + password = 'password' || SecureRandom.random_bytes timestamp = Time.now PD1 = perfdata :pd1_in_sec, :s, 10, 20, 0, 30 PD2 = perfdata :pd2_in_1, 1, 0.99, 0.98, 0, 1 PD3 = perfdata :pd3_count, :c, 3, 5, 0 - T0 = check 'TestNSCA0', 'uxnags01-sbe.net.mobilkom.at' - T1 = check 'TestNSCA1', 'uxnags01-sbe.net.mobilkom.at', [PD1, PD2] - T2 = check :TestNSCA2, 'uxnags01-sbe.net.mobilkom.at', [PD1, PD2, PD3] + T0 = check 'TestNSCA0', 'localhost' + T1 = check 'TestNSCA1', 'localhost', [PD1, PD2] + T2 = check :TestNSCA2, 'localhost', [PD1, PD2, PD3] checks = [] t0 = T0.new( 1, "0123456789"*51+"AB", nil, timestamp) # oversized service name @@ -44,10 +58,9 @@ class TestNSCACommunication < Test::Unit::TestCase checks << t1 NSCA::destinations.clear - NSCA::destinations << NSCA::Client.new( 'localhost', Port, password: password) + NSCA::destinations << NSCA::Client.new( 'localhost', Port, password) - server = Thread.new { NSCA.dummy_server Port, password: password } - sleep 1 # server needs time to start... + server = dummy_server port: Port, password: password NSCA::send *checks pc0, pc1 = server.value @@ -57,20 +70,19 @@ class TestNSCACommunication < Test::Unit::TestCase assert_equal timestamp.to_i, packet.timestamp.to_i assert_equal test.retcode, packet.return_code end - # original with B, but B is char 512 and will be replaced by \0 + # original with AB, but B is char 512 and will be replaced by \0 assert_equal "0123456789"*51+"A", pc0.status assert_equal "Should be OK | 'pd1_in_sec'=3s,10,20,0,30 'pd2_in_1'=0.99961,0.99,0.98,0,1 'pd3_count'=2c,3,5,0,", pc1.status end should 'fail crc32 if wrong password' do - password = SecureRandom.random_bytes + password = 'password' || SecureRandom.random_bytes timestamp = Time.now - T3 = check 'TestNSCA0', 'uxnags01-sbe.net.mobilkom.at' + T3 = check 'TestNSCA0', 'localhost' NSCA::destinations.clear - NSCA::destinations << NSCA::Client.new( 'localhost', Port, password: password+'a') - server = Thread.new { NSCA.dummy_server Port, password: password } - sleep 1 # server needs time to start... - NSCA::send T3.new( 1, 'status', nil, timestamp) + NSCA::destinations << NSCA::Client.new( 'localhost', Port, password+'a') + server = dummy_server hostname: 'localhost', port: Port, password: password + NSCA::send [T3.new( 1, 'status', nil, timestamp)] assert_raise( NSCA::Packet::CSC32CheckFailed) { server.join } end end