recog 0.01

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.gitignore +3 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +42 -0
  5. data/LICENSE +23 -0
  6. data/README.md +63 -0
  7. data/bin/recog_export.rb +81 -0
  8. data/bin/recog_match.rb +51 -0
  9. data/bin/recog_verify.rb +45 -0
  10. data/features/match.feature +16 -0
  11. data/features/support/env.rb +5 -0
  12. data/features/verify.feature +31 -0
  13. data/features/xml/banners.xml +2 -0
  14. data/features/xml/failing_banners_fingerprints.xml +20 -0
  15. data/features/xml/matching_banners_fingerprints.xml +22 -0
  16. data/features/xml/no_tests.xml +53 -0
  17. data/features/xml/successful_tests.xml +33 -0
  18. data/features/xml/tests_with_failures.xml +10 -0
  19. data/features/xml/tests_with_warnings.xml +10 -0
  20. data/lib/recog.rb +3 -0
  21. data/lib/recog/db.rb +38 -0
  22. data/lib/recog/db_manager.rb +27 -0
  23. data/lib/recog/fingerprint.rb +60 -0
  24. data/lib/recog/formatter.rb +51 -0
  25. data/lib/recog/match_reporter.rb +77 -0
  26. data/lib/recog/matcher.rb +60 -0
  27. data/lib/recog/matcher_factory.rb +14 -0
  28. data/lib/recog/nizer.rb +263 -0
  29. data/lib/recog/verifier.rb +46 -0
  30. data/lib/recog/verifier_factory.rb +13 -0
  31. data/lib/recog/verify_reporter.rb +85 -0
  32. data/lib/recog/version.rb +3 -0
  33. data/recog.gemspec +34 -0
  34. data/spec/data/best_os_match_1.yml +17 -0
  35. data/spec/data/best_os_match_2.yml +17 -0
  36. data/spec/data/best_service_match_1.yml +17 -0
  37. data/spec/data/smb_native_os.txt +31 -0
  38. data/spec/data/test_fingerprints.xml +24 -0
  39. data/spec/lib/db_spec.rb +89 -0
  40. data/spec/lib/formatter_spec.rb +69 -0
  41. data/spec/lib/match_reporter_spec.rb +90 -0
  42. data/spec/lib/nizer_spec.rb +124 -0
  43. data/spec/lib/verify_reporter_spec.rb +112 -0
  44. data/xml/apache_os.xml +295 -0
  45. data/xml/architecture.xml +45 -0
  46. data/xml/ftp_banners.xml +808 -0
  47. data/xml/h323_callresp.xml +701 -0
  48. data/xml/hp_pjl_id.xml +435 -0
  49. data/xml/http_cookies.xml +379 -0
  50. data/xml/http_servers.xml +3326 -0
  51. data/xml/http_wwwauth.xml +412 -0
  52. data/xml/imap_banners.xml +267 -0
  53. data/xml/nntp_banners.xml +51 -0
  54. data/xml/ntp_banners.xml +538 -0
  55. data/xml/pop_banners.xml +452 -0
  56. data/xml/rsh_resp.xml +90 -0
  57. data/xml/sip_banners.xml +14 -0
  58. data/xml/smb_native_os.xml +385 -0
  59. data/xml/smtp_banners.xml +1738 -0
  60. data/xml/smtp_debug.xml +45 -0
  61. data/xml/smtp_ehlo.xml +53 -0
  62. data/xml/smtp_expn.xml +95 -0
  63. data/xml/smtp_help.xml +212 -0
  64. data/xml/smtp_mailfrom.xml +24 -0
  65. data/xml/smtp_noop.xml +45 -0
  66. data/xml/smtp_quit.xml +31 -0
  67. data/xml/smtp_rcptto.xml +33 -0
  68. data/xml/smtp_rset.xml +23 -0
  69. data/xml/smtp_turn.xml +23 -0
  70. data/xml/smtp_vrfy.xml +109 -0
  71. data/xml/snmp_sysdescr.xml +8008 -0
  72. data/xml/snmp_sysobjid.xml +284 -0
  73. data/xml/ssh_banners.xml +790 -0
  74. data/xml/upnp_banners.xml +590 -0
  75. metadata +190 -0
