karmasphere-client 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,41 @@
1
+ = Karmasphere Ruby Client Library
2
+
3
+ produced by Meng Weng Wong 200606
4
+
5
+ == Usage
6
+
7
+ After installation, run
8
+
9
+ rubykarmaclient --ip4=127.0.0.2
10
+
11
+ You should get back a response from the Karmasphere system
12
+ showing the reputation of that IP address.
13
+
14
+ == Installation
15
+
16
+ The Karmasphere Client has been packaged as a rubygem. As
17
+ it is still in active development, and as the rest of the
18
+ Karmasphere system is still in alpha, this gem has not yet
19
+ been uploaded to RubyForge (20060701).
20
+
21
+ == User Documentation
22
+
23
+ To learn about the Karmasphere system, see http://www.karmasphere.com/userguide/
24
+
25
+ == Developer Documentation
26
+
27
+ If you are an MTA or Anti-Spam Vendor, see http://www.karmasphere.com/devzone/
28
+
29
+ == Bugs
30
+
31
+ See http://rt.karmasphere.com/
32
+
33
+ == History
34
+
35
+ [20060701] Packaged for rake/rubygems by Meng Weng Wong <mengwong@karmasphere.com>.
36
+ [200606] Originally written by Dave Brown <dagbrown>.
37
+
38
+ == License
39
+
40
+ This client software is distributed under the same terms as Ruby.
41
+
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # mengwong@pobox.com 20060629
4
+ # Karmasphere client for Ruby
5
+
6
+ # usage:
7
+ # rubykarmaclient --host=slave.karmasphere.com --ip4=127.0.0.2
8
+
9
+ # ----------------------------------------------------------
10
+ # libraries
11
+ # ----------------------------------------------------------
12
+
13
+ require "socket"
14
+ require "rubygems" # if run from trunk/lib, with ../bin/rubykarmaclient, the dev libs will load instead of the system libs
15
+ require "rubytorrent/bencoding"
16
+ require "karmasphere/client"
17
+ require "karmasphere/query"
18
+ require "karmasphere/response"
19
+
20
+ # ----------------------------------------------------------
21
+ # options processing
22
+ # ----------------------------------------------------------
23
+
24
+ require "getoptlong"
25
+ opts = GetoptLong.new(
26
+ [ "--host" , GetoptLong::REQUIRED_ARGUMENT ],
27
+ [ "--port" , GetoptLong::REQUIRED_ARGUMENT ],
28
+ [ "--composite" , GetoptLong::REQUIRED_ARGUMENT ],
29
+ [ "--combiner" , GetoptLong::REQUIRED_ARGUMENT ],
30
+ [ "--ip4" , GetoptLong::REQUIRED_ARGUMENT ],
31
+ [ "--ip6" , GetoptLong::REQUIRED_ARGUMENT ],
32
+ [ "--domain" , GetoptLong::REQUIRED_ARGUMENT ],
33
+ [ "--url" , GetoptLong::REQUIRED_ARGUMENT ],
34
+ [ "--email" , GetoptLong::REQUIRED_ARGUMENT ],
35
+ [ "--flags" , GetoptLong::REQUIRED_ARGUMENT ],
36
+ [ "--username" , GetoptLong::REQUIRED_ARGUMENT ],
37
+ [ "--password" , GetoptLong::REQUIRED_ARGUMENT ],
38
+ [ "--tcp" , GetoptLong::NO_ARGUMENT ],
39
+ [ "--help" , GetoptLong::NO_ARGUMENT ],
40
+ [ "--single" , GetoptLong::NO_ARGUMENT ],
41
+ [ "--debug", "-d" , GetoptLong::NO_ARGUMENT ]
42
+ );
43
+
44
+ myopts = {};
45
+ opts.each { |opt, arg| myopts[opt] = arg }
46
+
47
+ # ----------------------------------------------------------
48
+ # symbolic constants
49
+ # ----------------------------------------------------------
50
+
51
+ FL_WANTFACTS = 1
52
+ DEFAULT_IP = "127.0.0.2"
53
+ DEFAULT_COMPOSITE = "karmasphere.email-sender"
54
+
55
+ if myopts.has_key? '--help' then
56
+ print "usage: rubykarmaclient
57
+ [ --username=xxx --password=yyy ]
58
+ [ --ip4=127.0.0.2 ]
59
+ [ --domain=example.com ]
60
+ [ --url=http://www.example.com/phishing/ ]
61
+ [ --tcp ]
62
+ [ --composite=karmasphere.email-sender ]
63
+ "
64
+ exit;
65
+ end
66
+
67
+
68
+ # ----------------------------------------------------------
69
+ # data preparation
70
+ # ----------------------------------------------------------
71
+
72
+ identities = []
73
+ [ "--ip4", "--ip6", "--domain", "--url", "--email" ].each do |identifier|
74
+ identities << myopts[identifier] if myopts[identifier];
75
+ end
76
+
77
+ identities << DEFAULT_IP if identities.length == 0;
78
+
79
+ print "identifiers = #{identities}\n" if myopts['--debug'];
80
+
81
+ # ----------------------------------------------------------
82
+ # main
83
+ # ----------------------------------------------------------
84
+
85
+ #
86
+ # set up client
87
+ #
88
+
89
+ clientargs = { }
90
+
91
+ if myopts.has_key? '--tcp' then clientargs[:type] = :CONNECTION_TCP else clientargs[:type] = :CONNECTION_UDP end
92
+
93
+ if myopts.has_key? '--host' then clientargs[:host] = myopts['--host'] end
94
+
95
+ if myopts.has_key? '--username' then clientargs[:principal] = myopts['--username'] end
96
+ if myopts.has_key? '--password' then clientargs[:credentials] = myopts['--password'] end
97
+
98
+ client=Karmasphere::Client.new(clientargs)
99
+
100
+ #
101
+ # set up query
102
+ #
103
+
104
+ query_id = myopts['--id'] || "000";
105
+
106
+ queryargs = { :Id => query_id,
107
+ :Identities => identities }
108
+
109
+ if myopts.has_key? '--flags' then
110
+ queryargs[:Flags] = myopts['--flags']
111
+ else
112
+ queryargs[:Flags] = FL_WANTFACTS
113
+ end
114
+
115
+ if myopts.has_key? '--composite' then
116
+ queryargs[:Composites] = myopts['--composite'].split(',')
117
+ else
118
+ queryargs[:Composites] = [ DEFAULT_COMPOSITE ]
119
+ end
120
+
121
+ print "query args: " , queryargs.inspect, "\n" if myopts['--debug'];
122
+ query = Karmasphere::Query.new( queryargs )
123
+ print query.inspect, "\n" if myopts['--debug'];
124
+
125
+ #
126
+ # display response
127
+ #
128
+
129
+ response = client.ask(query)
130
+
131
+ if myopts['--debug']
132
+ puts response.instance_variable_get('@hash').inspect
133
+ end
134
+
135
+ puts response
@@ -0,0 +1,5 @@
1
+ require "karmasphere/debug"
2
+ require "karmasphere/constants"
3
+ require "karmasphere/client"
4
+ require "karmasphere/query"
5
+ require "karmasphere/response"
@@ -0,0 +1,116 @@
1
+ #------------------------------------------------------------------------
2
+ # The (mostly-complete) Karmasphere Client
3
+ #------------------------------------------------------------------------
4
+ require "socket"
5
+ require "rubytorrent/bencoding"
6
+ require "stringio"
7
+ require "fcntl"
8
+ require "karmasphere/debug"
9
+
10
+ module Karmasphere
11
+ CONNECTION_UDP = :CONNECTION_UDP
12
+ CONNECTION_TCP = :CONNECTION_TCP
13
+
14
+ class CredentialsError < Exception
15
+ end
16
+
17
+ class Client
18
+ private
19
+ def send_query(query)
20
+ if @auth
21
+ querystr=query.to_hash.merge(@auth).to_bencoding
22
+ else
23
+ querystr=query.to_hash.to_bencoding
24
+ end
25
+
26
+ if TCPSocket === @socket then
27
+ @socket.send [querystr.length].pack("N"),0
28
+ end
29
+
30
+ Karmasphere.dprint("sending query string #{querystr}\n");
31
+ @socket.send querystr,0
32
+ Karmasphere.dprint("sent.\n");
33
+ end
34
+
35
+ def find_response(query)
36
+ # (hack, not guaranteed to work) handle out-of-order responses
37
+ loop do
38
+ if @response_pool.has_key? query.id then
39
+ return @response_pool.delete(query.id)
40
+ else
41
+ response=Response.new( decode(receive_response), query )
42
+ @response_pool[response.id] = response;
43
+ end
44
+ end
45
+ end
46
+
47
+ def receive_response
48
+ if TCPSocket === @socket then
49
+ length_s = @socket.recvfrom(4)[0]
50
+ length_n = length_s.unpack("N")[0]
51
+ Karmasphere.dprint("client decoding TCP response of #{length_n} bytes\n");
52
+ @socket.recvfrom(length_n)[0]
53
+ else
54
+ Karmasphere.dprint("client awaiting response from #@socket\n");
55
+ reply, from = @socket.recvfrom(8192)
56
+ Karmasphere.dprint("client response received from #@socket\n");
57
+ reply
58
+ end
59
+ end
60
+
61
+ def decode(querystr)
62
+ RubyTorrent::BStream.new(
63
+ StringIO.new( querystr )
64
+ ).to_a[0]
65
+ end
66
+
67
+ def make_query(query)
68
+ send_query query
69
+ find_response query
70
+ end
71
+
72
+ public
73
+ def initialize(*args)
74
+ host="query.karmasphere.com"
75
+ port=8666
76
+ type=CONNECTION_UDP
77
+ if Hash === args[0] then
78
+ opts = args[0]
79
+ host = opts[:host] if opts.has_key? :host
80
+ port = opts[:port] if opts.has_key? :port
81
+ type = opts[:type] if opts.has_key? :type and
82
+ opts[:type] == CONNECTION_TCP ||
83
+ opts[:type] == CONNECTION_UDP
84
+ @auth = nil
85
+ if opts.has_key? :principal then
86
+ unless opts.has_key? :credentials
87
+ raise CredentialsError, "principal missing credentials"
88
+ end
89
+ end
90
+ if opts.has_key? :credentials then
91
+ unless opts.has_key? :principal
92
+ raise CredentialsError, "credentials missing principal"
93
+ end
94
+ @auth = { "a" => [ opts[:principal], opts[:credentials] ] }
95
+ end
96
+ end
97
+
98
+ if type == CONNECTION_UDP
99
+ @socket = UDPSocket.new
100
+ Karmasphere.dprint "UDP client: attempting to connect to #{host}:#{port}\n"
101
+ @socket.connect host, port
102
+ @socket.fcntl(Fcntl::F_SETFD,1) if defined? Fcntl::F_SETFD
103
+ Karmasphere.dprint "UDP client: created socket #@socket\n"
104
+ else
105
+ Karmasphere.dprint "TCP client: attempting to connect to #{host}:#{port}\n"
106
+ @socket = TCPSocket.new host, port
107
+ end
108
+
109
+ @response_pool={}
110
+ end
111
+
112
+ def ask(query)
113
+ make_query(query)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,18 @@
1
+ #------------------------------------------------------------------------
2
+ # Constants used in the Karmasphere Query class
3
+ #------------------------------------------------------------------------
4
+
5
+ module Karmasphere
6
+ IDT_IP4_ADDRESS = 0
7
+ IDT_IP6_ADDRESS = 1
8
+ IDT_DOMAIN_NAME = 2
9
+ IDT_EMAIL_ADDRESS = 3
10
+ IDT_URL = 4
11
+ IDT_OPAQUE = 5
12
+ AUTHENTIC = "a"
13
+ SMTP_CLIENT_IP = "smtp.client-ip"
14
+ SMTP_ENV_HELO = "smtp.env.helo"
15
+ SMTP_ENV_MAIL_FROM = "smtp.env.mail-from"
16
+ SMTP_ENV_RCPT_TO = "smtp.env.rcpt-to"
17
+ SMTP_HEADER_FROM_ADDRESS = "smtp.header.from.address"
18
+ end
@@ -0,0 +1,22 @@
1
+
2
+ module Karmasphere
3
+
4
+ # if you want debugging turned on,
5
+ # set any one of these environment variables:
6
+ # * KARMADEBUG
7
+ # * RUBYDEBUG
8
+ # or change the following line to read .find(lambda { true })
9
+
10
+ KARMADEBUG = %w{KARMADEBUG RUBYDEBUG}.find(nil) do |debugenv|
11
+ ENV[debugenv] and ENV[debugenv].size and ENV[debugenv] != "0"
12
+ end
13
+
14
+ # use +dprint+ instead of print. it will print to STDERR,
15
+ # but only when debugging is turned on.
16
+
17
+ def dprint(*args)
18
+ STDERR.print("KarmaDebug: ", "%0.6f: " % Time.now, *args) if KARMADEBUG
19
+ end
20
+
21
+ module_function :dprint
22
+ end
@@ -0,0 +1,92 @@
1
+ #------------------------------------------------------------------------
2
+ # Karmasphere Query class (translation of the Perl code)
3
+ #------------------------------------------------------------------------
4
+
5
+ require "karmasphere/constants"
6
+
7
+ module Karmasphere
8
+ class Query
9
+ @@ID=0
10
+ Realflag = {
11
+ :Id => "_",
12
+ :Identities => "i",
13
+ :Composites => "s",
14
+ :Combiners => "c",
15
+ :Flags => "fl"
16
+ }
17
+
18
+ private
19
+ def guess_identity_type(addr)
20
+ case addr
21
+ when /^[0-9\.]{7,15}$/: IDT_IP4_ADDRESS
22
+ when /^[0-9a-f:\/]{2,64}$/i: IDT_IP6_ADDRESS
23
+ when /^https?:\/\//: IDT_URL
24
+ when /@/: IDT_EMAIL_ADDRESS
25
+ when /\./: IDT_DOMAIN_NAME
26
+ else IDT_OPAQUE
27
+ end
28
+ end
29
+
30
+ public
31
+
32
+ def initialize(*args)
33
+ self.id = "q#{@@ID += 1}"
34
+
35
+ if args.length > 0
36
+ opts = args[0] if Hash === args[0]
37
+
38
+ [:Id, :Identities, :Composites, :Composite, :Feeds,
39
+ :Combiners, :Combiner, :Flags].each do |option|
40
+
41
+ if opts.has_key? option
42
+ send("#{option.to_s.downcase}=", opts[option])
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ attr_accessor :id, :composites, :feeds, :combiners
49
+
50
+ attr_writer :flags
51
+ def flags
52
+ @flags.to_i
53
+ end
54
+
55
+ def composite=(newcomposite)
56
+ self.composites=newcomposite
57
+ end
58
+
59
+ def feed=(newfeed)
60
+ @feeds=[newfeed]
61
+ end
62
+
63
+ def identities=(stuff)
64
+ if String === stuff then
65
+ return self.identities=[stuff]
66
+ end
67
+
68
+ @identities = if Array === stuff then
69
+ stuff.map do |item|
70
+ if Array === item then
71
+ item
72
+ else
73
+ [item, guess_identity_type(item)]
74
+ end
75
+ end
76
+ end
77
+ end
78
+ attr_reader :identities
79
+ alias identity= identities=
80
+
81
+ def to_hash
82
+ retval={}
83
+ [:Id, :Identities, :Composites,
84
+ :Feeds, :Combiners, :Flags].each do |k|
85
+ if instance_variables.member? "@#{k.to_s.downcase}"
86
+ retval[Realflag[k]]=send "#{k.to_s.downcase}"
87
+ end
88
+ end
89
+ retval
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,116 @@
1
+ #------------------------------------------------------------------------
2
+ # Desired Output Format
3
+ #------------------------------------------------------------------------
4
+
5
+ # the Java, Perl and C clients all produce the same output format.
6
+ #
7
+ # 20060907-15:53:55 mengwong@newyears-air:~/src/karmasphere/trunk/lib% karmaclient --ip4=127.0.0.2 --host=bowie.tx.karmasphere.com
8
+ # Response id 'q0': 12ms, 1 combinations, 8 facts
9
+ # Combiner 'karmasphere.emailchecker': verdict 1000 (isipp.iadb-vouched-ip: if-pass(0) => return good(1.0))
10
+ # Feed 'sorbs.dul' opinion -1000 (Dynamic IP Addresses See: http://www.sorbs.net/lookup.shtml?$)
11
+ # Feed 'spamhaus.xbl-cbl' opinion -1000 (http://www.spamhaus.org/query/bl?ip=$)
12
+ # Feed 'cymru.bogons' opinion -1000 (Invalid source IP address (cymru))
13
+ # Feed 'dsbl.list' opinion -1000 (http://dsbl.org/listing?$)
14
+ # Feed 'dsbl.multihop' opinion -1000 (http://dsbl.org/listing?$)
15
+ # Feed 'spamcop.bl' opinion 2147483647 (null data)
16
+ # Feed 'isipp.iadb-vouched-ip' opinion 1000 (Vouched by ISIPP/IADB)
17
+ # Feed 'spamhaus.sbl' opinion -1000 (http://www.spamhaus.org/query/bl?ip=$)
18
+ # 20060907-15:54:01 mengwong@newyears-air:~/src/karmasphere/trunk/lib%
19
+
20
+ #------------------------------------------------------------------------
21
+ # Response class for the Karmasphere client
22
+ #------------------------------------------------------------------------
23
+
24
+ module Karmasphere
25
+ class Response
26
+ private
27
+ def combination_get(key, combiner=nil)
28
+ if combinations then
29
+ if !combiner and combinations.has_key?("default") then
30
+ combination('default')
31
+ elsif !combiner then
32
+ combinations.values.find do |combo|
33
+ if combo.has_key? key then combo[key] end
34
+ end
35
+ else
36
+ my_combination=combination(combiner)
37
+ if my_combination then
38
+ return my_combination[key]
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ public
45
+
46
+ def initialize(hash, query=nil)
47
+ @hash=hash
48
+ @query=query
49
+ end
50
+
51
+ def id; @hash["_"] end
52
+
53
+ def time; @hash["time"] if @hash.has_key? "time" end
54
+
55
+ def facts
56
+ if @hash.has_key? "facts" then
57
+ @hash["facts"]
58
+ elsif @hash.has_key? "f" then
59
+ @hash["f"]
60
+ else
61
+ []
62
+ end
63
+ end
64
+
65
+ def combinations
66
+ if @hash.has_key? "c" then
67
+ @hash["c"]
68
+ elsif @hash.has_key? "combiners" then
69
+ @hash["combiners"]
70
+ end
71
+ end
72
+
73
+ def combination(combiner)
74
+ combinations[combiner]
75
+ end
76
+
77
+ def combiner_names
78
+ if combinations then
79
+ combinations.keys
80
+ else
81
+ []
82
+ end
83
+ end
84
+
85
+ def verdict(name=nil); combination_get "v",name; end
86
+ alias value verdict
87
+ def data(name=nil); combination_get "d", name; end
88
+
89
+ def error; @hash["error"] if @hash.has_key? "error"; end
90
+ def message; @hash["message"] if @hash.has_key? "message"; end
91
+
92
+ def to_s;
93
+ "Response_id '#{id}': " +
94
+ if time then "#{time}ms, " else "" end +
95
+ "#{combiner_names.length} combinations, " +
96
+ "#{facts.length} facts\n" +
97
+ if error then
98
+ "Error: #{message}\n"
99
+ else
100
+ combiner_names.map do |name|
101
+ "Combiner '#{name}': verdict #{value(name)} " +
102
+ "(#{data(name) || '(undef)'})\n"
103
+ end.join("") +
104
+ facts.map do |fact|
105
+ "Feed '#{fact['f']}' opinion #{fact['v']} (" +
106
+ if fact.has_key? "d" then
107
+ fact["d"]
108
+ else
109
+ "null data"
110
+ end + ")" +
111
+ if fact.has_key? "i" then " (identity=#{fact['i']})" else "" end + "\n"
112
+ end.join("")
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,182 @@
1
+ ## bencoding.rb -- parse and generate bencoded values.
2
+ ## Copyright 2004 William Morgan.
3
+ ##
4
+ ## This file is part of RubyTorrent. RubyTorrent is free software;
5
+ ## you can redistribute it and/or modify it under the terms of version
6
+ ## 2 of the GNU General Public License as published by the Free
7
+ ## Software Foundation.
8
+ ##
9
+ ## RubyTorrent is distributed in the hope that it will be useful, but
10
+ ## WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ ## General Public License (in the file COPYING) for more details.
13
+
14
+ require 'uri'
15
+ require 'digest/sha1'
16
+
17
+ module RubyTorrent
18
+
19
+ ## we mess in the users' namespaces in this file. there's no good way
20
+ ## around it. i don't think it's too egregious though.
21
+
22
+ class BEncodingError < StandardError; end
23
+
24
+ class BStream
25
+ include Enumerable
26
+
27
+ @@classes = []
28
+
29
+ def initialize(s)
30
+ @s = s
31
+ end
32
+
33
+ def self.register_bencoded_class(c)
34
+ @@classes.push c
35
+ end
36
+
37
+ def each
38
+ happy = true
39
+ begin
40
+ happy = false
41
+ c = @s.getc
42
+ @@classes.each do |klass|
43
+ if klass.bencoded? c
44
+ o = klass.parse_bencoding(c, @s)
45
+ happy = true
46
+ yield o
47
+ break
48
+ end
49
+ end unless c.nil?
50
+ unless happy
51
+ @s.ungetc c unless c.nil?
52
+ end
53
+ end while happy
54
+ self
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ class String
61
+ def to_bencoding
62
+ self.length.to_s + ":" + self.to_s
63
+ end
64
+
65
+ def self.bencoded?(c)
66
+ (?0 .. ?9).include? c
67
+ end
68
+
69
+ def self.parse_bencoding(c, s)
70
+ lens = c.chr
71
+ while ((x = s.getc) != ?:)
72
+ unless (?0 .. ?9).include? x
73
+ s.ungetc x
74
+ raise RubyTorrent::BEncodingError, "invalid bencoded string length #{lens} + #{x}"
75
+ end
76
+ lens += x.chr
77
+ end
78
+ raise RubyTorrent::BEncodingError, %{invalid length #{lens} in bencoded string} unless lens.length <= 20
79
+ len = lens.to_i
80
+ raise RubyTorrent::BEncodingError, %{invalid length #{lens} in bencoded string} unless len >= 0
81
+ (len > 0 ? s.read(len) : "")
82
+ end
83
+
84
+ RubyTorrent::BStream.register_bencoded_class self
85
+ end
86
+
87
+ class Integer
88
+ def to_bencoding
89
+ "i" + self.to_s + "e"
90
+ end
91
+
92
+ def self.bencoded?(c)
93
+ c == ?i
94
+ end
95
+
96
+ def self.parse_bencoding(c, s)
97
+ ints = ""
98
+ while ((x = s.getc.chr) != 'e')
99
+ raise RubyTorrent::BEncodingError, "invalid bencoded integer #{x.inspect}" unless x =~ /\d|-/
100
+ ints += x
101
+ end
102
+ raise RubyTorrent::BEncodingError, "invalid integer #{ints} (too long)" unless ints.length <= 20
103
+ int = ints.to_i
104
+ raise RubyTorrent::BEncodingError, %{can't parse bencoded integer "#{ints}"} if (int == 0) && (ints !~ /^0$/) #'
105
+ int
106
+ end
107
+
108
+ RubyTorrent::BStream.register_bencoded_class self
109
+ end
110
+
111
+ class Time
112
+ def to_bencoding
113
+ self.to_i.to_bencoding
114
+ end
115
+ end
116
+
117
+ module URI
118
+ def to_bencoding
119
+ self.to_s.to_bencoding
120
+ end
121
+ end
122
+
123
+ # Symbol#to_bencoding added by dagbrown 2006/6/3 for karmaclient
124
+
125
+ class Symbol
126
+ def to_bencoding
127
+ to_s.bencoding
128
+ end
129
+ end
130
+
131
+ class Array
132
+ def to_bencoding
133
+ "l" + self.map { |e| e.to_bencoding }.join + "e"
134
+ end
135
+
136
+ def self.bencoded?(c)
137
+ c == ?l
138
+ end
139
+
140
+ def self.parse_bencoding(c, s)
141
+ ret = RubyTorrent::BStream.new(s).map { |x| x }
142
+ raise RubyTorrent::BEncodingError, "missing list terminator" unless s.getc == ?e
143
+ ret
144
+ end
145
+
146
+ RubyTorrent::BStream.register_bencoded_class self
147
+ end
148
+
149
+ class Hash
150
+ def to_bencoding
151
+ "d" + keys.sort.map do |k|
152
+ v = self[k]
153
+ if v.nil?
154
+ nil
155
+ else
156
+ [k.to_bencoding, v.to_bencoding].join
157
+ end
158
+ end.compact.join + "e"
159
+ end
160
+
161
+ def self.bencoded?(c)
162
+ c == ?d
163
+ end
164
+
165
+ def self.parse_bencoding(c, s)
166
+ ret = {}
167
+ key = nil
168
+ RubyTorrent::BStream.new(s).each do |x|
169
+ if key == nil
170
+ key = x
171
+ else
172
+ ret[key] = x
173
+ key = nil
174
+ end
175
+ end
176
+
177
+ raise RubyTorrent::BEncodingError, "no dictionary terminator" unless s.getc == ?e
178
+ ret
179
+ end
180
+
181
+ RubyTorrent::BStream.register_bencoded_class self
182
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ $: << File.join(File.dirname(__FILE__), "..","lib")
4
+
5
+ require "test/unit"
6
+ require "test/test_client"
7
+ require "test/test_query"
8
+ require "test/test_response"
@@ -0,0 +1,264 @@
1
+ #------------------------------------------------------------------------
2
+ # Tests for the client
3
+ #------------------------------------------------------------------------
4
+
5
+ require "test/unit"
6
+ require "rubytorrent/bencoding"
7
+ require "socket"
8
+ require "karmasphere/client"
9
+ require "karmasphere/query"
10
+ require "karmasphere/response"
11
+ require "karmasphere/debug"
12
+
13
+ # For test purposes, we create a mini-server which bencodes
14
+ # and transmits this response no matter what input is given.
15
+ # In this test suite -- we're testing the client -- we
16
+ # decode the response from the mini-server and match it
17
+ # against this expected value.
18
+
19
+ $stub_response = {
20
+ "_" => 12345,
21
+ "facts" => [
22
+ {
23
+ "f" => 4000,
24
+ "v" => -1,
25
+ "d" => "Invalid Source IP Address (cymru)"
26
+ }
27
+ ],
28
+ "combiners" => {
29
+ "karmasphere.emailchecker" => {
30
+ "v" => -1000,
31
+ "d" => "<f4000: if-fail(0) => return bad(1.0)>"
32
+ }
33
+ }
34
+ }
35
+
36
+ # We have four test cases in total, exploring each combination of:
37
+ # * Turnaround vs Stub
38
+ # * TCP vs UDP
39
+
40
+ $port = 8666
41
+
42
+ class TC_Client_Turnaround_TCP < Test::Unit::TestCase
43
+ @@port = $port+1
44
+
45
+ def setup
46
+ @thr=Thread.new do
47
+ Karmasphere.dprint("TC_Client_Turnaround_TCP: constructing listen server on port #@@port\n");
48
+ @server = TCPServer.new(nil, @@port)
49
+
50
+ # mengwong 20060701: i believe TCPServers are
51
+ # automatically created with reuseaddr = true,
52
+ # but you may use the following code to check.
53
+ Karmasphere.dprint("created server with #{@server.getsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR).inspect}\n");
54
+
55
+ while(session = @server.accept)
56
+ Thread.new do
57
+ Karmasphere.dprint("accepted new session #{session.inspect}\n");
58
+ len_str = session.recv(4)
59
+ len=len_str.unpack("N")[0]
60
+ info=session.recv(len)
61
+ Karmasphere.dprint("received #{len} bytes of info: #{info}\n");
62
+ session.send len_str+info, 0
63
+ Karmasphere.dprint("echoing back #{len} bytes of info: #{info}\n");
64
+ session.close
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def teardown
71
+ @server.close
72
+ @server = nil
73
+ @thr.kill
74
+ GC.start
75
+ end
76
+
77
+ def test_startup
78
+ Karmasphere.dprint("test_client: test_startup: beginning\n");
79
+
80
+ # we need to wait for the listener thread established in
81
+ # setup to be ready. we do a poor man's thread
82
+ # synchronization by simply testing the @server. if we
83
+ # were doing this right, we'd use a MonitorMixin as
84
+ # documented on page 138 of the pickax book.
85
+ while not @server do
86
+ Karmasphere.dprint("test_startup: server not ready. passing.\n");
87
+ Thread.pass
88
+ end
89
+
90
+ Karmasphere.dprint("test_client: test_startup: proceeding.\n");
91
+
92
+ assert client=Karmasphere::Client.new(:host => "localhost",
93
+ :port => @@port,
94
+ :type => Karmasphere::CONNECTION_TCP)
95
+ query = Karmasphere::Query.new({ "_" => 12345 })
96
+ response = client.ask(query)
97
+ Karmasphere.dprint("test_client: got back response " , response.instance_variable_get("@hash"));
98
+ assert_equal query.to_hash, response.instance_variable_get("@hash")
99
+ end
100
+ end
101
+
102
+ class TC_Client_Turnaround_UDP < Test::Unit::TestCase
103
+ @@port = $port + 2
104
+ def setup
105
+ Karmasphere.dprint("TC_Client_Turnaround_UDP: constructing listen server on port #@@port\n");
106
+ @thr=Thread.new do
107
+
108
+ @server = UDPSocket.new
109
+ @server.bind(nil, @@port)
110
+
111
+ info=@server.recvfrom(8192)
112
+ Karmasphere.dprint("TC_Client_Turnaround_UDP: received ", info, "\n");
113
+
114
+ @server.flush
115
+ client_h=info[1][2]
116
+ client_p=info[1][1]
117
+ query=info[0]
118
+ @server.send query, 0, client_h, client_p
119
+ @server.flush
120
+ Karmasphere.dprint("TC_Client_Turnaround_UDP: sent stuff back to #{client_h}:#{client_p}\n");
121
+
122
+ end
123
+ end
124
+
125
+ def teardown
126
+ Karmasphere.dprint("TC_Client_Turnaround_UDP: tearing down server.\n");
127
+ @server.close
128
+ @server = nil
129
+ @thr.kill
130
+ GC.start
131
+ end
132
+
133
+ def test_startup
134
+ while not @server do
135
+ Karmasphere.dprint("test_startup: server not ready. passing.\n");
136
+ Thread.pass
137
+ end
138
+ assert client=Karmasphere::Client.new(:host => "localhost",
139
+ :port => @@port
140
+ )
141
+ query = Karmasphere::Query.new({ "_" => 12345 })
142
+ response = client.ask(query)
143
+ assert_equal query.to_hash, response.instance_variable_get("@hash")
144
+
145
+ end
146
+
147
+ def test_credentials
148
+ while not @server do
149
+ Karmasphere.dprint("test_startup: server not ready. passing.\n");
150
+ Thread.pass
151
+ end
152
+ assert_raises Karmasphere::CredentialsError do
153
+ Karmasphere::Client.new(:host => "localhost",
154
+ :port => @@port,
155
+ :principal => "foo")
156
+ end
157
+ assert_raises Karmasphere::CredentialsError do
158
+ Karmasphere::Client.new(:host => "localhost",
159
+ :port => @@port,
160
+ :credentials => "bar")
161
+ end
162
+
163
+ assert client=Karmasphere::Client.new(:host => "localhost",
164
+ :port => @@port,
165
+ :principal => "foo",
166
+ :credentials => "bar"
167
+ )
168
+ query = Karmasphere::Query.new({ "_" => 12345 })
169
+ response = client.ask(query)
170
+ assert_equal query.to_hash.merge({"a" => [ "foo", "bar" ]}),
171
+ response.instance_variable_get("@hash")
172
+ end
173
+ end
174
+
175
+ class TC_Client_Stub_UDP < Test::Unit::TestCase
176
+ @@port = $port + 3
177
+ def setup
178
+ @thr=Thread.new do
179
+ Karmasphere.dprint("TC_Client_Stub_UDP: constructing listen server on port #@@port\n");
180
+
181
+ @server = UDPSocket.new
182
+ @server.bind("localhost", @@port)
183
+ Karmasphere.dprint("TC_Client_Stub_UDP: created server #@port\n");
184
+ @server_ready = true
185
+
186
+ info=@server.recvfrom(8192)
187
+ @server.flush
188
+ client_h=info[1][2]
189
+ client_p=info[1][1]
190
+ Karmasphere.dprint("TC_Client_Stub_UDP: server received packet from #{client_h}:#{client_p}\n");
191
+ query=info[0]
192
+ @server.send $stub_response.to_bencoding, 0, client_h, client_p
193
+ Karmasphere.dprint("TC_Client_Stub_UDP: miniserver sent stuff back to #{client_h}:#{client_p}\n");
194
+ Karmasphere.dprint("TC_Client_Stub_UDP: close_write()d.\n");
195
+ end
196
+ end
197
+
198
+ def teardown
199
+ @thr.kill
200
+ @server.close
201
+ @server = nil
202
+ GC.start
203
+ end
204
+
205
+ def test_startup
206
+ while not @server_ready do
207
+ Karmasphere.dprint("Stub_UDP test_startup: server not ready. passing.\n");
208
+ Thread.pass
209
+ end
210
+ Karmasphere.dprint("test_startup: proceeding.\n");
211
+ assert client=Karmasphere::Client.new(:host => "localhost",
212
+ :port => @@port
213
+ )
214
+ query = Karmasphere::Query.new(:Id => 12345)
215
+ Karmasphere.dprint("test_startup: client asking query...\n");
216
+ response = client.ask(query)
217
+ Karmasphere.dprint("test_startup: ... client received response #{response}\n");
218
+ assert_equal $stub_response, response.instance_variable_get('@hash')
219
+ end
220
+ end
221
+
222
+ class TC_Client_Stub_TCP < Test::Unit::TestCase
223
+ @@port = $port + 4
224
+ def setup
225
+ Karmasphere.dprint("TC_Client_Stub_TCP: constructing listen server on port #@@port\n");
226
+ @thr = Thread.new do
227
+
228
+ @server = TCPServer.new(nil, @@port)
229
+ while(session = @server.accept)
230
+ Thread.new do
231
+ while not session.closed?
232
+ len_str = session.recv(4)
233
+ len=len_str.unpack("N")[0]
234
+ info=session.recv(len)
235
+ response_str=$stub_response.to_bencoding
236
+ response_len_str = [ response_str.length ].pack "N"
237
+ session.send response_len_str+response_str, 0
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ def teardown
245
+ @server.close
246
+ @server = nil
247
+ @thr.kill
248
+ GC.start
249
+ end
250
+
251
+ def test_startup
252
+ while not @server do
253
+ Karmasphere.dprint("test_startup: server not ready. passing.\n");
254
+ Thread.pass
255
+ end
256
+ assert client=Karmasphere::Client.new(:host => "localhost",
257
+ :port => @@port,
258
+ :type => Karmasphere::CONNECTION_TCP)
259
+ query = Karmasphere::Query.new(:Id => 12345)
260
+ response = client.ask(query)
261
+ assert_equal $stub_response, response.instance_variable_get('@hash')
262
+ end
263
+ end
264
+
@@ -0,0 +1,120 @@
1
+ require "test/unit"
2
+ require "karmasphere/query"
3
+
4
+ class TC_Basic_Query < Test::Unit::TestCase
5
+ def test_automatic_query_id
6
+ client=Karmasphere::Query.new
7
+ assert_kind_of Karmasphere::Query, client
8
+
9
+ client2=Karmasphere::Query.new
10
+ assert_kind_of Karmasphere::Query, client2
11
+
12
+ assert_equal client.id, "q1"
13
+ assert_equal client.to_hash, { "_" => "q1" }
14
+
15
+ assert_equal client2.id, "q2"
16
+ assert_equal client2.to_hash, { "_" => "q2" }
17
+ end
18
+
19
+ def test_query_identities_munging
20
+ client=Karmasphere::Query.new(:Id => "q2")
21
+ assert_kind_of Karmasphere::Query, client
22
+
23
+ client.identities=[ ["127.0.0.1", 0 ] ]
24
+ assert_equal( [ ["127.0.0.1", 0 ] ], client.identities)
25
+ assert_equal( { "_" => "q2", "i" => [ ["127.0.0.1", 0 ] ] },
26
+ client.to_hash)
27
+
28
+ client.identities = [ "127.0.0.1" ]
29
+ assert_equal( [ ["127.0.0.1", 0 ] ], client.identities)
30
+ assert_equal({ "_" => "q2", "i" => [ ["127.0.0.1", 0 ] ] },
31
+ client.to_hash)
32
+
33
+ client.identities = "127.0.0.1"
34
+ assert_equal( [ ["127.0.0.1", 0 ] ], client.identities)
35
+ assert_equal({ "_" => "q2", "i" => [ ["127.0.0.1", 0 ] ] },
36
+ client.to_hash)
37
+
38
+ client.identity = "127.0.0.1"
39
+ assert_equal( [ ["127.0.0.1", 0 ] ], client.identities)
40
+ assert_equal({ "_" => "q2", "i" => [ ["127.0.0.1", 0 ] ] },
41
+ client.to_hash)
42
+ end
43
+
44
+ def test_query_identities_guessing
45
+ client=Karmasphere::Query.new(:Id => "q3")
46
+ assert_kind_of Karmasphere::Query, client
47
+
48
+ client.identities = "127.0.0.1"
49
+ assert_equal( [ ["127.0.0.1", 0 ] ], client.identities )
50
+ assert_equal({ "_" => "q3", "i" => [ ["127.0.0.1", 0 ] ] },
51
+ client.to_hash)
52
+
53
+ client.identities = "::1/128"
54
+ assert_equal( [ ["::1/128", 1 ] ], client.identities )
55
+ assert_equal({ "_" => "q3", "i" => [ ["::1/128", 1 ] ] },
56
+ client.to_hash)
57
+
58
+ client.identities = "example.com"
59
+ assert_equal( [ ["example.com", 2 ] ], client.identities )
60
+ assert_equal({ "_" => "q3", "i" => [ ["example.com", 2 ] ] },
61
+ client.to_hash)
62
+
63
+ client.identities = "example@example.com"
64
+ assert_equal( [ ["example@example.com", 3 ] ], client.identities )
65
+ assert_equal({ "_" => "q3", "i" => [ ["example@example.com", 3 ] ] },
66
+ client.to_hash)
67
+
68
+ client.identities = "http://www.example.com/"
69
+ assert_equal( [ ["http://www.example.com/", 4 ] ], client.identities )
70
+ assert_equal({ "_" => "q3", "i" => [ ["http://www.example.com/", 4 ] ] },
71
+ client.to_hash)
72
+
73
+ client.identities = "chunky bacon"
74
+ assert_equal( [ ["chunky bacon", 5 ] ], client.identities )
75
+ assert_equal({ "_" => "q3", "i" => [ ["chunky bacon", 5 ] ] },
76
+ client.to_hash)
77
+ end
78
+
79
+ def test_query_identities_on_construction
80
+ client=Karmasphere::Query.new(:Id => 'q4',
81
+ :Identities => "example.com")
82
+ assert_kind_of Karmasphere::Query, client
83
+
84
+ assert_equal( [ ["example.com", 2 ] ], client.identities,
85
+ "Automatically-detected identity at construction time")
86
+ assert_equal({ "_" => "q4", "i" => [ ["example.com", 2 ] ] },
87
+ client.to_hash, "Simple identity at construction time")
88
+
89
+ client = Karmasphere::Query.new( :Id => 'q5',
90
+ :Identities => [ [ "127.0.0.1",
91
+ Karmasphere::IDT_IP4_ADDRESS,
92
+ Karmasphere::AUTHENTIC ] ] )
93
+ assert_equal( [ [ "127.0.0.1", 0, "a" ] ], client.identities )
94
+ assert_equal({ "_" => "q5", "i" => [ ["127.0.0.1", 0, "a" ] ] },
95
+ client.to_hash, "Complicated identity at construction time")
96
+ end
97
+
98
+ def test_query_example_query
99
+ client = Karmasphere::Query.new( :Id => 12345,
100
+ :Identities => [ [ "127.0.0.1", Karmasphere::IDT_IP4_ADDRESS,
101
+ Karmasphere::SMTP_CLIENT_IP ],
102
+ [ "antony@karmasphere.com",
103
+ Karmasphere::IDT_EMAIL_ADDRESS,
104
+ Karmasphere::SMTP_ENV_MAIL_FROM ],
105
+ [ "karmasphere.com",
106
+ Karmasphere::IDT_DOMAIN_NAME,
107
+ Karmasphere::SMTP_ENV_HELO ] ],
108
+ :Composites => "karmasphere.emailchecker",
109
+ :Flags => 1 )
110
+
111
+ assert_kind_of Karmasphere::Query, client
112
+
113
+ assert_equal( { "_" => 12345,
114
+ "i" => [ [ "127.0.0.1", 0, "smtp.client-ip" ],
115
+ [ "antony@karmasphere.com", 3, "smtp.env.mail-from" ],
116
+ [ "karmasphere.com", 2, "smtp.env.helo" ] ],
117
+ "s" => "karmasphere.emailchecker", "fl" => 1 },
118
+ client.to_hash, "Test query from the protocol spec")
119
+ end
120
+ end
@@ -0,0 +1,65 @@
1
+ require "test/unit"
2
+ require "karmasphere/response"
3
+
4
+ $response_hash = {
5
+ "_" => 12345,
6
+ "facts" => [
7
+ {
8
+ "f" => 4000,
9
+ "v" => -1,
10
+ "d" => "Invalid Source IP Address (cymru)"
11
+ }
12
+ ],
13
+ "combiners" => {
14
+ "karmasphere.emailchecker" => {
15
+ "v" => -1000,
16
+ "d" => "<f4000: if-fail(0) => return bad(1.0)>"
17
+ }
18
+ }
19
+ }
20
+
21
+ class TC_BasicResponse < Test::Unit::TestCase
22
+ def test_basicresponse
23
+ response=nil
24
+ assert_nothing_raised {
25
+ response=Karmasphere::Response.new({"_" => 12345})
26
+ }
27
+ assert_kind_of Karmasphere::Response, response
28
+ assert_equal 12345, response.id
29
+ assert_nil response.time
30
+ assert_nil response.combinations
31
+ assert_equal [], response.facts
32
+ assert_equal [], response.combiner_names
33
+ assert_nil response.error
34
+ assert_nil response.message
35
+ end
36
+
37
+ def test_errorresponse
38
+ response=nil
39
+ assert_nothing_raised {
40
+ response=Karmasphere::Response.new({"_" => "q314159",
41
+ "error" => 1,
42
+ "message" => "an error occurred"})
43
+ }
44
+ assert_kind_of Karmasphere::Response, response
45
+ assert_equal "q314159", response.id
46
+ # I don't think I need go through all those assert_nils again
47
+ assert_equal 1, response.error
48
+ assert_equal "an error occurred", response.message
49
+ assert_equal "Response_id 'q314159': 0 combinations, 0 facts\nError: an error occurred\n", response.to_s
50
+ end
51
+
52
+ def test_success
53
+ response=nil
54
+ assert_nothing_raised {
55
+ response=Karmasphere::Response.new $response_hash
56
+ }
57
+ assert_kind_of Karmasphere::Response, response
58
+ assert_equal ["karmasphere.emailchecker"], response.combiner_names
59
+ assert_equal([{
60
+ "f" => 4000,
61
+ "v" => -1,
62
+ "d" => "Invalid Source IP Address (cymru)"
63
+ }], response.facts)
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: karmasphere-client
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.6.4
7
+ date: 2007-04-30 00:00:00 -07:00
8
+ summary: Karmasphere Client
9
+ require_paths:
10
+ - lib
11
+ email: devsupport@karmasphere.com
12
+ homepage: http://www.karmasphere.com.com/devzone/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: karmasphere
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ post_install_message:
30
+ authors:
31
+ - Meng Weng Wong
32
+ files:
33
+ - bin/rubykarmaclient
34
+ - test/test_all.rb
35
+ - test/test_client.rb
36
+ - test/test_query.rb
37
+ - test/test_response.rb
38
+ - lib/karmasphere
39
+ - lib/karmasphere.rb
40
+ - lib/rubytorrent
41
+ - lib/karmasphere/client.rb
42
+ - lib/karmasphere/constants.rb
43
+ - lib/karmasphere/debug.rb
44
+ - lib/karmasphere/query.rb
45
+ - lib/karmasphere/response.rb
46
+ - lib/rubytorrent/bencoding.rb
47
+ - README
48
+ test_files:
49
+ - test/test_all.rb
50
+ rdoc_options: []
51
+ extra_rdoc_files:
52
+ - README
53
+ executables:
54
+ - rubykarmaclient
55
+ extensions: []
56
+ requirements: []
57
+ dependencies: []