karmasphere-client 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []