URI: 
       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