Commit some of the last minute changes for the WarVOX release - warvox - VoIP based wardialing tool, forked from rapid7/warvox.
DIR Log
DIR Files
DIR Refs
DIR README
---
DIR commit 76fc65ae7c8b0a944971cfcdbdf4dae3bb1cd3ec
DIR parent 4c5bff4f8d4cde61e0435e1a96b92fc707b4cd38
HTML Author: HD Moore <hd_moore@rapid7.com>
Date: Wed, 4 Mar 2009 00:15:40 +0000
Commit some of the last minute changes for the WarVOX release
Diffstat:
M Makefile | 5 ++++-
A bin/verify_install.rb | 70 +++++++++++++++++++++++++++++++
M lib/warvox/config.rb | 27 ++++++++++++++++++++++++++-
M lib/warvox/jobs/analysis.rb | 11 ++++++++++-
M lib/warvox/jobs/dialer.rb | 13 ++++++++++---
M web/app/controllers/dial_jobs_cont… | 13 ++++++++++++-
M web/app/controllers/providers_cont… | 4 ++++
M web/app/models/dial_job.rb | 12 ++++++++++++
M web/app/models/provider.rb | 2 +-
M web/app/views/analyze/index.html.e… | 2 ++
M web/app/views/analyze/view.html.erb | 10 ++++------
M web/app/views/dial_jobs/index.html… | 8 ++++++++
M web/app/views/dial_jobs/new.html.e… | 4 ++++
M web/app/views/dial_results/analyze… | 2 ++
M web/app/views/dial_results/edit.ht… | 4 ++++
M web/app/views/dial_results/index.h… | 2 ++
M web/app/views/dial_results/show.ht… | 5 +++++
M web/app/views/dial_results/view.ht… | 2 ++
M web/app/views/home/about.html.erb | 1 -
M web/app/views/home/index.html.erb | 11 ++++++++---
M web/app/views/layouts/warvox.html.… | 6 +++---
M web/app/views/providers/edit.html.… | 5 ++++-
M web/app/views/providers/index.html… | 3 ++-
M web/app/views/providers/new.html.e… | 1 -
M web/app/views/providers/show.html.… | 4 ++++
M web/config/environment.rb | 5 ++++-
A web/db/migrate/20090303204859_add_… | 9 +++++++++
A web/db/migrate/20090303204917_add_… | 9 +++++++++
A web/db/migrate/20090303225838_add_… | 9 +++++++++
M web/db/schema.rb | 5 ++++-
A web/public/images/close.gif | 0
A web/public/images/loading.gif | 0
A web/public/images/overlay.png | 0
A web/public/javascripts/lightbox.js | 426 +++++++++++++++++++++++++++++++
A web/public/stylesheets/lightbox.css | 27 +++++++++++++++++++++++++++
35 files changed, 691 insertions(+), 26 deletions(-)
---
DIR diff --git a/Makefile b/Makefile
@@ -1,5 +1,8 @@
-all: install
+all: test
+test: install
+ bin/verify_install.rb
+
install: iaxrecord ruby-kissfft db
cp -a src/iaxrecord/iaxrecord bin/
DIR diff --git a/bin/verify_install.rb b/bin/verify_install.rb
@@ -0,0 +1,70 @@
+#!/usr/bin/env ruby
+###################
+
+#
+# Load the library path
+#
+base = __FILE__
+while File.symlink?(base)
+ base = File.expand_path(File.readlink(base), File.dirname(base))
+end
+$:.unshift(File.join(File.expand_path(File.dirname(base)), '..', 'lib'))
+require 'warvox'
+
+#
+# Verify that WarVOX has been installed properly
+#
+
+puts("**********************************************************************")
+puts("* *")
+puts("* WarVOX Installation Verifier *")
+puts("* *")
+puts("**********************************************************************")
+puts(" ")
+
+begin
+ require 'kissfft'
+ puts "[*] The KissFFT module appears to be available"
+rescue ::LoadError
+ puts "[*] ERROR: The KissFFT module has not been installed"
+ exit
+end
+
+sox_path = WarVOX::Config.tool_path('sox')
+if(not sox_path)
+ puts "[*] ERROR: The 'sox' binary could not be found"
+ exit
+end
+
+sox_data = `#{sox_path} --help 2>&1`
+if(sox_data !~ /SUPPORTED FILE FORMATS.*raw/)
+ puts "[*] ERROR: The 'sox' binary does not have support for RAW audio"
+ exit
+end
+puts "[*] The SOX binary appears to be available with RAW file support"
+
+
+if(not WarVOX::Config.tool_path('gnuplot'))
+ puts "[*] ERROR: The 'gnuplot' binary could not be installed"
+ exit
+end
+puts "[*] The GNUPlot binary appears to be available"
+
+if(not WarVOX::Config.tool_path('lame'))
+ puts "[*] ERROR: The 'lame' binary could not be installed"
+ exit
+end
+puts "[*] The LAME binary appears to be available"
+
+
+if(not WarVOX::Config.tool_path('iaxrecord'))
+ puts "[*] ERROR: The 'iaxrecord' binary could not be installed"
+ exit
+end
+puts "[*] The IAXRECORD binary appears to be available"
+
+
+puts " "
+puts "[*] Congratulations! You are now ready to run WarVOX"
+puts "[*] Start WarVOX with bin/warvox.rb"
+puts " "
DIR diff --git a/lib/warvox/config.rb b/lib/warvox/config.rb
@@ -62,7 +62,32 @@ module Config
}
end
return nil
- end
+ end
+
+ # This method prevents two installations of WarVOX from using the same
+ # rails session key. The first time this method is called, it generates
+ # a new key and stores it in the rails directory, afterwards this key
+ # will be used every time.
+ def self.load_session_key
+ kfile = File.join(WarVOX::Base, 'web', 'config', 'session.key')
+ if(not File.exists?(kfile))
+ # XXX: assume /dev/urandom exists
+ kdata = File.read('/dev/urandom', 64).unpack("H*")[0]
+
+ # Create the new session key file
+ fd = File.new(kfile, 'w')
+
+ # Make this file mode 0600
+ File.chmod(0600, kfile)
+
+ # Write it and close
+ fd.write(kdata)
+ fd.close
+ return kdata
+ end
+ File.read(kfile)
+ end
+
end
end
DIR diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
@@ -3,7 +3,13 @@ module Jobs
class Analysis < Base
require 'fileutils'
- require 'kissfft'
+
+ @@kissfft_loaded = false
+ begin
+ require 'kissfft'
+ @kissfft_loaded = true
+ rescue ::LoadError
+ end
def type
'analysis'
@@ -11,6 +17,9 @@ class Analysis < Base
def initialize(job_id)
@name = job_id
+ if(not @@kissfft_loaded)
+ raise RuntimeError, "The KissFFT module is not availabale, analysis failed"
+ end
end
def get_job
DIR diff --git a/lib/warvox/jobs/dialer.rb b/lib/warvox/jobs/dialer.rb
@@ -15,7 +15,12 @@ class Dialer < Base
@seconds = model.seconds
@lines = model.lines
@nums = shuffle_a(WarVOX::Phone.crack_mask(@range))
- @cid = '8005551212' # XXX: Read from job
+
+ # CallerID modes (SELF or a mask)
+ @cid_self = model.cid_mask == 'SELF'
+ if(not @cid_self)
+ @cid_range = WarVOX::Phone.crack_mask(model.cid_mask)
+ end
end
#
@@ -39,7 +44,7 @@ class Dialer < Base
def get_providers
res = []
- ::Provider.find(:all).each do |prov|
+ ::Provider.find_all_by_enabled(true).each do |prov|
info = {
:name => prov.name,
:id => prov.id,
@@ -116,6 +121,7 @@ class Dialer < Base
fail = 1
byte = 0
path = ''
+ cid = @cid_self ? num : @cid_range[ rand(@cid_range.length) ]
IO.popen(
[
@@ -123,7 +129,7 @@ class Dialer < Base
prov[:host],
prov[:user],
prov[:pass],
- @cid,
+ cid,
out,
num,
@seconds
@@ -144,6 +150,7 @@ class Dialer < Base
res = ::DialResult.new
res.number = num
+ res.cid = cid
res.dial_job_id = @name
res.provider_id = prov[:id]
res.completed = (fail == 0) ? true : false
DIR diff --git a/web/app/controllers/dial_jobs_controller.rb b/web/app/controllers/dial_jobs_controller.rb
@@ -55,13 +55,24 @@ class DialJobsController < ApplicationController
# POST /dial_jobs
# POST /dial_jobs.xml
def create
- @dial_job = DialJob.new(params[:dial_job])
+
+ @dial_job = DialJob.new(params[:dial_job])
+
+ if(Provider.find_all_by_enabled(true).length == 0)
+ @dial_job.errors.add("No providers have been configured or enabled, this job ")
+ respond_to do |format|
+ format.html { render :action => "new" }
+ format.xml { render :xml => @dial_job.errors, :status => :unprocessable_entity }
+ end
+ return
+ end
@dial_job.status = 'submitted'
@dial_job.progress = 0
@dial_job.started_at = nil
@dial_job.completed_at = nil
@dial_job.range.gsub!(/[^0-9X]/, '')
+ @dial_job.cid_mask.gsub!(/[^0-9X]/, '') if @dial_job.cid_mask != "SELF"
respond_to do |format|
if @dial_job.save
DIR diff --git a/web/app/controllers/providers_controller.rb b/web/app/controllers/providers_controller.rb
@@ -6,6 +6,8 @@ class ProvidersController < ApplicationController
def index
@providers = Provider.find(:all)
@new_provider = Provider.new
+ @new_provider.enabled = true
+
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @providers }
@@ -27,6 +29,7 @@ class ProvidersController < ApplicationController
# GET /providers/new.xml
def new
@provider = Provider.new
+ @provider.enabled = true
respond_to do |format|
format.html # new.html.erb
@@ -43,6 +46,7 @@ class ProvidersController < ApplicationController
# POST /providers.xml
def create
@provider = Provider.new(params[:provider])
+ @provider.enabled = true
respond_to do |format|
if @provider.save
DIR diff --git a/web/app/models/dial_job.rb b/web/app/models/dial_job.rb
@@ -9,5 +9,17 @@ class DialJob < ActiveRecord::Base
if(range.gsub(/[^0-9X]/, '').length != 10)
errors.add(:range, "The range must be exactly 10 characters long and made up of 0-9 and X as the mask.")
end
+
+ if(range.scan(/X/).length > 5)
+ errors.add(:range, "The range must contain no more than 5 mask digits.")
+ end
+
+ if(cid_mask != "SELF" and cid_mask.gsub(/[^0-9X]/, '').length != 10)
+ errors.add(:range, "The Caller ID must be exactly 10 characters long and made up of 0-9 and X as the mask.")
+ end
+
+ if(cid_mask != "SELF" and cid_mask.scan(/X/).length > 5)
+ errors.add(:range, "The Caller ID must contain no more than 5 mask digits.")
+ end
end
end
DIR diff --git a/web/app/models/provider.rb b/web/app/models/provider.rb
@@ -3,5 +3,5 @@ class Provider < ActiveRecord::Base
validates_presence_of :name, :host, :port, :user, :pass, :lines
validates_numericality_of :port, :less_than => 65536, :greater_than => 0
- validates_numericality_of :lines, :less_than => 255, :greater_than => 1
+ validates_numericality_of :lines, :less_than => 255, :greater_than => 0
end
DIR diff --git a/web/app/views/analyze/index.html.erb b/web/app/views/analyze/index.html.erb
@@ -6,6 +6,7 @@
<tr>
<th>ID</th>
<th>Range</th>
+ <th>CallerID</th>
<th>Connected</th>
<th>Date</th>
</tr>
@@ -14,6 +15,7 @@
<tr>
<td><%=h dial_job.id %></td>
<td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
<td><%=h DialResult.find(:all, :conditions => ['dial_job_id = ? and processed = ?', dial_job.id, true]).length.to_s + "/" + dial_job.dial_results.length.to_s %></td>
<td><%=h dial_job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
<td><%= link_to 'View', view_analyze_path(dial_job) %></td>
DIR diff --git a/web/app/views/analyze/view.html.erb b/web/app/views/analyze/view.html.erb
@@ -15,6 +15,7 @@
<th>Signal</th>
<th>Spectrum</th>
<th>Number</th>
+ <th>CallerID</th>
<th>Provider</th>
<th>Call Time</th>
<th>Ring Time</th>
@@ -24,18 +25,15 @@
<tr>
<td><%=h dial_result.id %></td>
<td>
- <a href="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=big_sig_dots">
- <img src="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=small_sig" border=0>
- </a>
+ <a href="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=big_sig_dots" rel="lightbox"><img src="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=small_sig" /></a>
</td>
<td>
- <a href="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=big_freq">
- <img src="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=small_freq" border=0>
- </a>
+ <a href="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=big_freq" rel="lightbox"><img src="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=small_freq" /></a>
</td>
<td>
<a href="<%=resource_analyze_path(@job_id)%>?result_id=<%= dial_result.id %>&type=mp3" target="_new"><%= dial_result.number %></a>
</td>
+ <td><%=h dial_result.cid %></td>
<td><%=h dial_result.provider.name %></td>
<td><%=h dial_result.seconds %></td>
<td><%=h dial_result.ringtime %></td>
DIR diff --git a/web/app/views/dial_jobs/index.html.erb b/web/app/views/dial_jobs/index.html.erb
@@ -6,6 +6,7 @@
<tr>
<th>ID</th>
<th>Range</th>
+ <th>CallerID</th>
<th>Seconds</th>
<th>Lines</th>
<th>Submitted Time</th>
@@ -15,6 +16,7 @@
<tr>
<td><%=h dial_job.id %></td>
<td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
<td><%=h dial_job.seconds %></td>
<td><%=h dial_job.lines %></td>
<td><%=h dial_job.created_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
@@ -35,6 +37,7 @@
<tr>
<th>ID</th>
<th>Range</th>
+ <th>CallerID</th>
<th>Seconds</th>
<th>Lines</th>
<th>Status</th>
@@ -46,6 +49,7 @@
<tr class='active_job_row'>
<td><%=h dial_job.id %></td>
<td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
<td><%=h dial_job.seconds %></td>
<td><%=h dial_job.lines %></td>
<td><%=h dial_job.status %></td>
@@ -85,6 +89,10 @@
<%= f.text_field :lines, :value => 10 %>
</p>
<p>
+ <%= f.label :lines, 'The source Caller ID range (555-555-55XX)' %><br />
+ <%= f.text_field :cid_mask, :value => '000-000-XXXX' %>
+ </p>
+ <p>
<%= f.submit "Create" %>
</p>
<% end %>
DIR diff --git a/web/app/views/dial_jobs/new.html.erb b/web/app/views/dial_jobs/new.html.erb
@@ -15,6 +15,10 @@
<%= f.text_field :lines, :value => 10 %>
</p>
<p>
+ <%= f.label :lines, 'The source Caller ID range (555-555-55XX or SELF)' %><br />
+ <%= f.text_field :cid_mask, :value => '000-000-XXXX' %>
+ </p>
+ <p>
<%= f.submit "Create" %>
</p>
<% end %>
DIR diff --git a/web/app/views/dial_results/analyze.html.rb b/web/app/views/dial_results/analyze.html.rb
@@ -12,6 +12,7 @@
<table class='table_scaffold' width='100%'>
<tr>
<th>Number</th>
+ <th>CallerID</th>
<th>Provider</th>
<th>Call Time</th>
<th>Ring Time</th>
@@ -20,6 +21,7 @@
<% for dial_result in @dial_data_todo.sort{|a,b| a.number <=> b.number } %>
<tr>
<td><%=h dial_result.number %></td>
+ <td><%=h dial_result.cid %></td>
<td><%=h dial_result.provider.name %></td>
<td><%=h dial_result.seconds %></td>
<td><%=h dial_result.ringtime %></td>
DIR diff --git a/web/app/views/dial_results/edit.html.erb b/web/app/views/dial_results/edit.html.erb
@@ -8,6 +8,10 @@
<%= f.text_field :number %>
</p>
<p>
+ <%= f.label :cid %><br />
+ <%= f.text_field :cid %>
+ </p>
+ <p>
<%= f.label :dial_job_id %><br />
<%= f.text_field :dial_job_id %>
</p>
DIR diff --git a/web/app/views/dial_results/index.html.erb b/web/app/views/dial_results/index.html.erb
@@ -5,6 +5,7 @@
<tr>
<th>ID</th>
<th>Range</th>
+ <th>CallerID</th>
<th>Seconds</th>
<th>Lines</th>
<th>Started</th>
@@ -14,6 +15,7 @@
<tr>
<td><%=h dial_job.id %></td>
<td><%=h dial_job.range %></td>
+ <td><%=h dial_job.cid_mask %></td>
<td><%=h dial_job.seconds %></td>
<td><%=h dial_job.lines %></td>
<td><%=h dial_job.started_at.localtime.strftime("%Y-%m-%d %H:%M:%S %Z") %></td>
DIR diff --git a/web/app/views/dial_results/show.html.erb b/web/app/views/dial_results/show.html.erb
@@ -4,6 +4,11 @@
</p>
<p>
+ <b>CallerID:</b>
+ <%=h @dial_result.cid %>
+</p>
+
+<p>
<b>Dial job:</b>
<%=h @dial_result.dial_job_id %>
</p>
DIR diff --git a/web/app/views/dial_results/view.html.erb b/web/app/views/dial_results/view.html.erb
@@ -14,6 +14,7 @@
<table class='table_scaffold' width='100%'>
<tr>
<th>Number</th>
+ <th>CallerID</th>
<th>Provider</th>
<th>Completed</th>
<th>Busy</th>
@@ -25,6 +26,7 @@
<% for dial_result in @dial_results.sort{|a,b| a.number <=> b.number } %>
<tr>
<td><%=h dial_result.number %></td>
+ <td><%=h dial_result.cid %></td>
<td><%=h dial_result.provider.name %></td>
<td><%=h dial_result.completed %></td>
<td><%=h dial_result.busy %></td>
DIR diff --git a/web/app/views/home/about.html.erb b/web/app/views/home/about.html.erb
@@ -1 +0,0 @@
-WarVOX is a project created by H D Moore for Metasploit LLC
DIR diff --git a/web/app/views/home/index.html.erb b/web/app/views/home/index.html.erb
@@ -4,9 +4,14 @@
<h1 class='title'>Introduction</h1>
<b>WarVOX</b> is a suite of tools for exploring, classifying, and auditing the
-telephone system. Unlike normal wardialing tools, WarVOX can be used to assess
-voice lines as well as data. To get started, configure one or more <b>Providers</b>
-and submit a new <b>Job</b>.
+telephone system. Unlike normal wardialing tools, WarVOX works with the actual
+audio from each call and does not use a modem directly. This model allows
+WarVOX to find and classify a wide range of devices, including modems, through
+direct data analysis. To get started, configure an IAX-capable VoIP provider
+in the <b>Providers</b> section, then submit a new <b>Job</b>. Keep in mind
+that the laws regulating automated dialing can vary by location, it is your
+responsibility to ensure that your local laws and the laws governing the
+target telephone range are respected.
</td><td valign='top' align='center'>
DIR diff --git a/web/app/views/layouts/warvox.html.erb b/web/app/views/layouts/warvox.html.erb
@@ -5,16 +5,16 @@
<title><%= @title || "WarVOX" %></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-US" />
- <%= stylesheet_link_tag 'global' %>
+ <%= stylesheet_link_tag 'global', 'lightbox' %>
<!--[if IE 7]><%= stylesheet_link_tag 'ie7' %><![endif]-->
- <%= javascript_include_tag 'custom', 'prototype', 'effects', 'FusionCharts' %>
+ <%= javascript_include_tag 'custom', 'prototype', 'effects', 'FusionCharts', 'lightbox' %>
</head>
<body>
<div id="header">
<%= render :partial => 'shared/header' %>
-</div>
+</div>
<div id="content">
<div class="box_full">
DIR diff --git a/web/app/views/providers/edit.html.erb b/web/app/views/providers/edit.html.erb
@@ -2,7 +2,10 @@
<% form_for(@provider) do |f| %>
<%= f.error_messages %>
-
+ <p>
+ <%= f.label :enabled %><br />
+ <%= f.check_box :enabled %>
+ </p>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
DIR diff --git a/web/app/views/providers/index.html.erb b/web/app/views/providers/index.html.erb
@@ -2,6 +2,7 @@
<h1 class='title'>Providers</h1>
<table class='table_scaffold' width='100%'>
<tr>
+ <th>Enabled</th>
<th>Name</th>
<th>Host</th>
<th>Port</th>
@@ -12,6 +13,7 @@
<% for provider in @providers %>
<tr>
+ <td><%=h provider.enabled %></td>
<td><%=h provider.name %></td>
<td><%=h provider.host %></td>
<td><%=h provider.port %></td>
@@ -37,7 +39,6 @@
<% form_for(@new_provider) do |f| %>
<%= f.error_messages %>
-
<p>
<%= f.label :name, 'The nickname for this provider' %><br />
<%= f.text_field :name %>
DIR diff --git a/web/app/views/providers/new.html.erb b/web/app/views/providers/new.html.erb
@@ -2,7 +2,6 @@
<% form_for(@provider) do |f| %>
<%= f.error_messages %>
-
<p>
<%= f.label :name, 'The nickname for this provider' %><br />
<%= f.text_field :name %>
DIR diff --git a/web/app/views/providers/show.html.erb b/web/app/views/providers/show.html.erb
@@ -1,5 +1,9 @@
<h1 class='title'>View Provider</h1>
<p>
+ <b>Enabled:</b>
+ <%=h @provider.enabled %>
+</p>
+<p>
<b>Name:</b>
<%=h @provider.name %>
</p>
DIR diff --git a/web/config/environment.rb b/web/config/environment.rb
@@ -58,9 +58,12 @@ Rails::Initializer.run do |config|
# If you change this key, all old sessions will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
+ #
+ # WarVOX generates a new key for each installation
+ # This file is stored under web/config/session.key
config.action_controller.session = {
:session_key => '_warvox',
- :secret => 'feada588709ac0fd51987c264ad6fba018be0e3a579547b9c9978098a71d4a1a96e630150aa0e26cba058c7f8081827b70586e0eb3cd83e7abf81ce49a13301e'
+ :secret => WarVOX::Config.load_session_key
}
# Use the database for sessions instead of the cookie-based default,
DIR diff --git a/web/db/migrate/20090303204859_add_cid_mask_to_dial_jobs.rb b/web/db/migrate/20090303204859_add_cid_mask_to_dial_jobs.rb
@@ -0,0 +1,9 @@
+class AddCidMaskToDialJobs < ActiveRecord::Migration
+ def self.up
+ add_column :dial_jobs, :cid_mask, :string
+ end
+
+ def self.down
+ remove_column :dial_jobs, :cid_mask
+ end
+end
DIR diff --git a/web/db/migrate/20090303204917_add_cid_to_dial_results.rb b/web/db/migrate/20090303204917_add_cid_to_dial_results.rb
@@ -0,0 +1,9 @@
+class AddCidToDialResults < ActiveRecord::Migration
+ def self.up
+ add_column :dial_results, :cid, :string
+ end
+
+ def self.down
+ remove_column :dial_results, :cid
+ end
+end
DIR diff --git a/web/db/migrate/20090303225838_add_enabled_to_providers.rb b/web/db/migrate/20090303225838_add_enabled_to_providers.rb
@@ -0,0 +1,9 @@
+class AddEnabledToProviders < ActiveRecord::Migration
+ def self.up
+ add_column :providers, :enabled, :boolean
+ end
+
+ def self.down
+ remove_column :providers, :enabled
+ end
+end
DIR diff --git a/web/db/schema.rb b/web/db/schema.rb
@@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20090301084459) do
+ActiveRecord::Schema.define(:version => 20090303225838) do
create_table "dial_jobs", :force => true do |t|
t.string "range"
@@ -22,6 +22,7 @@ ActiveRecord::Schema.define(:version => 20090301084459) do
t.boolean "processed"
t.datetime "created_at"
t.datetime "updated_at"
+ t.string "cid_mask"
end
create_table "dial_results", :force => true do |t|
@@ -37,6 +38,7 @@ ActiveRecord::Schema.define(:version => 20090301084459) do
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "processed_at"
+ t.string "cid"
end
create_table "providers", :force => true do |t|
@@ -48,6 +50,7 @@ ActiveRecord::Schema.define(:version => 20090301084459) do
t.integer "lines"
t.datetime "created_at"
t.datetime "updated_at"
+ t.boolean "enabled"
end
end
DIR diff --git a/web/public/images/close.gif b/web/public/images/close.gif
Binary files differ.
DIR diff --git a/web/public/images/loading.gif b/web/public/images/loading.gif
Binary files differ.
DIR diff --git a/web/public/images/overlay.png b/web/public/images/overlay.png
Binary files differ.
DIR diff --git a/web/public/javascripts/lightbox.js b/web/public/javascripts/lightbox.js
@@ -0,0 +1,426 @@
+/*
+ Lightbox JS: Fullsize Image Overlays
+ by Lokesh Dhakar - http://www.huddletogether.com
+
+ For more information on this script, visit:
+ http://huddletogether.com/projects/lightbox/
+
+ Script featured on Dynamic Drive code library Jan 24th, 06':
+ http://www.dynamicdrive.com
+
+ Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
+ (basically, do anything you want, just leave my name and link)
+
+ Table of Contents
+ -----------------
+ Configuration
+
+ Functions
+ - getPageScroll()
+ - getPageSize()
+ - pause()
+ - getKey()
+ - listenKey()
+ - showLightbox()
+ - hideLightbox()
+ - initLightbox()
+ - addLoadEvent()
+
+ Function Calls
+ - addLoadEvent(initLightbox)
+
+*/
+
+
+
+//
+// Configuration
+//
+
+// If you would like to use a custom loading image or close button reference them in the next two lines.
+var loadingImage = '/images/loading.gif';
+var closeButton = '/images/close.gif';
+
+
+
+
+
+//
+// getPageScroll()
+// Returns array with x,y page scroll values.
+// Core code from - quirksmode.org
+//
+function getPageScroll(){
+
+ var yScroll;
+
+ if (self.pageYOffset) {
+ yScroll = self.pageYOffset;
+ } else if (document.documentElement && document.documentElement.scrollTop){ // Explorer 6 Strict
+ yScroll = document.documentElement.scrollTop;
+ } else if (document.body) {// all other Explorers
+ yScroll = document.body.scrollTop;
+ }
+
+ arrayPageScroll = new Array('',yScroll)
+ return arrayPageScroll;
+}
+
+
+
+//
+// getPageSize()
+// Returns array with page width, height and window width, height
+// Core code from - quirksmode.org
+// Edit for Firefox by pHaez
+//
+function getPageSize(){
+
+ var xScroll, yScroll;
+
+ if (window.innerHeight && window.scrollMaxY) {
+ xScroll = document.body.scrollWidth;
+ yScroll = window.innerHeight + window.scrollMaxY;
+ } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
+ xScroll = document.body.scrollWidth;
+ yScroll = document.body.scrollHeight;
+ } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
+ xScroll = document.body.offsetWidth;
+ yScroll = document.body.offsetHeight;
+ }
+
+ var windowWidth, windowHeight;
+ if (self.innerHeight) { // all except Explorer
+ windowWidth = self.innerWidth;
+ windowHeight = self.innerHeight;
+ } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
+ windowWidth = document.documentElement.clientWidth;
+ windowHeight = document.documentElement.clientHeight;
+ } else if (document.body) { // other Explorers
+ windowWidth = document.body.clientWidth;
+ windowHeight = document.body.clientHeight;
+ }
+
+ // for small pages with total height less then height of the viewport
+ if(yScroll < windowHeight){
+ pageHeight = windowHeight;
+ } else {
+ pageHeight = yScroll;
+ }
+
+ // for small pages with total width less then width of the viewport
+ if(xScroll < windowWidth){
+ pageWidth = windowWidth;
+ } else {
+ pageWidth = xScroll;
+ }
+
+
+ arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight)
+ return arrayPageSize;
+}
+
+
+//
+// pause(numberMillis)
+// Pauses code execution for specified time. Uses busy code, not good.
+// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
+//
+function pause(numberMillis) {
+ var now = new Date();
+ var exitTime = now.getTime() + numberMillis;
+ while (true) {
+ now = new Date();
+ if (now.getTime() > exitTime)
+ return;
+ }
+}
+
+//
+// getKey(key)
+// Gets keycode. If 'x' is pressed then it hides the lightbox.
+//
+
+function getKey(e){
+ if (e == null) { // ie
+ keycode = event.keyCode;
+ } else { // mozilla
+ keycode = e.which;
+ }
+ key = String.fromCharCode(keycode).toLowerCase();
+
+ if(key == 'x'){ hideLightbox(); }
+}
+
+
+//
+// listenKey()
+//
+function listenKey () { document.onkeypress = getKey; }
+
+
+//
+// showLightbox()
+// Preloads images. Pleaces new image in lightbox then centers and displays.
+//
+function showLightbox(objLink)
+{
+ // prep objects
+ var objOverlay = document.getElementById('overlay');
+ var objLightbox = document.getElementById('lightbox');
+ var objCaption = document.getElementById('lightboxCaption');
+ var objImage = document.getElementById('lightboxImage');
+ var objLoadingImage = document.getElementById('loadingImage');
+ var objLightboxDetails = document.getElementById('lightboxDetails');
+
+
+ var arrayPageSize = getPageSize();
+ var arrayPageScroll = getPageScroll();
+
+ // center loadingImage if it exists
+ if (objLoadingImage) {
+ objLoadingImage.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - objLoadingImage.height) / 2) + 'px');
+ objLoadingImage.style.left = (((arrayPageSize[0] - 20 - objLoadingImage.width) / 2) + 'px');
+ objLoadingImage.style.display = 'block';
+ }
+
+ // set height of Overlay to take up whole page and show
+ objOverlay.style.height = (arrayPageSize[1] + 'px');
+ objOverlay.style.display = 'block';
+
+ // preload image
+ imgPreload = new Image();
+
+ imgPreload.onload=function(){
+ objImage.src = objLink.href;
+
+ // center lightbox and make sure that the top and left values are not negative
+ // and the image placed outside the viewport
+ var lightboxTop = arrayPageScroll[1] + ((arrayPageSize[3] - 35 - imgPreload.height) / 2);
+ var lightboxLeft = ((arrayPageSize[0] - 20 - imgPreload.width) / 2);
+
+ objLightbox.style.top = (lightboxTop < 0) ? "0px" : lightboxTop + "px";
+ objLightbox.style.left = (lightboxLeft < 0) ? "0px" : lightboxLeft + "px";
+
+
+ objLightboxDetails.style.width = imgPreload.width + 'px';
+
+ if(objLink.getAttribute('title')){
+ objCaption.style.display = 'block';
+ //objCaption.style.width = imgPreload.width + 'px';
+ objCaption.innerHTML = objLink.getAttribute('title');
+ } else {
+ objCaption.style.display = 'none';
+ }
+
+ // A small pause between the image loading and displaying is required with IE,
+ // this prevents the previous image displaying for a short burst causing flicker.
+ if (navigator.appVersion.indexOf("MSIE")!=-1){
+ pause(250);
+ }
+
+ if (objLoadingImage) { objLoadingImage.style.display = 'none'; }
+ objLightbox.style.display = 'block';
+
+ // After image is loaded, update the overlay height as the new image might have
+ // increased the overall page height.
+ arrayPageSize = getPageSize();
+ objOverlay.style.height = (arrayPageSize[1] + 'px');
+
+ // Check for 'x' keypress
+ listenKey();
+
+ return false;
+ }
+
+ imgPreload.src = objLink.href;
+
+}
+
+
+
+
+
+//
+// hideLightbox()
+//
+function hideLightbox()
+{
+ // get objects
+ objOverlay = document.getElementById('overlay');
+ objLightbox = document.getElementById('lightbox');
+
+ // hide lightbox and overlay
+ objOverlay.style.display = 'none';
+ objLightbox.style.display = 'none';
+
+ // disable keypress listener
+ document.onkeypress = '';
+}
+
+
+
+
+//
+// initLightbox()
+// Function runs on window load, going through link tags looking for rel="lightbox".
+// These links receive onclick events that enable the lightbox display for their targets.
+// The function also inserts html markup at the top of the page which will be used as a
+// container for the overlay pattern and the inline image.
+//
+function initLightbox()
+{
+
+ if (!document.getElementsByTagName){ return; }
+ var anchors = document.getElementsByTagName("a");
+
+ // loop through all anchor tags
+ for (var i=0; i<anchors.length; i++){
+ var anchor = anchors[i];
+
+ if (anchor.getAttribute("href") && (anchor.getAttribute("rel") == "lightbox")){
+ anchor.onclick = function () {showLightbox(this); return false;}
+ }
+ }
+
+ // the rest of this code inserts html at the top of the page that looks like this:
+ //
+ // <div id="overlay">
+ // <a href="#" onclick="hideLightbox(); return false;"><img id="loadingImage" /></a>
+ // </div>
+ // <div id="lightbox">
+ // <a href="#" onclick="hideLightbox(); return false;" title="Click anywhere to close image">
+ // <img id="closeButton" />
+ // <img id="lightboxImage" />
+ // </a>
+ // <div id="lightboxDetails">
+ // <div id="lightboxCaption"></div>
+ // <div id="keyboardMsg"></div>
+ // </div>
+ // </div>
+
+ var objBody = document.getElementsByTagName("body").item(0);
+
+ // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file)
+ var objOverlay = document.createElement("div");
+ objOverlay.setAttribute('id','overlay');
+ objOverlay.onclick = function () {hideLightbox(); return false;}
+ objOverlay.style.display = 'none';
+ objOverlay.style.position = 'absolute';
+ objOverlay.style.top = '0';
+ objOverlay.style.left = '0';
+ objOverlay.style.zIndex = '90';
+ objOverlay.style.width = '100%';
+ objBody.insertBefore(objOverlay, objBody.firstChild);
+
+ var arrayPageSize = getPageSize();
+ var arrayPageScroll = getPageScroll();
+
+ // preload and create loader image
+ var imgPreloader = new Image();
+
+ // if loader image found, create link to hide lightbox and create loadingimage
+ imgPreloader.onload=function(){
+
+ var objLoadingImageLink = document.createElement("a");
+ objLoadingImageLink.setAttribute('href','#');
+ objLoadingImageLink.onclick = function () {hideLightbox(); return false;}
+ objOverlay.appendChild(objLoadingImageLink);
+
+ var objLoadingImage = document.createElement("img");
+ objLoadingImage.src = loadingImage;
+ objLoadingImage.setAttribute('id','loadingImage');
+ objLoadingImage.style.position = 'absolute';
+ objLoadingImage.style.zIndex = '150';
+ objLoadingImageLink.appendChild(objLoadingImage);
+
+ imgPreloader.onload=function(){}; // clear onLoad, as IE will flip out w/animated gifs
+
+ return false;
+ }
+
+ imgPreloader.src = loadingImage;
+
+ // create lightbox div, same note about styles as above
+ var objLightbox = document.createElement("div");
+ objLightbox.setAttribute('id','lightbox');
+ objLightbox.style.display = 'none';
+ objLightbox.style.position = 'absolute';
+ objLightbox.style.zIndex = '100';
+ objBody.insertBefore(objLightbox, objOverlay.nextSibling);
+
+ // create link
+ var objLink = document.createElement("a");
+ objLink.setAttribute('href','#');
+ objLink.setAttribute('title','Click to close');
+ objLink.onclick = function () {hideLightbox(); return false;}
+ objLightbox.appendChild(objLink);
+
+ // preload and create close button image
+ var imgPreloadCloseButton = new Image();
+
+ // if close button image found,
+ imgPreloadCloseButton.onload=function(){
+
+ var objCloseButton = document.createElement("img");
+ objCloseButton.src = closeButton;
+ objCloseButton.setAttribute('id','closeButton');
+ objCloseButton.style.position = 'absolute';
+ objCloseButton.style.zIndex = '200';
+ objLink.appendChild(objCloseButton);
+
+ return false;
+ }
+
+ imgPreloadCloseButton.src = closeButton;
+
+ // create image
+ var objImage = document.createElement("img");
+ objImage.setAttribute('id','lightboxImage');
+ objLink.appendChild(objImage);
+
+ // create details div, a container for the caption and keyboard message
+ var objLightboxDetails = document.createElement("div");
+ objLightboxDetails.setAttribute('id','lightboxDetails');
+ objLightbox.appendChild(objLightboxDetails);
+
+ // create caption
+ var objCaption = document.createElement("div");
+ objCaption.setAttribute('id','lightboxCaption');
+ objCaption.style.display = 'none';
+ objLightboxDetails.appendChild(objCaption);
+
+ // create keyboard message
+ var objKeyboardMsg = document.createElement("div");
+ objKeyboardMsg.setAttribute('id','keyboardMsg');
+ objKeyboardMsg.innerHTML = 'press <kbd>x</kbd> to close';
+ objLightboxDetails.appendChild(objKeyboardMsg);
+
+
+}
+
+
+
+
+//
+// addLoadEvent()
+// Adds event to window.onload without overwriting currently assigned onload functions.
+// Function found at Simon Willison's weblog - http://simon.incutio.com/
+//
+function addLoadEvent(func)
+{
+ var oldonload = window.onload;
+ if (typeof window.onload != 'function'){
+ window.onload = func;
+ } else {
+ window.onload = function(){
+ oldonload();
+ func();
+ }
+ }
+
+}
+
+
+
+addLoadEvent(initLightbox); // run initLightbox onLoad
DIR diff --git a/web/public/stylesheets/lightbox.css b/web/public/stylesheets/lightbox.css
@@ -0,0 +1,26 @@
+#lightbox{
+ background-color:#eee;
+ padding: 10px;
+ border-bottom: 2px solid #666;
+ border-right: 2px solid #666;
+ }
+#lightboxDetails{
+ font-size: 0.8em;
+ padding-top: 0.4em;
+ }
+#lightboxCaption{ float: left; }
+#keyboardMsg{ float: right; }
+#closeButton{ top: 5px; right: 5px; }
+
+#lightbox img{ border: none; clear: both;}
+#overlay img{ border: none; }
+
+#overlay{ background-image: url(overlay.png); }
+
+* html #overlay{
+ background-color: #333;
+ back\ground-color: transparent;
+ background-image: url(blank.gif);
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="overlay.png", sizingMethod="scale");
+ }
+
+\ No newline at end of file