#!/usr/bin/env ruby # IOS: # logging discriminator CFG mnemonics includes CONFIG_I # logging host SERVER discriminator CFG # JunOS: # set system syslog host SERVER interactive-commands notice # set system syslog host SERVER match "^mgd\[[0-9]+\]: UI_COMMIT: .*" # Ports < 1024 need extra privileges, use a port higher than this by setting the port option in your oxidized config file. # To use the default port for syslog (514) you shouldn't pass an argument, but you will need to allow this with: # sudo setcap 'cap_net_bind_service=+ep' /usr/bin/ruby # Config options are: # syslogd # port (Default = 514) # file (Default = messages) # resolve (Default = true) # To stop the resolution of IP's to PTR you can set resolve to false # exit if fork ## TODO: proper daemonize require 'socket' require 'resolv' require_relative 'rest_client' module Oxidized require 'asetus' class Config Root = File.join ENV['HOME'], '.config', 'oxidized' end CFGS = Asetus.new :name => 'oxidized', :load => false, :key_to_s => true CFGS.default.syslogd.port = 514 CFGS.default.syslogd.file = 'messages' CFGS.default.syslogd.resolve = true begin CFGS.load rescue => error raise InvalidConfig, "Error loading config: #{error.message}" ensure CFG = CFGS.cfg # convenienence, instead of Config.cfg.password, CFG.password end class SyslogMonitor NAME_MAP = { /(.*)\.ip\.tdc\.net/ => '\1', /(.*)\.ip\.fi/ => '\1', } MSG = { :ios => /%SYS-(SW[0-9]+-)?5-CONFIG_I:/, :junos => 'UI_COMMIT:', :eos => /%SYS-5-CONFIG_I:/, :nxos => /%VSHD-5-VSHD_SYSLOG_CONFIG_I:/, } class << self def udp port = Oxidized::CFG.syslogd.port, listen = 0 io = UDPSocket.new io.bind listen, port new io, :udp end def file syslog_file = Oxidized::CFG.syslogd.file io = open syslog_file, 'r' io.seek 0, IO::SEEK_END new io, :file end end private def initialize io, mode = :udp @mode = mode run io end def rest opt Oxidized::RestClient.next opt end def ios ip, log, i # TODO: we need to fetch 'ip/name' in mode == :file here user = log[i + 5] from = log[-1][1..-2] rest(:user => user, :from => from, :model => 'ios', :ip => ip, :name => getname(ip)) end def jnpr ip, log, i # TODO: we need to fetch 'ip/name' in mode == :file here user = log[i + 2][1..-2] msg = log[(i + 6)..-1].join(' ')[10..-2] msg = nil if msg == 'none' rest(:user => user, :msg => msg, :model => 'jnpr', :ip => ip, :name => getname(ip)) end def handle_log log, ip log = log.to_s.split ' ' if i = log.find_index { |e| e.match(MSG[:ios]) } ios ip, log, i elsif i = log.index(MSG[:junos]) jnpr ip, log, i end end def run io while true log = select [io] log, ip = log.first.first, nil if @mode == :udp log, ip = log.recvfrom_nonblock 2000 ip = ip.last else begin log = log.read_nonblock 2000 rescue EOFError sleep 1 retry end end handle_log log, ip end end def getname ip if Oxidized::CFG.syslogd.resolve == false ip else name = (Resolv.getname ip.to_s rescue ip) NAME_MAP.each { |re, sub| name.sub! re, sub } name end end end end Oxidized::SyslogMonitor.udp # Oxidized::SyslogMonitor.file '/var/log/poop'