# TODO - encrypt time
# TODO - noise is image chars
# TODO - newline in value??
# TODO - vertical offset in chars
class Flatulent
Flatulent::VERSION = '0.0.2' unless defined? Flatulent::VERSION
def self.flatulent() Flatulent::VERSION end
def self.libdir() File.expand_path(__FILE__).gsub(%r/\.rb$/, '') end
require 'cgi'
require 'base64'
require 'digest/md5'
begin
require 'rubygems'
rescue LoadError
end
begin
$:.unshift libdir
require 'text/figlet'
require 'crypt/blowfish'
require 'pervasives'
require 'attributes'
ensure
$:.shift
end
class Error < ::StandardError; end
class EncryptionError < Error; end
class TimeBombError < Error; end
singleton_class =
class << self
self
end
singleton_class.module_eval do
attribute('fontdir'){ File.join libdir, 'fontfiles' }
attribute('style'){ Hash[
'white-space' => 'pre,nowrap',
'font-family' => 'monospace',
'font-weight' => 'bold',
'display' => 'table',
'padding' => '11px',
'background' => '#ffc',
'color' => '#00f',
] }
attribute('noise_style'){ Hash[
'color' => '#ccc',
] }
attribute('key'){ default_key }
def valid? keywords = {}
begin
validate! keywords
true
rescue EncryptionError, TimeBombError
false
end
end
# TODO - time should be encrypted too
def validate! keywords = {}
keywords = keywords['flatulent'] if keywords.has_key?('flatulent')
keywords = keywords[:flatulent] if keywords.has_key?(:flatulent)
opts = getopts keywords
captcha = opts['c'] or raise 'no captcha'
string = opts['s'] or raise 'no string'
time = opts['t'] or raise 'no time'
expected = zeroh(decrypt(string))
actual = zeroh(captcha)
raise EncryptionError, "expected #{ expected } got #{ actual }" unless
expected == actual
timebomb = Time.at(Integer(decrypt(time))).utc rescue(raise("bad time #{ time }"))
raise TimeBombError unless Time.now.utc <= timebomb
return actual
end
def zeroh string
string.gsub(%r/[0Oo]/, '0') # ignore diffs between 0/o/O
end
def blowfish
@blowfish ||= Hash.new{|h,k| h[k] = Crypt::Blowfish.new(key)}
end
def munge string
string.strip.downcase
end
def encrypt string
Base64.encode64(blowfish[key].encrypt_string(string.to_s)).chop # kill "\n"
end
def decrypt string
munge(blowfish[key].decrypt_string(Base64.decode64("#{ string }\n")))
end
def getopts options
lambda do |key, *default|
default = default.first
break options[key] if options.has_key?(key)
key = key.to_s
break options[key] if options.has_key?(key)
key = key.to_sym
break options[key] if options.has_key?(key)
break default
end
end
def default_key
#attribute('default_key') do
return @default_key if defined? @default_key
require 'socket'
hostname = Socket.gethostname
maddr = mac_address rescue nil
warn "could not determine mac addresss!" unless maddr
#puts(( Digest::MD5.hexdigest "--#{ hostname }--#{ maddr }--" ))
#Digest::MD5.hexdigest "--#{ hostname }--#{ maddr }--"
#@default_key = "--#{ hostname }--#{ maddr }--"
@default_key = "--#{ hostname }--#{ maddr }--"
end
def mac_address
return @mac_address if defined? @mac_address
re = %r/[^:\-](?:[0-9A-F][0-9A-F][:\-]){5}[0-9A-F][0-9A-F][^:\-]/io
cmds = '/sbin/ifconfig', '/bin/ifconfig', 'ifconfig', 'ipconfig /all'
null = test(?e, '/dev/null') ? '/dev/null' : 'NUL'
lines = nil
cmds.each do |cmd|
stdout = IO.popen("#{ cmd } 2> #{ null }"){|fd| fd.readlines} rescue next
next unless stdout and stdout.size > 0
lines = stdout and break
end
raise "all of #{ cmds.join ' ' } failed" unless lines
candidates = lines.select{|line| line =~ re}
raise 'no mac address candidates' unless candidates.first
candidates.map!{|c| c[re]}
maddr = candidates.first
raise 'no mac address found' unless maddr
maddr.strip!
maddr.instance_eval{ @list = candidates; def list() @list end }
@mac_address = maddr
end
def figlet options = {}
new(options).figlet
end
def element options = {}
new(options).element
end
def form_tags options = {}
new(options).form_tags
end
def form options = {}
new(options).form
end
end
singleton_class.attributes.each{|a| attribute(a){ self.class.send a}}
attribute 'string'
attribute 'size'
attribute 'font'
attribute 'noise'
attribute 'id'
attribute 'action'
attribute 'ttl'
attribute 'figlet'
attribute 'element'
attribute 'form_tags'
attribute 'form'
def initialize arg = {}
if Hash === arg
opt = getopts arg
@size = Integer opt[ 'size', 4 ]
@string = String opt[ 'string', generate_random_string ]
else
opt = getopts Hash.new
@string = String arg
@size = @string.size
end
@font = String opt[ 'font', 'big' ]
@noise = Float opt[ 'noise', 0.05 ]
@id = String opt[ 'id', 'flatulent' ]
@action = String opt[ 'action' ]
@ttl = Integer opt[ 'ttl', 5 * 60 ]
figlet!
element!
form_tags!
form!
end
def generate_random_string
chars = ('A' .. 'Z').to_a + ('1' .. '9').to_a ### zero is too much like o/O
Array.new(@size).map{ chars[rand(chars.size - 1)]}.join
end
def figlet!
spaced = @string.split(%r//).join(' ')
fontfile = File.join fontdir, "#{ @font }.flf"
font = Text::Figlet::Font.new fontfile
typesetter = Text::Figlet::Typesetter.new font
@figlet = typesetter[spaced]
end
def element!
rows = []
rows << (row = [])
chars = @figlet.split %r//
size = chars.size
last = size - 1
drawn = chars.select{|char| char !~ %r/\s/}
chars.each_with_index do |char, idx|
content =
case char
when %r/\n/o
"
"
when %r/\s/o
rand > 0.42 ? " " : " "
when %r/([^\s])/o
$1
end
row << content
rows << (row = []) unless idx == last
end
(@noise * chars.size).ceil.times do
y = rand(rows.size - 1)
x = rand(rows.first.size - 1)
next if rows[y][x] == '
'
char = drawn[ rand(drawn.size) ]
rows[y][x] = "#{ char }"
end
content = rows.join
@element = "
#{ content }" end def css css_for style end def noise_css css_for noise_style end def css_for hash hash.map{|kv| kv.join ':'}.join ';' end def form_tags! n = @string.scan(%r/\w/).size string = @string timebomb = Time.now.utc.to_i + @ttl @form_tags = <<-html #{ element }
Please enter the #{ n } large characters (A-Z, 1-9) shown.
html end alias_method 'to_html', 'form_tags' def encrypt string self.class.encrypt string.to_s end def encrypted self.class.encrypt @string end def munge string self.class.munge string end def form! action = "action='#{ @action }'" @form = <<-html html end def to_html element end def to_s form end def getopts options self.class.getopts options end end def Flatulent(*a, &b) Flatulent.new(*a, &b) end if $0 == __FILE__ #string = rand.to_s #puts Flatulent(string) #Flatulent.validate! :t => string, :e => Flatulent.encrypt(string), :v => (Time.now.utc.to_i + 60) e = Flatulent.encrypt('foobar') p e p Flatulent.decrypt(e) #puts Flatulent.element('foobar') end