@@ -0,0 +1,33 @@
1
+ <?xml version="1.0"?>
2
+ <fingerprints>
3
+ <fingerprint pattern="^Cisco-SIPGateway/IOS-([\d\.x]+)$">
4
+ <description>Cisco SIPGateway</description>
5
+ <example>Cisco-SIPGateway/IOS-12.x</example>
6
+ <param pos="0" name="os.vendor" value="Cisco"/>
7
+ <param pos="0" name="os.product" value="IOS"/>
8
+ <param pos="1" name="os.version"/>
9
+ </fingerprint>
10
+ <fingerprint pattern="^Microsoft Exchange Server 2007 IMAP4 service ready$">
11
+ <!-- Microsoft Exchange Server 2007 IMAP4 service ready
12
+ -->
13
+ <description>Microsoft Exchange Server 2007</description>
14
+ <param pos="0" name="service.vendor" value="Microsoft"/>
15
+ <param pos="0" name="service.family" value="Exchange Server"/>
16
+ <param pos="0" name="service.product" value="Exchange 2007 Server"/>
17
+ <param pos="0" name="os.vendor" value="Microsoft"/>
18
+ <param pos="0" name="os.device" value="General"/>
19
+ <param pos="0" name="os.family" value="Windows"/>
20
+ <param pos="0" name="os.product" value="Windows"/>
21
+ </fingerprint>
22
+ <fingerprint pattern="^The Microsoft Exchange IMAP4 service is ready\.?$">
23
+ <example>The Microsoft Exchange IMAP4 service is ready.</example>
24
+ <description>Microsoft Exchange Server</description>
25
+ <param pos="0" name="service.vendor" value="Microsoft"/>
26
+ <param pos="0" name="service.family" value="Exchange Server"/>
27
+ <param pos="0" name="service.product" value="Exchange Server"/>
28
+ <param pos="0" name="os.vendor" value="Microsoft"/>
29
+ <param pos="0" name="os.device" value="General"/>
30
+ <param pos="0" name="os.family" value="Windows"/>
31
+ <param pos="0" name="os.product" value="Windows"/>
32
+ </fingerprint>
33
+ </fingerprints>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0"?>
2
+ <fingerprints>
3
+ <fingerprint pattern="^foo$">
4
+ <description>foo test</description>
5
+ <example>bar</example>
6
+ </fingerprint>
7
+ <fingerprint pattern="^This matches$">
8
+ <example>This almost matches</example>
9
+ </fingerprint>
10
+ </fingerprints>
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0"?>
2
+ <fingerprints>
3
+ <fingerprint pattern="^-{10} Welcome to Pure-FTPd (.*)-{10}$">
4
+ <example>---------- Welcome to Pure-FTPd ----------</example>
5
+ <description>Pure-FTPd</description>
6
+ <param pos="1" name="pureftpd.config"/>
7
+ <param pos="0" name="service.family" value="Pure-FTPd"/>
8
+ <param pos="0" name="service.product" value="Pure-FTPd"/>
9
+ </fingerprint>
10
+ </fingerprints>
@@ -0,0 +1,3 @@
1
+ require 'recog/version'
2
+ require 'recog/db_manager'
3
+ require 'recog/nizer'
@@ -0,0 +1,38 @@
1
+ module Recog
2
+ class DB
3
+ require 'nokogiri'
4
+ require 'recog/fingerprint'
5
+
6
+ attr_accessor :path, :fingerprints, :match_key
7
+
8
+ def initialize(path)
9
+ self.path = path
10
+ parse_fingerprints
11
+ end
12
+
13
+ def parse_fingerprints
14
+ self.fingerprints = []
15
+ xml = nil
16
+
17
+ File.open(self.path, "rb") do |fd|
18
+ xml = Nokogiri::XML( fd.read(fd.stat.size))
19
+ end
20
+
21
+ xml.xpath("/fingerprints").each do |fbase|
22
+ if fbase['matches']
23
+ self.match_key = fbase['matches'].to_s
24
+ end
25
+ end
26
+
27
+ unless self.match_key
28
+ self.match_key = File.basename(self.path).sub(/\.xml$/, '')
29
+ end
30
+
31
+ xml.xpath("/fingerprints/fingerprint").each do |fprint|
32
+ fingerprints << Fingerprint.new(fprint)
33
+ end
34
+
35
+ xml = nil
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ module Recog
2
+ class DBManager
3
+ require 'nokogiri'
4
+ require 'recog/db'
5
+
6
+ attr_accessor :path, :databases
7
+
8
+ DefaultDatabasePath = File.expand_path( File.join( File.dirname(__FILE__), "..", "..", "xml") )
9
+
10
+ def initialize(path = DefaultDatabasePath)
11
+ self.path = path
12
+ reload
13
+ end
14
+
15
+ def load_databases
16
+ Dir[self.path + "/*.xml"].each do |dbxml|
17
+ self.databases << DB.new(dbxml)
18
+ end
19
+ end
20
+
21
+ def reload
22
+ self.databases = []
23
+ load_databases
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,60 @@
1
+ module Recog
2
+ class Fingerprint
3
+ attr_reader :name, :regex, :params, :tests
4
+
5
+ def initialize(xml)
6
+ @name = description(xml)
7
+ @regex = create_regexp(xml)
8
+ @params = parse_params(xml)
9
+ @tests = examples(xml)
10
+ end
11
+
12
+ private
13
+
14
+ def description(xml)
15
+ element = xml.xpath('description')
16
+ element.empty? ? '' : element.first.content
17
+ end
18
+
19
+ def create_regexp(xml)
20
+ pattern = xml['pattern']
21
+ flags = xml['flags'].to_s.split(',')
22
+ RegexpFactory.build(pattern, flags)
23
+ end
24
+
25
+ def parse_params(xml)
26
+ {}.tap do |h|
27
+ xml.xpath('param').each do |e|
28
+ name = e['name']
29
+ pos = e['pos'].to_i
30
+ value = e['value'].to_s
31
+ h[name] = [pos, value]
32
+ end
33
+ end
34
+ end
35
+
36
+ def examples(xml)
37
+ xml.xpath('example').collect(&:content)
38
+ end
39
+
40
+ module RegexpFactory
41
+ def self.build(pattern, flags)
42
+ options = build_options(flags)
43
+ Regexp.new(pattern, options)
44
+ end
45
+
46
+ def self.build_options(flags)
47
+ rflags = Regexp::NOENCODING
48
+ flags.each do |flag|
49
+ case flag
50
+ when 'REG_DOT_NEWLINE', 'REG_LINE_ANY_CRLF'
51
+ rflags |= Regexp::MULTILINE
52
+ when 'REG_ICASE'
53
+ rflags |= Regexp::IGNORECASE
54
+ end
55
+ end
56
+ rflags
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+ module Recog
2
+ class Formatter
3
+ COLORS = {
4
+ :red => 31,
5
+ :yellow => 33,
6
+ :green => 32,
7
+ :white => 15
8
+ }
9
+
10
+ attr_reader :options, :output
11
+
12
+ def initialize(options, output)
13
+ @options = options
14
+ @output = output || StringIO.new
15
+ end
16
+
17
+ def status_message(text)
18
+ output.puts color(text, :white)
19
+ end
20
+
21
+ def success_message(text)
22
+ output.puts color(text, :green)
23
+ end
24
+
25
+ def warning_message(text)
26
+ output.puts color(text, :yellow)
27
+ end
28
+
29
+ def failure_message(text)
30
+ output.puts color(text, :red)
31
+ end
32
+
33
+ private
34
+
35
+ def color_enabled?
36
+ options.color
37
+ end
38
+
39
+ def color(text, color_code)
40
+ color_enabled? ? colorize(text, color_code) : text
41
+ end
42
+
43
+ def colorize(text, color_code)
44
+ "\e[#{color_code_for(color_code)}m#{text}\e[0m"
45
+ end
46
+
47
+ def color_code_for(code)
48
+ COLORS.fetch(code) { COLORS.fetch(:white) }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,77 @@
1
+ module Recog
2
+ class MatchReporter
3
+ attr_reader :formatter
4
+ attr_reader :line_count, :match_count, :fail_count
5
+
6
+ def initialize(options, formatter)
7
+ @options = options
8
+ @formatter = formatter
9
+ reset_counts
10
+ end
11
+
12
+ def report
13
+ reset_counts
14
+ yield self
15
+ summarize
16
+ end
17
+
18
+ def stop?
19
+ return false unless @options.fail_fast
20
+ @fail_count >= @options.stop_after
21
+ end
22
+
23
+ def increment_line_count
24
+ @line_count += 1
25
+ end
26
+
27
+ def match(text)
28
+ @match_count += 1
29
+ formatter.success_message(text)
30
+ end
31
+
32
+ def failure(text)
33
+ @fail_count += 1
34
+ formatter.failure_message(text)
35
+ end
36
+
37
+ def print_summary
38
+ colorize_summary(summary_line)
39
+ end
40
+
41
+ private
42
+
43
+ def reset_counts
44
+ @line_count = @match_count = @fail_count = 0
45
+ end
46
+
47
+ def detail?
48
+ @options.detail
49
+ end
50
+
51
+ def summarize
52
+ if detail?
53
+ print_lines_processed
54
+ print_summary
55
+ end
56
+ end
57
+
58
+ def print_lines_processed
59
+ formatter.status_message("\nProcessed #{line_count} lines")
60
+ end
61
+
62
+ def summary_line
63
+ summary = "SUMMARY: "
64
+ summary << "#{match_count} matches"
65
+ summary << " and #{fail_count} failures"
66
+ summary
67
+ end
68
+
69
+ def colorize_summary(summary)
70
+ if @fail_count > 0
71
+ formatter.failure_message(summary)
72
+ else
73
+ formatter.success_message(summary)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,60 @@
1
+ module Recog
2
+ class Matcher
3
+ attr_reader :fingerprints, :reporter
4
+
5
+ def initialize(fingerprints, reporter)
6
+ @fingerprints = fingerprints
7
+ @reporter = reporter
8
+ end
9
+
10
+ def match_banners(banners_file)
11
+ reporter.report do
12
+
13
+ fd = $stdin
14
+ file_source = false
15
+
16
+ if banners_file and banners_file != "-"
17
+ fd = File.open(banners_file, "rb")
18
+ file_source = true
19
+ end
20
+
21
+ fd.each_line do |line|
22
+ reporter.increment_line_count
23
+
24
+ line = line.to_s.unpack("C*").pack("C*").strip.gsub(/\\[rn]/, '')
25
+ found = nil
26
+ fingerprints.each do |fp|
27
+ m = line.match(fp.regex)
28
+ if m
29
+ found = [fp, m]
30
+ break
31
+ end
32
+ end
33
+
34
+ if found
35
+ info = { }
36
+ fp, m = found
37
+ fp.params.each_pair do |k,v|
38
+ if v[0] == 0
39
+ info[k] = v[1]
40
+ else
41
+ info[k] = m[ v[0] ]
42
+ end
43
+ end
44
+ info['data'] = line
45
+ reporter.match "MATCH: #{info.inspect}"
46
+ else
47
+ reporter.failure "FAIL: #{line}"
48
+ end
49
+
50
+ if reporter.stop?
51
+ break
52
+ end
53
+ end
54
+
55
+ fd.close if file_source
56
+
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+
2
+ require 'recog/matcher'
3
+ require 'recog/formatter'
4
+ require 'recog/match_reporter'
5
+
6
+ module Recog
7
+ module MatcherFactory
8
+ def self.build(options)
9
+ formatter = Formatter.new(options, $stdout)
10
+ reporter = MatchReporter.new(options, formatter)
11
+ Matcher.new(options.fingerprints, reporter)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,263 @@
1
+ module Recog
2
+ class Nizer
3
+
4
+ # Default certainty ratings where none are specified in the fingerprint itself
5
+ DEFAULT_OS_CERTAINTY = 0.85 # Most frequent weights are 0.9, 1.0, and 0.5
6
+ DEFAULT_SERVICE_CERTAINTY = 0.85 # Most frequent weight is 0.85
7
+
8
+ # Non-weighted host attributes that can be extracted from fingerprint matches
9
+ HOST_ATTRIBUTES = %W{
10
+ host.domain
11
+ host.id
12
+ host.ip
13
+ host.mac
14
+ host.name
15
+ host.time
16
+ hw.device
17
+ hw.family
18
+ hw.product
19
+ hw.vendor
20
+ }
21
+
22
+ @@db_manager = nil
23
+
24
+ #
25
+ # Locate a database that corresponds with the match_key and attempt to
26
+ # find a matching fingerprinting, stopping a the first hit. Returns
27
+ # nil when no matching database or fingerprint is found.
28
+ #
29
+ def self.match(match_key, match_string)
30
+ match_string = match_string.to_s.unpack("C*").pack("C*")
31
+ @@db_manager ||= Recog::DBManager.new
32
+ @@db_manager.databases.each do |db|
33
+ next unless db.match_key == match_key
34
+ db.fingerprints.each do |fprint|
35
+ m = fprint.regex.match(match_string)
36
+ next unless m
37
+ result = { 'matched' => fprint.name }
38
+ fprint.params.each_pair do |k,v|
39
+ if v[0] == 0
40
+ result[k] = v[1]
41
+ else
42
+ result[k] = m[ v[0] ]
43
+ end
44
+ end
45
+ return result
46
+ end
47
+ end
48
+ nil
49
+ end
50
+
51
+ #
52
+ # Consider an array of match outputs, choose the best result, taking into
53
+ # account the granularity of OS vs Version vs SP vs Language. Only consider
54
+ # fields relevant to the host (OS, name, mac address, etc).
55
+ #
56
+ def self.best_os_match(matches)
57
+
58
+ # The result hash we return to the caller
59
+ result = {}
60
+
61
+ # Certain attributes should be evaluated separately
62
+ host_attrs = {}
63
+
64
+ # Bucket matches into matched OS product names
65
+ os_products = {}
66
+
67
+ matches.each do |m|
68
+ # Count how many times each host attribute value is asserted
69
+ (HOST_ATTRIBUTES & m.keys).each do |ha|
70
+ host_attrs[ha] ||= {}
71
+ host_attrs[ha][m[ha]] ||= 0
72
+ host_attrs[ha][m[ha]] += 1
73
+ end
74
+
75
+ next unless m.has_key?('os.product')
76
+
77
+ # Group matches by OS product and normalize certainty
78
+ cm = m.dup
79
+ cm['os.certainty'] = ( m['os.certainty'] || DEFAULT_OS_CERTAINTY ).to_f
80
+ os_products[ cm['os.product'] ] ||= []
81
+ os_products[ cm['os.product'] ] << cm
82
+ end
83
+
84
+ #
85
+ # Select the best host attribute value by highest frequency
86
+ #
87
+ host_attrs.keys.each do |hk|
88
+ ranked_attr = host_attrs[hk].keys.sort do |a,b|
89
+ host_attrs[hk][b] <=> host_attrs[hk][a]
90
+ end
91
+ result[hk] = ranked_attr.first
92
+ end
93
+
94
+ # Unable to guess the OS without OS matches
95
+ unless os_products.keys.length > 0
96
+ return result
97
+ end
98
+
99
+ #
100
+ # Select the best operating system name by combined certainty of all
101
+ # matches within an os.product group. Multiple weak matches can
102
+ # outweigh a single strong match by design.
103
+ #
104
+ ranked_os = os_products.keys.sort do |a,b|
105
+ os_products[b].map{ |r| r['os.certainty'] }.inject(:+) <=>
106
+ os_products[a].map{ |r| r['os.certainty'] }.inject(:+)
107
+ end
108
+
109
+ # Within the best match group, try to fill in missing attributes
110
+ os_name = ranked_os.first
111
+
112
+ # Find the best match within the winning group
113
+ ranked_os_matches = os_products[os_name].sort do |a,b|
114
+ b['os.certainty'] <=> a['os.certainty']
115
+ end
116
+
117
+ # Fill in missing result values in descending order of best match
118
+ ranked_os_matches.each do |rm|
119
+ rm.each_pair do |k,v|
120
+ result[k] ||= v
121
+ end
122
+ end
123
+
124
+ result
125
+ end
126
+
127
+ #
128
+ # Consider an array of match outputs, choose the best result, taking into
129
+ # account the granularity of service. Only consider fields relevant to the
130
+ # service.
131
+ #
132
+ def self.best_service_match(matches)
133
+
134
+ # The result hash we return to the caller
135
+ result = {}
136
+
137
+ # Bucket matches into matched service product names
138
+ service_products = {}
139
+
140
+ matches.select{ |m| m.has_key?('service.product') }.each do |m|
141
+ # Group matches by product and normalize certainty
142
+ cm = m.dup
143
+ cm['service.certainty'] = ( m['service.certainty'] || DEFAULT_SERVICE_CERTAINTY ).to_f
144
+ service_products[ cm['service.product'] ] ||= []
145
+ service_products[ cm['service.product'] ] << cm
146
+ end
147
+
148
+ # Unable to guess the service without service matches
149
+ unless service_products.keys.length > 0
150
+ return result
151
+ end
152
+
153
+ #
154
+ # Select the best service name by combined certainty of all matches
155
+ # within an service.product group. Multiple weak matches can
156
+ # outweigh a single strong match by design.
157
+ #
158
+ ranked_service = service_products.keys.sort do |a,b|
159
+ service_products[b].map{ |r| r['service.certainty'] }.inject(:+) <=>
160
+ service_products[a].map{ |r| r['service.certainty'] }.inject(:+)
161
+ end
162
+
163
+ # Within the best match group, try to fill in missing attributes
164
+ service_name = ranked_service.first
165
+
166
+ # Find the best match within the winning group
167
+ ranked_service_matches = service_products[service_name].sort do |a,b|
168
+ b['service.certainty'] <=> a['service.certainty']
169
+ end
170
+
171
+ # Fill in missing service values in descending order of best match
172
+ ranked_service_matches.each do |rm|
173
+ rm.keys.select{ |k| k.index('service.') == 0 }.each do |k|
174
+ result[k] ||= rm[k]
175
+ end
176
+ end
177
+
178
+ result
179
+ end
180
+
181
+ end
182
+ end
183
+
184
+ =begin
185
+
186
+ Current key names:
187
+
188
+ apache.info
189
+ apache.variant
190
+ apache.variant.version
191
+ cookie
192
+ host.domain
193
+ host.id
194
+ host.ip
195
+ host.mac
196
+ host.name
197
+ host.time
198
+ hw.device
199
+ hw.family
200
+ hw.product
201
+ hw.vendor
202
+ imail.eval
203
+ jetty.info
204
+ junction.cookie
205
+ junction.name
206
+ linux.kernel.version
207
+ loadbalancer.poolname
208
+ mdaemon.unregistered
209
+ mercur.os.info
210
+ metainfo.version
211
+ metainfo.version.version
212
+ ms.nttp.version
213
+ notes.build.version
214
+ notes.intl
215
+ ntmail.id
216
+ openssh.comment
217
+ openssh.cvepatch
218
+ os.arch
219
+ os.build
220
+ os.certainty
221
+ os.device
222
+ os.edition
223
+ os.family
224
+ os.product
225
+ os.vendor
226
+ os.version
227
+ os.version.version
228
+ os.version.version.version
229
+ postfix.os.info
230
+ postoffice.build
231
+ postoffice.id
232
+ proftpd.server.name
233
+ pureftpd.config
234
+ qpopper.version
235
+ sendmail.config.version
236
+ sendmail.hpux.phne.version
237
+ sendmail.vendor.version
238
+ service.certainty
239
+ service.component.family
240
+ service.component.product
241
+ service.component.vendor
242
+ service.component.version
243
+ service.family
244
+ service.product
245
+ service.vendor
246
+ service.version
247
+ service.version.version
248
+ service.version.version.version
249
+ service.version.version.version.version
250
+ service.version.version.version.version.version
251
+ siemens.model
252
+ snmp.fpmib.oid.1
253
+ snmp.fpmib.oid.2
254
+ system.time
255
+ system.time.format
256
+ system.time.micros
257
+ system.time.millis
258
+ thttpd.mx-patch
259
+ timeout
260
+ tomcat.info
261
+ zmailer.ident
262
+
263
+ =end