diff --git a/README.md b/README.md index 7969a51..e82d254 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,17 @@ This is a ruby-pure implementation. First it was planed to provide a client-API, but now there is also a full server-API. -Tested against nsca-2.7. +Tested against nsca-2.7, -2.9. + +Usage +===== + +Simple sending + +```ruby +NSCA.destinations << NSCA::Client.new('localhost') +NSCA.send 'serverA', 'serviceA', 1, 'Ok' +``` TO DO AND DONE ============== @@ -34,5 +44,4 @@ DONE Copyright ========= -Copyright (c) 2013 Denis Knauf. See LICENSE.txt for -further details. +Copyright (c) 2013-2015 Denis Knauf. See [LICENSE.txt](LICENSE.txt) for further details. diff --git a/Rakefile b/Rakefile index 852c6e2..0a2131f 100644 --- a/Rakefile +++ b/Rakefile @@ -15,7 +15,7 @@ require 'jeweler' Jeweler::Tasks.new do |gem| # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options gem.name = "nsca" - gem.homepage = "http://github.com/DenisKnauf/nsca" + gem.homepage = "http://github.com/DenisKnauf/ruby-nsca" gem.license = "LGPL-3" gem.summary = %Q{Nagios passive alerts with friendly API} gem.description = %Q{Create your alerts easily and send it to Nagios} diff --git a/VERSION b/VERSION index 341cf11..7dff5b8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 \ No newline at end of file +0.2.1 \ No newline at end of file diff --git a/lib/nsca.rb b/lib/nsca.rb index 766c1a0..d3cb622 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,30 @@ 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 rand_padding( str, maxlen) str + SecureRandom.random_bytes( maxlen - str.length) end + def str2cstr_rand_padding( str, maxlen = nil) rand_padding str2cstr( str, maxlen), maxlen end + def str2nstr str, maxlen = nil + str = str.to_s.gsub ' ', "\x00" + "#{str} " + end + def str2nstr_rand_padding( str, maxlen = nil) rand_padding str2nstr( str, maxlen), maxlen 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 569d950..f305785 100644 --- a/lib/nsca/check.rb +++ b/lib/nsca/check.rb @@ -51,7 +51,7 @@ module NSCA def min() self.class.min end def max() self.class.max end def to_a() [label, value, unit, warn, crit, min, max] end - def to_s() "#{label}=#{value}#{unit},#{warn},#{crit},#{min},#{max}" end + def to_s() "'#{label.gsub /[\n'\|]/, ''}'=#{value}#{unit},#{warn},#{crit},#{min},#{max}" end def to_sym() self.class.to_sym end def to_h @@ -107,7 +107,7 @@ module NSCA end def push *perfdatas - perfdatas.each {|perfdata| @perfdatas[perfdata.label] = perfdata } + perfdatas.each {|perfdata| @perfdatas[perfdata.label.to_sym] = perfdata } @perfdatas end @@ -122,6 +122,7 @@ module NSCA def []= perfdata_label, value return push value if value.is_a? PerformanceData::Base + perfdata_label = perfdata_label.to_sym @perfdatas[perfdata_label] = perfdata_for( perfdata_label).new value end @@ -146,11 +147,7 @@ module NSCA def determine_return_code self.class.perfdatas.map do |label, pdc| pd = @perfdatas[label] - if pd - pd.return_code - else - -1 - end + pd ? pd.return_code : -1 end.max end @@ -166,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..ccbbc87 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/nsca.gemspec b/nsca.gemspec index d6957c2..d763177 100644 --- a/nsca.gemspec +++ b/nsca.gemspec @@ -5,11 +5,11 @@ Gem::Specification.new do |s| s.name = "nsca" - s.version = "0.2.0" + s.version = "0.2.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Denis Knauf"] - s.date = "2013-04-11" + s.date = "2013-04-24" s.description = "Create your alerts easily and send it to Nagios" s.email = "Denis.Knauf@gmail.com" s.extra_rdoc_files = [ diff --git a/test/test_nsca.rb b/test/test_nsca.rb index 18f987f..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,23 +70,20 @@ 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 - assert_equal pc0.status, "0123456789"*51+"A" - assert_equal pc1.status, "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," + # 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) - assert_raise NSCA::Packet::CSC32CheckFailed do - server.join - end + 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 end @@ -227,4 +237,18 @@ class TestNSCA::Check < Test::Unit::TestCase assert_equal ce1_data, ce2_data end end + + context 'Perfdatas in Checks' do + should 'be saved as symbol-key' do + PH = NSCA::PerformanceData.new 'simplename', :ms + CH = NSCA::Check.new 'a check with perfdata', 'hostname', [PH] + assert_equal PH, CH.perfdatas[:'simplename'] + ch = CH.new + assert_equal nil, ch['simplename'] + assert_equal nil, ch[:simplename] + a = 0 + ch.measure( 'simplename') { 0.upto( 10000) { a += 1 } } + assert_equal ch['simplename'], ch[:simplename] + end + end end