1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
#!/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'
|