Introduction
Introduction Statistics Contact Development Disclaimer Help
First push of the new code base - warvox - VoIP based wardialing tool, forked f…
Log
Files
Refs
README
---
commit 1d11bb69a4d816c751af34dc79b60a4245afd3dc
parent b99f2cf35da070c8d767310bf30a24808bf788e4
Author: HD Moore <[email protected]>
Date: Sun, 1 Mar 2009 21:31:55 +0000
First push of the new code base
Diffstat:
A Makefile | 18 ++++++++++++++++++
A README | 21 +++++++++++++++++++++
M bin/automatch.rb | 3 +++
A bin/warvox.rb | 46 +++++++++++++++++++++++++++++…
A etc/warvox.conf | 28 ++++++++++++++++++++++++++++
M lib/warvox.rb | 15 ++++++++++++---
A lib/warvox/config.rb | 68 +++++++++++++++++++++++++++++…
M lib/warvox/db.rb | 2 +-
A lib/warvox/jobs.rb | 60 +++++++++++++++++++++++++++++…
A lib/warvox/jobs/analysis.rb | 178 +++++++++++++++++++++++++++++…
A lib/warvox/jobs/base.rb | 24 ++++++++++++++++++++++++
A lib/warvox/jobs/dialer.rb | 206 +++++++++++++++++++++++++++++…
A lib/warvox/phone.rb | 32 +++++++++++++++++++++++++++++…
M src/iaxrecord/iaxrecord.c | 10 +++++++++-
14 files changed, 706 insertions(+), 5 deletions(-)
---
diff --git a/Makefile b/Makefile
@@ -0,0 +1,18 @@
+all: install
+
+install: iaxrecord ruby-kissfft
+ cp -a src/iaxrecord/iaxrecord bin/
+ cp -a src/ruby-kissfft/kissfft.so lib/
+
+iaxrecord:
+ make -C src/iaxrecord/
+
+ruby-kissfft:
+ ( cd src/ruby-kissfft/; ruby extconf.rb )
+ make -C src/ruby-kissfft/
+
+clean:
+ ( cd src/ruby-kissfft/; ruby extconf.rb )
+ make -C src/ruby-kissfft/ clean
+ make -C src/iaxrecord/ clean
+ rm -f bin/iaxrecord lib/kissfft.so
diff --git a/README b/README
@@ -0,0 +1,21 @@
+WarVOX Quick Start Guide
+========================
+
+1. Install all pre-requisites
+ Ubuntu:
+ $ sudo apt-get install libiaxclient-dev sox lame ruby gnuplot
+
+2. Build the WarVOX tools and modules
+ $ make install
+
+3. Change the admin user/pass in etc/warvox.conf
+
+4. Start the WarVOX interface
+ $ bin/warvox.rb
+
+5. Access the interface in a web browser
+ $ firefox http://127.0.0.1:7777/
+
+6. Configure an IAX-capable VoIP provider
+
+7. Create a a new job
diff --git a/bin/automatch.rb b/bin/automatch.rb
@@ -42,6 +42,9 @@ oset = wdb.keys.sort
iset = oset.dup
+$stdout.puts car.keys.map{|x| "#{x}-100" }.join(" ")
+$stdout.flush
+
while(not oset.empty?)
s = Time.now
diff --git a/bin/warvox.rb b/bin/warvox.rb
@@ -0,0 +1,46 @@
+#!/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
+
+voxroot = File.join(File.dirname(base), '..', 'web')
+Dir.chdir(voxroot)
+
+voxserv = File.join('script', 'server')
+
+opts =
+{
+ 'ServerPort' => 7777,
+ 'ServerHost' => '127.0.0.1',
+ 'Background' => false,
+}
+
+
+# Clear ARGV
+while(ARGV.length > 0)
+ ARGV.shift
+end
+
+# Rebuild ARGV
+[
+ '-p', opts['ServerPort'].to_s,
+ '-b', opts['ServerHost'],
+ '-e', 'production',
+ (opts['Background'] ? '-d' : '')
+].each do |arg|
+ ARGV.push arg
+end
+
+$browser_url = "http://#{opts['ServerHost']}:#{opts['ServerPort']}/"
+
+$stderr.puts ""
+$stderr.puts "[*] Starting WarVOX on #{$browser_url}"
+$stderr.puts ""
+
+load(voxserv)
diff --git a/etc/warvox.conf b/etc/warvox.conf
@@ -0,0 +1,28 @@
+#
+# WarVOX Configuration
+#
+
+
+#
+# Configure the username and password for the WarVOX
+# web interface. This password is sent in clear text
+#
+authentication:
+ user: admin
+ pass: warvox
+
+#
+# Configure the path to all saved data files
+# This requires ~500M of space per prefix
+#
+data_path: %BASE%/data/
+
+#
+# Configure filesystem paths to each required tool
+#
+tools:
+ gnuplot: gnuplot
+ sox: sox
+ lame: lame
+ iaxrecord: %BASE%/bin/iaxrecord
+
diff --git a/lib/warvox.rb b/lib/warvox.rb
@@ -2,8 +2,17 @@
# top level include file for warvox libaries
##
-module WarVOX
-end
-
+# Load components
+require 'warvox/config'
+require 'warvox/jobs'
+require 'warvox/phone'
require 'warvox/audio'
require 'warvox/db'
+
+# Global configuration
+module WarVOX
+ VERSION = '1.0.0'
+ Base = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ Conf = File.expand_path(File.join(Base, 'etc', 'warvox.conf'))
+ JobManager = WarVOX::JobQueue.new
+end
diff --git a/lib/warvox/config.rb b/lib/warvox/config.rb
@@ -0,0 +1,68 @@
+module WarVOX
+module Config
+ require 'yaml'
+
+ def self.authentication_creds
+ user = nil
+ pass = nil
+ info = YAML.load_file(WarVOX::Conf)
+ if( info and
+ info['authentication'] and
+ info['authentication']['user'] and
+ info['authentication']['pass']
+ )
+ user = info['authentication']['user']
+ pass = info['authentication']['pass']
+ end
+ [user,pass]
+ end
+
+ def self.authenticate(user,pass)
+ wuser,wpass = authentication_creds
+ (wuser == user and wpass == pass) ? true : false
+ end
+
+ def self.tool_path(name)
+ info = YAML.load_file(WarVOX::Conf)
+ return nil if not info
+ return nil if not info['tools']
+ return nil if not info['tools'][name]
+ find_full_path(
+ info['tools'][name].gsub('%BASE%', WarVOX::Base)
+ )
+ end
+
+ def self.data_path
+ info = YAML.load_file(WarVOX::Conf)
+ return nil if not info
+ return nil if not info['data_path']
+ File.expand_path(info['data_path'].gsub('%BASE%', WarVOX::Base…
+ end
+
+ # This method searches the PATH environment variable for
+ # a fully qualified path to the supplied file name.
+ # Stolen from Rex
+ def self.find_full_path(file_name)
+
+ # Return absolute paths unmodified
+ if(file_name[0,1] == ::File::SEPARATOR)
+ return file_name
+ end
+
+ path = ENV['PATH']
+ if (path)
+ path.split(::File::PATH_SEPARATOR).each { |base|
+ begin
+ path = base + ::File::SEPARATOR + file…
+ if (::File::Stat.new(path))
+ return path
+ end
+ rescue
+ end
+ }
+ end
+ return nil
+ end
+
+end
+end
diff --git a/lib/warvox/db.rb b/lib/warvox/db.rb
@@ -137,7 +137,7 @@ class DB < ::Hash
tone << rec
end
- (tone.empty? or tone.length == 1) ? false : tone
+ (tone.empty? or (data.length > 5 and tone.length == 1)) ? fals…
end
def find_carriers
diff --git a/lib/warvox/jobs.rb b/lib/warvox/jobs.rb
@@ -0,0 +1,60 @@
+module WarVOX
+class JobQueue
+ attr_accessor :active_job, :active_thread, :queue, :queue_thread
+
+ def initialize
+ @queue = []
+ @queue_thread = Thread.new{ manage_queue }
+ super
+ end
+
+ # XXX synchronize
+ def deschedule(job_id)
+
+ if(@active_job and @active_job.name == job_id)
+ @active_thread.kill
+ @active_job = @active_thread = nil
+ end
+
+ res = []
+ @queue.each do |j|
+ res << j if j.name == job_id
+ end
+
+ if(res.length > 0)
+ res.each {|j| @queue.delete(j) }
+ end
+ end
+
+ def schedule(job)
+ @queue.push(job)
+ end
+
+ def manage_queue
+ begin
+ while(true)
+ if(@active_job and @active_job.status == 'completed')
+ @active_job = nil
+ @active_thread = nil
+ end
+
+ if(not @active_job and @queue.length > 0)
+ @active_job = @queue.shift
+ @active_thread = Thread.new { @active_job.star…
+ end
+
+ Kernel.select(nil, nil, nil, 1)
+ end
+ rescue ::Exception
+ $stderr.puts "QUEUE MANAGER:#{$!.class} #{$!}"
+ $stderr.flush
+ end
+ end
+
+end
+end
+
+
+require 'warvox/jobs/base'
+require 'warvox/jobs/dialer'
+require 'warvox/jobs/analysis'
diff --git a/lib/warvox/jobs/analysis.rb b/lib/warvox/jobs/analysis.rb
@@ -0,0 +1,178 @@
+module WarVOX
+module Jobs
+class Analysis < Base
+
+ require 'fileutils'
+ require 'kissfft'
+
+ def type
+ 'analysis'
+ end
+
+ def initialize(job_id)
+ @name = job_id
+ end
+
+ def get_job
+ ::DialJob.find(@name)
+ end
+
+ def start
+ @status = 'active'
+
+ begin
+ start_processing()
+
+ model = get_job
+ model.processed = true
+ model.save
+
+ stop()
+
+ rescue ::Exception => e
+ $stderr.puts "Exception in the job queue: #{e.class} #…
+ end
+ end
+
+ def stop
+ @status = 'completed'
+ end
+
+ def start_processing
+ todo = ::DialResult.find_all_by_dial_job_id(@name)
+ todo.each do |r|
+ next if r.processed
+ next if not r.completed
+ next if r.busy
+ next if not File.exist?(r.rawfile)
+
+ bname = r.rawfile.gsub(/\..*/, '')
+ num = r.number
+
+ #
+ # Create the signature database
+ #
+ raw = WarVOX::Audio::Raw.from_file(r.rawfile)
+ fd = File.new("#{bname}.sig", "wb")
+ fd.write raw.to_flow
+ fd.close
+
+ #
+ # Create a raw decompressed file
+ #
+
+ # Decompress the audio file
+ rawfile = Tempfile.new("rawfile")
+ datfile = Tempfile.new("datfile") …
+
+ # Data files for audio processing and signal graph
+ cnt = 0
+ rawfile.write(raw.samples.pack('v*'))
+ datfile.write(raw.samples.map{|val| cnt +=1; "#{cnt/80…
+ rawfile.flush
+ datfile.flush
+
+ # Data files for spectrum plotting
+ frefile = Tempfile.new("frefile")
+
+ # Perform a DFT on the samples
+ res = KissFFT.fftr(8192, 8000, 1, raw.samples)
+
+ # Calculate the peak frequencies for the sample
+ maxf = 0
+ maxp = 0
+ tones = {}
+ res.each do |x|
+ rank = x.sort{|a,b| a[1].to_i <=> b[1].to_i }.…
+ rank[0..10].each do |t|
+ f = t[0].round
+ p = t[1].round
+ next if f == 0
+ next if p < 1
+ tones[ f ] ||= []
+ tones[ f ] << t
+ if(t[1] > maxp)
+ maxf = t[0]
+ maxp = t[1]
+ end
+ end
+ end
+
+ # Calculate average frequency and peaks over time
+ avg = {}
+ pks = []
+ res.each do |slot|
+ pks << slot.sort{|a,b| a[1] <=> b[1] }.reverse…
+ slot.each do |freq|
+ avg[ freq[0] ] ||= 0
+ avg[ freq[0] ] += freq[1]
+ end
+ end
+ avg.keys.sort.each do |k|
+ avg[k] = avg[k] / res.length
+ frefile.write("#{k} #{avg[k]}\n")
+ end
+ frefile.flush
+
+ # XXX: Store all frequency information
+ # maxf == peak frequency
+ # avg == averages over whole sample
+ # pks == peaks over time
+ # tones == significant frequencies
+
+ # Plot samples to a graph
+ plotter = Tempfile.new("gnuplot")
+
+ plotter.puts("set ylabel \"Signal\"")
+ plotter.puts("set xlabel \"Time\"")
+
+ plotter.puts("set terminal png medium size 640,480 tra…
+ plotter.puts("set output \"#{bname}_big.png\"")
+ plotter.puts("plot \"#{datfile.path}\" using 1:2 title…
+ plotter.puts("set output \"#{bname}_big_dots.png\"")
+ plotter.puts("plot \"#{datfile.path}\" using 1:2 title…
+
+ plotter.puts("set terminal png medium size 640,480 tra…
+ plotter.puts("set ylabel \"Power\"")
+ plotter.puts("set xlabel \"Frequency\"")
+ plotter.puts("set output \"#{bname}_freq_big.png\"")
+ plotter.puts("plot \"#{frefile.path}\" using 1:2 title…
+
+ plotter.puts("set terminal png small size 160,120 tran…
+ plotter.puts("set format x ''")
+ plotter.puts("set format y ''")
+ plotter.puts("set output \"#{bname}.png\"")
+ plotter.puts("plot \"#{datfile.path}\" using 1:2 notit…
+
+ plotter.puts("set terminal png small size 160,120 tran…
+ plotter.puts("set format x ''")
+ plotter.puts("set format y ''")
+ plotter.puts("set output \"#{bname}_freq.png\"")
+ plotter.puts("plot \"#{frefile.path}\" using 1:2 notit…
+ plotter.flush
+
+ system("gnuplot #{plotter.path}")
+ File.unlink(plotter.path)
+ File.unlink(datfile.path)
+ plotter.close
+ datfile.close
+
+ # Generate a MP3 audio file
+ system("sox -s -w -r 8000 -t raw -c 1 #{rawfile.path} …
+ system("lame #{bname}.wav #{bname}.mp3 >/dev/null 2>&1…
+ File.unlink("#{bname}.wav")
+ File.unlink(rawfile.path)
+ rawfile.close
+
+ # XXX: Dump the frequencies
+
+ # Save the changes
+ r.processed = true
+ r.processed_at = Time.now
+ r.save
+ end
+ end
+
+end
+end
+end
diff --git a/lib/warvox/jobs/base.rb b/lib/warvox/jobs/base.rb
@@ -0,0 +1,24 @@
+module WarVOX
+module Jobs
+class Base
+ attr_accessor :name, :status
+
+ def type
+ 'base'
+ end
+
+ def name
+ 'noname'
+ end
+
+ def stop
+ @status = 'active'
+ end
+
+ def start
+ @status = 'completed'
+ end
+end
+end
+end
+
diff --git a/lib/warvox/jobs/dialer.rb b/lib/warvox/jobs/dialer.rb
@@ -0,0 +1,206 @@
+module WarVOX
+module Jobs
+class Dialer < Base
+
+ require 'fileutils'
+
+ def type
+ 'dialer'
+ end
+
+ def initialize(job_id)
+ @name = job_id
+ model = get_job
+ @range = model.range
+ @seconds = model.seconds
+ @lines = model.lines
+ @nums = shuffle_a(WarVOX::Phone.crack_mask(@range))
+ @cid = '8005551212' # XXX: Read from job
+ end
+
+ #
+ # Performs a Fisher-Yates shuffle on an array
+ #
+ def shuffle_a(arr)
+ len = arr.length
+ max = len - 1
+ cyc = [* (0..max) ]
+ for d in cyc
+ e = rand(d+1)
+ next if e == d
+ f = arr[d];
+ g = arr[e];
+ arr[d] = g;
+ arr[e] = f;
+ end
+ return arr
+ end
+
+ def get_providers
+ res = []
+
+ ::Provider.find(:all).each do |prov|
+ info = {
+ :name => prov.name,
+ :id => prov.id,
+ :port => prov.port,
+ :host => prov.host,
+ :user => prov.user,
+ :pass => prov.pass,
+ :lines => prov.lines
+ }
+ 1.upto(prov.lines) {|i| res.push(info) }
+ end
+
+ shuffle_a(res)
+ end
+
+ def get_job
+ ::DialJob.find(@name)
+ end
+
+ def start
+ begin
+
+ model = get_job
+ model.status = 'active'
+ model.started_at = Time.now
+ model.save
+
+ start_dialing()
+
+ stop()
+
+ rescue ::Exception => e
+ $stderr.puts "Exception in the job queue: #{$e.class} …
+ end
+ end
+
+ def stop
+ @status = 'completed'
+ model = get_job
+ model.status = 'completed'
+ model.completed_at = Time.now
+ model.save
+ end
+
+ def start_dialing
+ dest = File.join(WarVOX::Config.data_path, "#{@name}-#{@range}…
+ FileUtils.mkdir_p(dest)
+
+ @nums_total = @nums.length
+ while(@nums.length > 0)
+ @calls = []
+ @provs = get_providers
+ tasks = []
+ max_tasks = [@provs.length, @lines].min
+
+ 1.upto(max_tasks) do
+ tasks << Thread.new do
+
+ Thread.current.kill if @nums.length ==…
+ Thread.current.kill if @provs.length =…
+
+ num = @nums.shift
+ prov = @provs.shift
+
+ Thread.current.kill if not num
+ Thread.current.kill if not prov
+
+ out = File.join(dest, num+".raw")
+
+ begin
+ # Execute and read the output
+ busy = 0
+ ring = 0
+ fail = 1
+ byte = 0
+ path = ''
+
+ IO.popen(
+ [
+ WarVOX::Config.tool_pa…
+ prov[:host],
+ prov[:user],
+ prov[:pass],
+ @cid,
+ out,
+ num,
+ @seconds
+ ].map{|i|
+ "'" + i.to_s.gsub("'",…
+ }.join(" ")).each_line do |line|
+ $stderr.puts "DEBUG: #{line.st…
+ if(line =~ /^COMPLETED/)
+ line.split(/\s+/).map{…
+ busy = info[1]…
+ fail = info[1]…
+ ring = info[1]…
+ byte = info[1]…
+ path = info[1]…
+ end
+ end
+ end
+
+ res = ::DialResult.new
+ res.number = num
+ res.dial_job_id = @name
+ res.provider_id = prov[:id]
+ res.completed = (fail == 0) ? true : f…
+ res.busy = (busy == 1) ? true : false
+ res.seconds = (byte / 16000) # 8khz @…
+ res.ringtime = ring
+ res.processed = false
+ res.created_at = Time.now
+ res.updated_at = Time.now
+
+ if(File.exists?(out))
+ system("gzip -9 #{out}")
+ res.rawfile = out + ".gz"
+ end
+
+ @calls << res
+
+ rescue ::Exception => e
+ $stderr.puts "ERROR: #{e.class…
+ end
+ end
+
+ # END NEW THREAD
+ end
+ # END SPAWN THREADS
+ tasks.map{|t| t.join if t}
+
+ # Save data to the database
+ begin
+
+ # Iterate through the results
+ @calls.each do |r|
+ tries = 0
+ begin
+ r.save
+ rescue ::Exception => e
+ $stderr.puts "ERROR: #{r.inspe…
+ tries += 1
+ Kernel.select(nil, nil, nil, 0…
+ retry if tries < 5
+ end
+ end
+
+ # Update the progress bar
+ model = get_job
+ model.progress = ((@nums_total - @nums.length)…
+ model.save
+
+ rescue ::SQLite3::BusyException => e
+ $stderr.puts "ERROR: Database lock hit trying …
+ retry
+ end
+ end
+
+ # ALL DONE
+ end
+
+end
+end
+end
diff --git a/lib/warvox/phone.rb b/lib/warvox/phone.rb
@@ -0,0 +1,32 @@
+module WarVOX
+class Phone
+
+ # Convert 123456XXXX to an array of expanded numbers
+ def self.crack_mask(mask)
+ res = {}
+
+ incdigits = 0
+ mask.each_char do |c|
+ incdigits += 1 if c =~ /^[X#]$/i
+ end
+
+ max = (10**incdigits)-1
+
+ (0..max).each do |num|
+ number = mask.dup # copy the mask
+ numstr = sprintf("%0#{incdigits}d", num) # stringify o…
+ j = 0 # index for numstr
+ for i in 0..number.length-1 do # step through the numb…
+ if number[i].chr =~ /^[X#]$/i
+ number[i] = numstr[j] # replaced maske…
+ j += 1
+ end
+ end
+ res[number] = {}
+ end
+
+ return res.keys.sort
+ end
+
+end
+end
diff --git a/src/iaxrecord/iaxrecord.c b/src/iaxrecord/iaxrecord.c
@@ -26,6 +26,7 @@
int initialized = 0;
int debug = 0;
int busy = 0;
+int fail = 1;
float silence_threshold = 0.0f;
int call_state = 0;
@@ -61,7 +62,9 @@ void usage(char **argv) {
int state_event_callback(struct iaxc_ev_call_state call) {
if(call.state & IAXC_CALL_STATE_BUSY) busy = 1;
+ if(call.state & IAXC_CALL_STATE_COMPLETE) fail = 0;
call_state = call.state;
+
/*
fprintf(stdout, "STATE: ");
if(call.state & IAXC_CALL_STATE_FREE)
@@ -109,6 +112,7 @@ int audio_event_callback( struct iaxc_ev_audio audio) {
int iaxc_callback(iaxc_event e) {
switch(e.type) {
case IAXC_EVENT_TEXT:
+ // fprintf(stdout, "TEXT: %s\n", e.ev.text.message);
return ( debug ? 0 : 1 );
break;
case IAXC_EVENT_STATE:
@@ -200,13 +204,17 @@ int main(int argc, char **argv) {
if(iaxc_first_free_call() == call_id) break;
iaxc_millisleep(250);
}
+ } else {
+ fail = 1;
}
+
if(! etime) time(&etime);
- fprintf(stdout, "COMPLETED %s BYTES=%d FILE=%s BUSY=%d RINGTIME=%d\n",
+ fprintf(stdout, "COMPLETED %s BYTES=%d FILE=%s FAIL=%d BUSY=%d RINGTIM…
iax_num,
call_bytes,
iax_out,
+ fail,
busy,
(unsigned int)(etime) - (unsigned int)(stime)
);
You are viewing proxied material from jay.scot. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.