diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c23188 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.lock +pkg/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f5c6d67 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,9 @@ +language: ruby + +rvm: + - 2.0.0 + - 1.9.3 + - 1.8.7 + +gemfile: + - Gemfile diff --git a/Gemfile b/Gemfile index 689b9ac..fa75df1 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,3 @@ source 'https://rubygems.org' -gem 'rack' +gemspec diff --git a/README.md b/README.md index 6a59a8e..2f29d09 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ It lacks: * authentication * caching (parses file on each request, page does auto-refresh every minute as OpenVPN updates the status file these often) * management interface support -* a gem * *possibly more...* ## License diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..93cb943 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/bin/openvpn-status-web b/bin/openvpn-status-web new file mode 100755 index 0000000..af7ad16 --- /dev/null +++ b/bin/openvpn-status-web @@ -0,0 +1,4 @@ + +require 'openvpn-status-web' + +OpenVPNStatusWeb::Daemon.run! diff --git a/examples/status.v1 b/examples/status.v1 new file mode 100644 index 0000000..293c4d6 --- /dev/null +++ b/examples/status.v1 @@ -0,0 +1,14 @@ +OpenVPN CLIENT LIST +Updated,Sun Jan 1 23:42:00 2012 +Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since +foo,1.2.3.4:1234,11811160064,4194304,Sun Jan 1 23:42:00 2012 +bar,1.2.3.5:1235,512,2048,Sun Jan 1 23:42:00 2012 +ROUTING TABLE +Virtual Address,Common Name,Real Address,Last Ref +192.168.0.0/24,foo,1.2.3.4:1234,Sun Jan 1 23:42:00 2012 +192.168.66.2,bar,1.2.3.5:1235,Sun Jan 1 23:42:00 2012 +192.168.66.3,foo,1.2.3.4:1234,Sun Jan 1 23:42:00 2012 +2001:db8:0:0::1000,bar,1.2.3.5:1235,Sun Jan 1 23:42:00 2012 +GLOBAL STATS +Max bcast/mcast queue length,42 +END diff --git a/examples/status.v2 b/examples/status.v2 new file mode 100644 index 0000000..248dce8 --- /dev/null +++ b/examples/status.v2 @@ -0,0 +1,12 @@ +TITLE,OpenVPN 2.1_rc15 mipsel-unknown-linux-gnu [SSL] [LZO1] [EPOLL] built on Mar 27 2009 +TIME,Sun Jan 1 23:42:00 2012,1238702330 +HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t) +CLIENT_LIST,foo,1.2.3.4:1234,11811160064,4194304,Sun Jan 1 23:42:00 2012,1238702330 +CLIENT_LIST,bar,1.2.3.5:1235,512,2048,Sun Jan 1 23:42:00 2012,1238702330 +HEADER,ROUTING_TABLE,Virtual Address,Common Name,Real Address,Last Ref,Last Ref (time_t) +ROUTING_TABLE,192.168.0.0/24,foo,1.2.3.4:1234,Sun Jan 1 23:42:00 2012,1238702330 +ROUTING_TABLE,192.168.66.2,bar,1.2.3.5:1235,Sun Jan 1 23:42:00 2012,1238702330 +ROUTING_TABLE,192.168.66.3,foo,1.2.3.4:1234,Sun Jan 1 23:42:00 2012,1238702330 +ROUTING_TABLE,2001:db8:0:0::1000,bar,1.2.3.5:1235,Sun Jan 1 23:42:00 2012,1238702330 +GLOBAL_STATS,Max bcast/mcast queue length,42 +END diff --git a/examples/status.v3 b/examples/status.v3 new file mode 100644 index 0000000..dcbc2ba --- /dev/null +++ b/examples/status.v3 @@ -0,0 +1,12 @@ +TITLE OpenVPN 2.1_rc15 mipsel-unknown-linux-gnu [SSL] [LZO1] [EPOLL] built on Mar 27 2009 +TIME Sun Jan 1 23:42:00 2012 1238702330 +HEADER CLIENT_LIST Common Name Real Address Virtual Address Bytes Received Bytes Sent Connected Since Connected Since (time_t) +CLIENT_LIST foo 1.2.3.4:1234 11811160064 4194304 Sun Jan 1 23:42:00 2012 1238702330 +CLIENT_LIST bar 1.2.3.5:1235 512 2048 Sun Jan 1 23:42:00 2012 1238702330 +HEADER ROUTING_TABLE Virtual Address Common Name Real Address Last Ref Last Ref (time_t) +ROUTING_TABLE 192.168.0.0/24 foo 1.2.3.4:1234 Sun Jan 1 23:42:00 2012 1238702330 +ROUTING_TABLE 192.168.66.2 bar 1.2.3.5:1235 Sun Jan 1 23:42:00 2012 1238702330 +ROUTING_TABLE 192.168.66.3 foo 1.2.3.4:1234 Sun Jan 1 23:42:00 2012 1238702330 +ROUTING_TABLE 2001:db8:0:0::1000 bar 1.2.3.5:1235 Sun Jan 1 23:42:00 2012 1238702330 +GLOBAL_STATS Max bcast/mcast queue length 42 +END diff --git a/lib/openvpn-status-web.rb b/lib/openvpn-status-web.rb new file mode 100644 index 0000000..6c0c493 --- /dev/null +++ b/lib/openvpn-status-web.rb @@ -0,0 +1,96 @@ +#!/usr/bin/env ruby + +require 'etc' +require 'logger' +require 'ipaddr' +require 'yaml' +require 'rack' +require 'metriks' + +require 'openvpn-status-web/int_patch' +require 'openvpn-status-web/version' + +module OpenVPNStatusWeb + def self.logger + @logger + end + + def self.logger=(logger) + @logger = logger + end + + class LogFormatter + def call(lvl, time, progname, msg) + "[%s] %-5s %s\n" % [Time.now.strftime('%Y-%m-%d %H:%M:%S'), lvl, msg.to_s] + end + end + + class Daemon + def initialize(name, file) + @name = name + @file = file + end + + def call(env) + main_tmpl = read_template(File.join(File.dirname(__FILE__), 'openvpn-status-web/main.html.erb')) + # variables for template + name = @name + client_list, routing_table, global_stats = read_status_log(@file) + + html = main_tmpl.result(binding) + [200, {"Content-Type" => "text/html"}, [html]] + end + + def read_template(file) + text = File.open(file, 'rb') do |f| f.read end + + ERB.new(text) + end + + def read_status_log(file) + text = File.open(file, 'rb') do |f| f.read end + + current_section = :none + client_list = [] + routing_table = [] + global_stats = [] + + text.lines.each do |line| + (current_section = :cl; next) if line == "OpenVPN CLIENT LIST\n" + (current_section = :rt; next) if line == "ROUTING TABLE\n" + (current_section = :gs; next) if line == "GLOBAL STATS\n" + (current_section = :end; next) if line == "END\n" + + case current_section + when :cl then client_list << line.strip.split(',') + when :rt then routing_table << line.strip.split(',') + when :gs then global_stats << line.strip.split(',') + end + end + + [client_list[2..-1], routing_table[1..-1], global_stats] + end + + def self.run! + if ARGV.length != 4 + puts "Usage: openvpn-status-web vpn-name status-log listen-host listen-port" + exit 1 + end + + OpenVPNStatusWeb.logger = Logger.new(STDOUT) + + OpenVPNStatusWeb.logger.progname = "openvpn-status-web" + OpenVPNStatusWeb.logger.formatter = LogFormatter.new + + OpenVPNStatusWeb.logger.info "Starting..." + + Signal.trap('INT') do + OpenVPNStatusWeb.logger.info "Quitting..." + Rack::Handler::WEBrick.shutdown + end + + app = Daemon.new(ARGV[0], ARGV[1]) + Rack::Handler::WEBrick.run app, :Host => ARGV[2], :Port => ARGV[3] + end + end +end diff --git a/lib/openvpn-status-web/int_patch.rb b/lib/openvpn-status-web/int_patch.rb new file mode 100644 index 0000000..2e57f80 --- /dev/null +++ b/lib/openvpn-status-web/int_patch.rb @@ -0,0 +1,16 @@ + +class Integer + def as_bytes + return "1 Byte" if self == 1 + + label = ["Bytes", "KiB", "MiB", "GiB", "TiB"] + i = 0 + num = self.to_f + while num >= 1024 do + num = num / 1024 + i += 1 + end + + "#{format('%.2f', num)} #{label[i]}" + end +end diff --git a/main.html.erb b/lib/openvpn-status-web/main.html.erb similarity index 100% rename from main.html.erb rename to lib/openvpn-status-web/main.html.erb diff --git a/lib/openvpn-status-web/version.rb b/lib/openvpn-status-web/version.rb new file mode 100644 index 0000000..1f90c2f --- /dev/null +++ b/lib/openvpn-status-web/version.rb @@ -0,0 +1,4 @@ + +module OpenVPNStatusWeb + VERSION = "0.0.1" +end diff --git a/openvpn-status-web.gemspec b/openvpn-status-web.gemspec new file mode 100644 index 0000000..78b53c7 --- /dev/null +++ b/openvpn-status-web.gemspec @@ -0,0 +1,31 @@ + +$:.push File.expand_path("../lib", __FILE__) + +require 'openvpn-status-web/version' + +Gem::Specification.new do |s| + s.name = 'openvpn-status-web' + s.version = OpenVPNStatusWeb::VERSION + s.summary = 'openvpn-status-web' + s.description = 'Small Rack application that parses and serves the OpenVPN status file.' + s.author = 'Christian Nicolai' + s.email = 'chrnicolai@gmail.com' + s.license = 'Apache License Version 2.0' + s.homepage = 'https://github.com/cmur2/openvpn-status-web' + + s.files = `git ls-files`.split($/) + s.test_files = s.files.grep(%r{^(test|spec|features)/}) + + s.require_paths = ['lib'] + + s.executables = ['openvpn-status-web'] + + s.add_runtime_dependency 'rack' + s.add_runtime_dependency 'json' + s.add_runtime_dependency 'metriks' + + s.add_development_dependency 'bundler', '~> 1.3' + s.add_development_dependency 'rake' + s.add_development_dependency 'rspec' + s.add_development_dependency 'rack-test' +end diff --git a/status.rb b/status.rb deleted file mode 100755 index 3cd5d9f..0000000 --- a/status.rb +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env ruby - -require 'erb' - -require 'rubygems' -require 'rack' - -class Integer - def as_bytes - return "1 Byte" if self == 1 - - label = ["Bytes", "KiB", "MiB", "GiB", "TiB"] - i = 0 - num = self.to_f - while num >= 1024 do - num = num / 1024 - i += 1 - end - - "#{format('%.2f', num)} #{label[i]}" - end -end - -class OpenVPNStatusWeb - def initialize(name, file) - @name = name - @file = file - end - - def call(env) - main_tmpl = read_template(File.join(File.dirname(__FILE__), 'main.html.erb')) - # variables for template - name = @name - client_list, routing_table, global_stats = read_status_log(@file) - - html = main_tmpl.result(binding) - [200, {"Content-Type" => "text/html"}, [html]] - end - - def read_template(file) - text = File.open(file, 'rb') do |f| f.read end - - ERB.new(text) - end - - def read_status_log(file) - text = File.open(file, 'rb') do |f| f.read end - - current_section = :none - client_list = [] - routing_table = [] - global_stats = [] - - text.lines.each do |line| - (current_section = :cl; next) if line == "OpenVPN CLIENT LIST\n" - (current_section = :rt; next) if line == "ROUTING TABLE\n" - (current_section = :gs; next) if line == "GLOBAL STATS\n" - (current_section = :end; next) if line == "END\n" - - case current_section - when :cl then client_list << line.strip.split(',') - when :rt then routing_table << line.strip.split(',') - when :gs then global_stats << line.strip.split(',') - end - end - - [client_list[2..-1], routing_table[1..-1], global_stats] - end -end - -if ARGV.length != 4 - puts "Usage: status.rb vpn-name status-log listen-host listen-port" -else - Rack::Handler::WEBrick.run OpenVPNStatusWeb.new(ARGV[0], ARGV[1]), :Host => ARGV[2], :Port => ARGV[3] -end