1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-12 11:52:01 +01:00

Import the Net-DNS library

git-svn-id: file:///home/svn/framework3/trunk@5574 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
HD Moore 2008-07-23 17:32:05 +00:00
parent 5cfdffc395
commit b70ce0ae10
25 changed files with 4935 additions and 0 deletions

1
lib/net/dns.rb Executable file
View File

@ -0,0 +1 @@
require "net/dns/dns"

55
lib/net/dns/README Normal file
View File

@ -0,0 +1,55 @@
Net::DNS README
============
This is a port of the Perl Net::DNS module, written by Michael Fuhr
and now currently maintained by Olaf Kolkman (www.net-dns.org). It
keeps the same interfaces and function names, although has a bit
improved OO and some other stuff.
It can be used to query DNS servers for various kind of records, perform
zone transfer and dynamic updates. It has even a class for acting as a
nameserver.
This version is quite incomplete. You can use it as a resolver.
Requirements
------------
* Ruby 1.6
Install
-------
De-compress archive and enter its top directory.
Then type:
($ su)
# ruby setup.rb
These simple step installs this program under the default
location of Ruby libraries. You can also install files into
your favorite directory by supplying setup.rb some options.
Try "ruby setup.rb --help".
Usage
-----
Have a look on the manual pages.
In doc/ you will find many useful documents too.
License
-------
Net::DNS is distributed under the same license Ruby is.
Author
------
See AUTHORS
# $Id: README,v 1.2 2005/06/17 15:11:18 bluemonk Exp $

117
lib/net/dns/dns.rb Normal file
View File

@ -0,0 +1,117 @@
##
#
# dns.rb
#
# $id$
#
##
module Net # :nodoc:
module DNS
# Version of the library
VERSION = "0.4"
# Packet size in bytes
PACKETSZ = 512
# Size of the header
HFIXEDSZ = 12
# Size of the question portion (type and class)
QFIXEDSZ = 4
# Size of an RR portion (type,class,lenght and ttl)
RRFIXEDSZ = 10
# Size of an int 32 bit
INT32SZ = 4
# Size of a short int
INT16SZ = 2
module QueryTypes
SIGZERO = 0
A = 1
NS = 2
MD = 3
MF = 4
CNAME = 5
SOA = 6
MB = 7
MG = 8
MR = 9
NULL = 10
WKS = 11
PTR = 12
HINFO = 13
MINFO = 14
MX = 15
TXT = 16
RP = 17
AFSDB = 18
X25 = 19
ISDN = 20
RT = 21
NSAP = 22
NSAPPTR = 23
SIG = 24
KEY = 25
PX = 26
GPOS = 27
AAAA = 28
LOC = 29
NXT = 30
EID = 31
NIMLOC = 32
SRV = 33
ATMA = 34
NAPTR = 35
KX = 36
CERT = 37
DNAME = 39
OPT = 41
DS = 43
SSHFP = 44
RRSIG = 46
NSEC = 47
DNSKEY = 48
UINFO = 100
UID = 101
GID = 102
UNSPEC = 103
TKEY = 249
TSIG = 250
IXFR = 251
AXFR = 252
MAILB = 253
MAILA = 254
ANY = 255
end
module QueryClasses
# Internet class
IN = 1
# Chaos class
CH = 3
# Hesiod class
HS = 4
# None class
NONE = 254
# Any class
ANY = 255
end
include QueryTypes
include QueryClasses
end # module DNS
end # module Net

761
lib/net/dns/header.rb Normal file
View File

@ -0,0 +1,761 @@
#---
# $Id: Header.rb,v 1.5 2006/07/30 16:54:28 bluemonk Exp $
#+++
require 'net/dns/dns'
module Net # :nodoc:
module DNS
#
# =Name
#
# Net::DNS::Header - DNS packet header class
#
# =Synopsis
#
# require 'net/dns/header'
#
# =Description
#
# The Net::DNS::Header class represents the header portion of a
# DNS packet. An Header object is created whenever a new packet
# is parsed or as user request.
#
# header = Net::DNS::Header.new
# # ;; id = 18123
# # ;; qr = 0 opCode: 0 aa = 0 tc = 0 rd = 1
# # ;; ra = 0 ad = 0 cd = 0 rcode = 0
# # ;; qdCount = 1 anCount = 0 nsCount = 0 arCount = 0
#
# header.format
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 18123 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # |0| 0 |0|0|1|0|0| 0 | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 1 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#
# # packet is an instance of Net::DNS::Packet
# header = packet.header
# puts "Answer is #{header.auth? ? '' : 'non'} authoritative"
#
# A lot of methods were written to keep a compatibility layer with
# the Perl version of the library, as long as methods name which are
# more or less the same.
#
# =Error classes
#
# Some error classes has been defined for the Net::DNS::Header class,
# which are listed here to keep a light and browsable main documentation.
# We have:
#
# * HeaderArgumentError: canonical argument error
# * HeaderWrongCount: a wrong +count+ parameter has been passed
# * HeaderWrongRecursive: a wrong +recursive+ parameter has been passed
# * HeaderWrongOpcode: a not valid +opCode+ has been specified
# * HeaderDuplicateID: the requested ID is already in use
#
# =Copyright
#
# Copyright (c) 2006 Marco Ceresa
#
# All rights reserved. This program is free software; you may redistribute
# it and/or modify it under the same terms as Ruby itself.
#
class Header
#
# =Name
#
# Net::DNS::Header::RCode - DNS Header RCode handling class
#
# =Synopsis
#
# It should be used internally by Net::DNS::Header class. However, it's still
# possible to instantiate it directly.
#
# require 'net/dns/header'
# rcode = Net::DNS::Header::RCode.new 0
#
# =Description
#
# The RCode class represents the RCode field in the Header portion of a
# DNS packet. This field (called Response Code) is used to get informations
# about the status of a DNS operation, such as a query or an update. These
# are the values in the original Mockapetris's standard (RFC1035):
#
# * 0 No error condition
# * 1 Format error - The name server was unable to interpret
# the query.
# * 2 Server failure - The name server was
# unable to process this query due to a
# problem with the name server.
# * 3 Name Error - Meaningful only for
# responses from an authoritative name
# server, this code signifies that the
# domain name referenced in the query does
# not exist.
# * 4 Not Implemented - The name server does
# not support the requested kind of query.
# * 5 Refused - The name server refuses to
# perform the specified operation for
# policy reasons. For example, a name
# server may not wish to provide the
# information to the particular requester,
# or a name server may not wish to perform
# a particular operation (e.g., zone
# transfer) for particular data.
# * 6-15 Reserved for future use.
#
# In the next DNS RFCs, codes 6-15 has been assigned to the following
# errors:
#
# * 6 YXDomain
# * 7 YXRRSet
# * 8 NXRRSet
# * 9 NotAuth
# * 10 NotZone
#
# More RCodes has to come for TSIGs and other operations.
#
class RCode
# Constant for +rcode+ Response Code No Error
NOERROR = 0
# Constant for +rcode+ Response Code Format Error
FORMAT = 1
# Constant for +rcode+ Response Code Server Format Error
SERVER = 2
# Constant for +rcode+ Response Code Name Error
NAME = 3
# Constant for +rcode+ Response Code Not Implemented Error
NOTIMPLEMENTED = 4
# Constant for +rcode+ Response Code Refused Error
REFUSED = 5
RCodeType = %w[NoError FormErr ServFail NXDomain NotImp
Refused YXDomain YXRRSet NXRRSet NotAuth NotZone]
RCodeErrorString = ["No errors",
"The name server was unable to interpret the query",
"The name server was unable to process this query due to problem with the name server",
"Domain name referenced in the query does not exists",
"The name server does not support the requested kind of query",
"The name server refuses to perform the specified operation for policy reasons",
"",
"",
"",
"",
""]
attr_reader :code, :type, :explanation
def initialize(code)
if (0..10).include? code
@code = code
@type = RCodeType[code]
@explanation = RCodeErrorString[code]
else
raise HeaderArgumentError, "RCode #{code} out of range"
end
end
def to_s
@code.to_s
end
end
# Constant for +opCode+ query
QUERY = 0
# Constant for +opCode+ iquery
IQUERY = 1
# Constant for +opCode+ status
STATUS = 2
# Array with given strings
OPARR = %w[QUERY IQUERY STATUS]
@@id_arr = []
# Reader for +id+ attribute
attr_reader :id
# Reader for the operational code
attr_reader :opCode
# Reader for the rCode instance
attr_reader :rCode
# Reader for question section entries number
attr_reader :qdCount
# Reader for answer section entries number
attr_reader :anCount
# Reader for authority section entries number
attr_reader :nsCount
# Reader for addictional section entries number
attr_reader :arCount
# Creates a new Net::DNS::Header object with the desired values,
# which can be specified as an Hash argument. When called without
# arguments, defaults are used. If a data string is passed, values
# are taken from parsing the string.
#
# Examples:
#
# # Create a new Net::DNS::Header object
# header = Net::DNS::Header.new
#
# # Create a new Net::DNS::Header object passing values
# header = Net::DNS::Header.new(:opCode => 1, :rd => 0)
#
# # Create a new Net::DNS::Header object with binary data
# header = Net::DNS::Header.new(data)
#
# Default values are:
#
# :id => auto generated
# :qr => 0 # Query response flag
# :aa => 0 # Authoritative answer flag
# :tc => 0 # Truncated packet flag
# :ra => 0 # Recursiond available flag
# :rCode => 0 # Response code (status of the query)
# :opCode => 0 # Operational code (purpose of the query)
# :cd => 0 # Checking disable flag
# :ad => 0 # Only relevant in DNSSEC context
# :rd => 1 # Recursion desired flag
# :qdCount => 1 # Number of questions in the dns packet
# :anCount => 0 # Number of answer RRs in the dns packet
# :nsCount => 0 # Number of authoritative RRs in the dns packet
# :arCount => 0 # Number of additional RRs in the dns packet
#
# See also each option for a detailed explanation of usage.
#
def initialize(arg = {})
if arg.kind_of? Hash
new_from_hash(arg)
else
raise HeaderArgumentError, "Wrong argument class: #{arg.class}"
end
end
# Creates a new Net::DNS::Header object from binary data, which is
# passed as a string object as argument.
# The configurations parameters are taken from parsing the string.
#
# Example:
#
# # Create a new Net::DNS::Header object with binary data
# header = Net::DNS::Header.new(data)
#
# header.auth?
# #=> "true" if it comes from authoritative name server
#
def self.parse(arg)
if arg.kind_of? String
o = allocate
o.send(:new_from_binary, arg)
o
else
raise HeaderArgumentError, "Wrong argument class: #{arg.class}"
end
end
# Inspect method, prints out all the options and relative values.
#
# p Net::DNS::Header.new
# # ;; id = 18123
# # ;; qr = 0 opCode: 0 aa = 0 tc = 0 rd = 1
# # ;; ra = 0 ad = 0 cd = 0 rcode = 0
# # ;; qdCount = 1 anCount = 0 nsCount = 0 arCount = 0
#
# This method will maybe be changed in the future to a more pretty
# way of display output.
#
def inspect
";; id = #@id\n" +
if false # @opCode == "UPDATE"
#do stuff
else
";; qr = #@qr\t" +
"opCode: #{opCode_str}\t" +
"aa = #@aa\t" +
"tc = #@tc\t" +
"rd = #@rd\n" +
";; ra = #@ra\t" +
"ad = #@ad\t" +
"cd = #@cd\t" +
"rcode = #{@rCode.type}\n" +
";; qdCount = #@qdCount\t"+
"anCount = #@anCount\t"+
"nsCount = #@nsCount\t"+
"arCount = #@arCount\n"
end
end
# The Net::DNS::Header#format method prints out the header
# in a special ascii representation of data, in a way
# similar to those often found on RFCs.
#
# p Net::DNS::Header.new.format
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 18123 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # |0| 0 |0|0|1|0|0| 0 | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 1 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# # | 0 |
# # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
#
# This can be very usefull for didactical purpouses :)
#
def format
del = ("+-" * 16) + "+\n"
len = del.length
str = del + "|" + @id.to_s.center(len-3) + "|\n"
str += del + "|" + @qr.to_s
str += "|" + @opCode.to_s.center(7)
str += "|" + @aa.to_s
str += "|" + @tc.to_s
str += "|" + @rd.to_s
str += "|" + @ra.to_s
str += "|" + @ad.to_s
str += "|" + @cd.to_s.center(3)
str += "|" + @rCode.to_s.center(7) + "|\n"
str += del + "|" + @qdCount.to_s.center(len-3) + "|\n"
str += del + "|" + @anCount.to_s.center(len-3) + "|\n"
str += del + "|" + @nsCount.to_s.center(len-3) + "|\n"
str += del + "|" + @arCount.to_s.center(len-3) + "|\n" + del
str
end
# Returns the header data in binary format, appropriate
# for use in a DNS query packet.
#
# hdata = header.data
# puts "Header is #{hdata.size} bytes"
#
def data
arr = []
arr.push(@id)
arr.push((@qr<<7)|(@opCode<<3)|(@aa<<2)|(@tc<<1)|@rd)
arr.push((@ra<<7)|(@ad<<5)|(@cd<<4)|@rCode.code)
arr.push(@qdCount)
arr.push(@anCount)
arr.push(@nsCount)
arr.push(@arCount)
arr.pack("n C2 n4")
end
# Set the ID for the current header. Useful when
# performing security tests.
#
def id=(val)
if @@id_arr.include? val
raise HeaderDuplicateID, "ID #{val} already used"
end
if (1..65535).include? val
@id = val
@@id_arr.push val
else
raise HeaderArgumentError, "ID #{val} out of range"
end
end
# Checks whether the header is a query (+qr+ bit set to 0)
#
def query?
@qr == 0
end
# Set the +qr+ query response flag to be either +true+ or
# +false+. You can also use the values 0 and 1. This flag
# indicates if the DNS packet contains a query or an answer,
# so it should be set to +true+ in DNS answer packets.
# If +qr+ is +true+, the packet is a response.
#
def qr=(val)
case val
when true
@qr = 1
when false
@qr = 0
when 0,1
@qr = val
else
raise HeaderArgumentError, ":qr must be true(or 1) or false(or 0)"
end
end
# Checks whether the header is a response
# (+qr+ bit set to 1)
#
def response?
@qr == 1
end
# Returns a string representation of the +opCode+
#
# puts "Packet is a #{header.opCode_str}"
# #=> Packet is a QUERY
#
def opCode_str
OPARR[@opCode]
end
# Set the +opCode+ variable to a new value. This fields indicates
# the type of the question present in the DNS packet; +val+ can be
# one of the values QUERY, IQUERY or STATUS.
#
# * QUERY is the standard DNS query
# * IQUERY is the inverse query
# * STATUS is used to query the nameserver for its status
#
# Example:
#
# include Net::DNS
# header = Header.new
# header.opCode = Header::STATUS
#
def opCode=(val)
if (0..2).include? val
@opCode = val
else
raise HeaderWrongOpcode, "Wrong opCode value (#{val}), must be QUERY, IQUERY or STATUS"
end
end
# Checks whether the response is authoritative
#
# if header.auth?
# puts "Response is authoritative"
# else
# puts "Answer is NOT authoritative"
# end
#
def auth?
@aa == 1
end
# Set the +aa+ flag (authoritative answer) to either +true+
# or +false+. You can also use 0 or 1.
#
# This flag indicates whether a DNS answer packet contains
# authoritative data, meaning that is was generated by a
# nameserver authoritative for the domain of the question.
#
# Must only be set to +true+ in DNS answer packets.
#
def aa=(val)
case val
when true
@aa = 1
when false
@aa = 0
when 0,1
@aa = val
else
raise HeaderArgumentError, ":aa must be true(or 1) or false(or 0)"
end
end
# Checks whether the packet was truncated
#
# # Sending packet using UDP
# if header.truncated?
# puts "Warning, packet has been truncated!"
# # Sending packet using TCP
# end
# # Do something with the answer
#
def truncated?
@tc == 1
end
# Set the +tc+ flag (truncated packet) to either +true+
# ot +false+. You can also use 0 or 1.
#
# The truncated flag is used in response packets to indicate
# that the amount of data to be trasmitted exceedes the
# maximum allowed by the protocol in use, tipically UDP, and
# that the data present in the packet has been truncated.
# A different protocol (such has TCP) need to be used to
# retrieve full data.
#
# Must only be set in DNS answer packets.
#
def tc=(val)
case val
when true
@tc = 1
when false
@tc = 0
when 0,1
@tc = val
else
raise HeaderArgumentError, ":tc must be true(or 1) or false(or 0)"
end
end
# Checks whether the packet has a recursion bit
# set, meaning that recursion is desired
#
def recursive?
@rd == 1
end
# Sets the recursion desidered bit.
# Remember that recursion query support is
# optional.
#
# header.recursive = true
# hdata = header.data # suitable for sending
#
# Consult RFC1034 and RFC1035 for a detailed explanation
# of how recursion works.
#
def recursive=(val)
case val
when true
@rd = 1
when false
@rd = 0
when 1
@rd = 1
when 0
@rd = 0
else
raise HeaderWrongRecursive, "Wrong value (#{val}), please specify true (1) or false (0)"
end
end
# Alias for Header#recursive= to keep compatibility
# with the Perl version.
#
def rd=(val)
self.recursive = val
end
# Checks whether recursion is available.
# This flag is usually set by nameservers to indicate
# that they support recursive-type queries.
#
def r_available?
@ra == 1
end
# Set the +ra+ flag (recursion available) to either +true+ or
# +false+. You can also use 0 and 1.
#
# This flag must only be set in DNS answer packets.
#
def ra=(val)
case val
when true
@ra = 1
when false
@ra = 0
when 0,1
@ra = val
else
raise HeaderArgumentError, ":ra must be true(or 1) or false(or 0)"
end
end
# Checks whether checking is enabled or disabled.
#
# Checking is enabled by default.
#
def checking?
@cd == 0
end
# Set the +cd+ flag (checking disabled) to either +true+
# ot +false+. You can also use 0 or 1.
#
def cd=(val)
case val
when true
@cd = 1
when false
@cd = 0
when 0,1
@cd = val
else
raise HeaderArgumentError, ":cd must be true(or 1) or false(or 0)"
end
end
# Checks whether +ad+ flag has been set.
#
# This flag is only relevant in DNSSEC context.
#
def verified?
@ad == 1
end
# Set the +ad+ flag to either +true+
# ot +false+. You can also use 0 or 1.
#
# The AD bit is only set on answers where signatures have
# been cryptographically verified or the server is
# authoritative for the data and is allowed to set the bit by policy.
#
def ad=(val)
case val
when true
@ad = 1
when false
@ad = 0
when 0,1
@ad = val
else
raise HeaderArgumentError, ":ad must be true(or 1) or false(or 0)"
end
end
# Returns an error array for the header response code, or
# +nil+ if no error is generated.
#
# error, cause = header.rCode_str
# puts "Error #{error} cause by: #{cause}" if error
# #=> Error ForErr caused by: The name server
# #=> was unable to interpret the query
#
def rCode_str
return rCode.type, rCode.explanation
end
# Checks for errors in the DNS packet
#
# unless header.error?
# puts "No errors in DNS answer packet"
# end
#
def error?
@rCode.code > 0
end
# Set the rCode value. This should only be done in DNS
# answer packets.
#
def rCode=(val)
@rCode = RCode.new(val)
end
# Sets the number of entries in a question section
#
def qdCount=(val)
if (0..65535).include? val
@qdCount = val
else
raise HeaderWrongCount, "Wrong number of count (#{val}), must be 0-65535"
end
end
# Sets the number of RRs in an answer section
#
def anCount=(val)
if (0..65535).include? val
@anCount = val
else
raise HeaderWrongCount, "Wrong number of count (#{val}), must be 0-65535"
end
end
# Sets the number of RRs in an authority section
#
def nsCount=(val)
if (0..65535).include? val
@nsCount = val
else
raise HeaderWrongCount, "Wrong number of count (#{val}), must be 0-65535"
end
end
# Sets the number of RRs in an addictional section
#
def arCount=(val)
if (0..65535).include? val
@arCount = val
else
raise HeaderWrongCount, "Wrong number of count (#{val}), must be 0-65535"
end
end
private
def new_from_scratch
@id = genID # generate ad unique id
@qr = @aa = @tc = @ra = @ad = @cd = 0
@rCode = RCode.new(0) # no error
@anCount = @nsCount = @arCount = 0
@rd = @qdCount = 1
@opCode = QUERY # standard query, default message
end
def new_from_binary(str)
unless str.size == Net::DNS::HFIXEDSZ
raise HeaderArgumentError, "Header binary data has wrong size: #{str.size} bytes"
end
arr = str.unpack("n C2 n4")
@id = arr[0]
@qr = (arr[1] >> 7) & 0x01
@opCode = (arr[1] >> 3) & 0x0F
@aa = (arr[1] >> 2) & 0x01
@tc = (arr[1] >> 1) & 0x01
@rd = arr[1] & 0x1
@ra = (arr[2] >> 7) & 0x01
@ad = (arr[2] >> 5) & 0x01
@cd = (arr[2] >> 4) & 0x01
@rCode = RCode.new(arr[2] & 0xf)
@qdCount = arr[3]
@anCount = arr[4]
@nsCount = arr[5]
@arCount = arr[6]
end
def new_from_hash(hash)
new_from_scratch
hash.each do |key,val|
eval "self.#{key.to_s} = val"
end
end
def genID
while (@@id_arr.include?(q = rand(65535)))
end
@@id_arr.push(q)
q
end
end # class Header
end # class DNS
end # module Net
class HeaderArgumentError < ArgumentError # :nodoc: all
end
class HeaderWrongCount < ArgumentError # :nodoc: all
end
class HeaderWrongRecursive < ArgumentError # :nodoc: all
end
class HeaderWrongOpcode < ArgumentError # :nodoc: all
end
class HeaderDuplicateID < ArgumentError # :nodoc: all
end

109
lib/net/dns/names/names.rb Normal file
View File

@ -0,0 +1,109 @@
module Net # :nodoc:
module DNS
module Names # :nodoc: all
INT16SZ = 2
# Expand a compressed name in a DNS Packet object. Please
# see RFC1025 for an explanation of how the compression
# in DNS packets works, how may it be useful and how should
# be handled.
#
# This method accept two parameters: a raw packet data and an
# offset, which indicates the point in the packet in which the
# parsing has arrived.
#
def dn_expand(packet,offset)
name = ""
packetlen = packet.size
while true
raise ExpandError, "offset is greater than packet lenght!" if packetlen < (offset+1)
len = packet.unpack("@#{offset} C")[0]
if len == 0
offset += 1
break
elsif (len & 0xC0) == 0xC0
raise ExpandError, "Packet ended before offset expand" if packetlen < (offset+INT16SZ)
ptr = packet.unpack("@#{offset} n")[0]
ptr &= 0x3FFF
name2 = dn_expand(packet,ptr)[0]
raise ExpandError, "Packet is malformed!" if name2 == nil
name += name2
offset += INT16SZ
break
else
offset += 1
raise ExpandError, "No expansion found" if packetlen < (offset+len)
elem = packet[offset..offset+len-1]
name += "#{elem}."
offset += len
end
end
return [name,offset] # name.chomp(".") if trailing dot has to be omitted
end
def pack_name(name)
if name.size > 63
raise ArgumentError, "Label data cannot exceed 63 chars"
end
arr = name.split(".")
str = ""
arr.each do |elem|
str += [elem.size,elem].pack("Ca*")
end
str += [0].pack("C")
str
end
def names_array(name)
arr = name.split(".")
ar = []
string = ""
arr.size.times do |i|
x = i+1
elem = arr[-x]
len = elem.size
string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
ar.unshift(string)
end
return ar
end
def dn_comp(name,offset,compnames)
names = {}
ptr = 0
str = ""
arr = names_array(name)
arr.each do |entry|
if compnames.has_key?(entry)
ptr = 0xC000 | compnames[entry]
str += [ptr].pack("n")
offset += INT16SZ
break
else
len = entry.unpack("C")[0]
elem = entry[1..len]
str += [len,elem].pack("Ca*")
names.update({"#{entry}" => offset})
offset += len
end
end
return str,offset,names
end
def valid?(name)
if name =~ /^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|h[kmnrtu]|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])$/i
return name
else
raise ArgumentError, "Invalid FQDN: #{name}"
end
end
end # module Names
end # module DNS
end # module Net
class ExpandError < StandardError # :nodoc:
end

570
lib/net/dns/packet.rb Normal file
View File

@ -0,0 +1,570 @@
require 'logger'
require 'net/dns/names/names'
require 'net/dns/dns'
require 'net/dns/header'
require 'net/dns/question'
require 'net/dns/rr'
module Net # :nodoc:
module DNS
# =Name
#
# Net::DNS::Packet - DNS packet object class
#
# =Synopsis
#
# require 'net/dns/packet'
#
# =Description
#
# The Net::DNS::Packet class represents an entire DNS packet,
# divided in his main section:
#
# * Header (instance of Net::DNS::Header)
# * Question (array of Net::DNS::Question objects)
# * Answer, Authority, Additional (each formed by an array of Net::DNS::RR
# objects)
#
# You can use this class whenever you need to create a DNS packet, whether
# in an user application, in a resolver instance (have a look, for instance,
# at the Net::DNS::Resolver#send method) or for a nameserver.
#
# Some example:
#
# # Create a packet
# packet = Net::DNS::Packet.new("www.example.com")
# mx = Net::DNS::Packet.new("example.com", Net::DNS::MX)
#
# # Getting packet binary data, suitable for network transmission
# data = packet.data
#
# A packet object can be created from binary data too, like an
# answer packet just received from a network stream:
#
# packet = Net::DNS::Packet::parse(data)
#
# Each part of a packet can be gotten by the right accessors:
#
# header = packet.header # Instance of Net::DNS::Header class
# question = packet.question # Instance of Net::DNS::Question class
#
# # Iterate over additional RRs
# packet.additional.each do |rr|
# puts "Got an #{rr.type} record"
# end
#
# Some iterators have been written to easy the access of those RRs,
# which are often the most important. So instead of doing:
#
# packet.answer.each do |rr|
# if rr.type == Net::DNS::RR::Types::A
# # do something with +rr.address+
# end
# end
#
# we can do:
#
# packet.each_address do |ip|
# # do something with +ip+
# end
#
# Be sure you don't miss all the iterators in the class documentation.
#
# =Logging facility
#
# As Net::DNS::Resolver class, Net::DNS::Packet class has its own logging
# facility too. It work in the same way the other one do, so you can
# maybe want to override it or change the file descriptor.
#
# packet = Net::DNS::Packet.new("www.example.com")
# packet.logger = $stderr
#
# # or even
# packet.logger = Logger.new("/tmp/packet.log")
#
# If the Net::DNS::Packet class is directly instantiated by the Net::DNS::Resolver
# class, like the great majority of the time, it will use the same logger facility.
#
# Logger level will be set to Logger::Debug if $DEBUG variable is set.
#
# =Error classes
#
# Some error classes has been defined for the Net::DNS::Packet class,
# which are listed here to keep a light and browsable main documentation.
# We have:
#
# * PacketArgumentError: Generic argument error for class Net::DNS::Packet
# * PacketError: Generic Packet error
#
# =Copyright
#
# Copyright (c) 2006 Marco Ceresa
#
# All rights reserved. This program is free software; you may redistribute
# it and/or modify it under the same terms as Ruby itself.
#
class Packet
include Names
attr_reader :header, :question, :answer, :authority, :additional
attr_reader :answerfrom, :answersize
# Create a new instance of Net::DNS::Packet class. Arguments are the
# canonical name of the resourse, an optional type field and an optional
# class field. The record type and class can be omitted; they default
# to +A+ and +IN+.
#
# packet = Net::DNS::Packet.new("www.example.com")
# packet = Net::DNS::Packet.new("example.com", Net::DNS::MX)
# packet = Net::DNS::Packet.new("example.com",Net::DNS::TXT,Net::DNS::CH)
#
# This class no longer instantiate object from binary data coming from
# network streams. Please use Net::DNS::Packet.new_from_data instead.
#
def initialize(name,type=Net::DNS::A,cls=Net::DNS::IN)
@header = Net::DNS::Header.new(:qdCount => 1)
@question = [Net::DNS::Question.new(name,type,cls)]
@answer = []
@authority = []
@additional = []
@logger = Logger.new $stdout
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
end
# Create a new instance of Net::DNS::Packet class from binary data, taken
# out by a network stream. For example:
#
# # udp_socket is an UDPSocket waiting for a response
# ans = udp_socket.recvfrom(1500)
# packet = Net::DNS::Packet::parse(ans)
#
# An optional +from+ argument can be used to specify the information
# of the sender. If data is passed as is from a Socket#recvfrom call,
# the method will accept it.
#
# Be sure that your network data is clean from any UDP/TCP header,
# expecially when using RAW sockets.
#
def Packet.parse(*args)
o = allocate
o.send(:new_from_data, *args)
o
end
# Checks if the packet is a QUERY packet
def query?
@header.opCode == Net::DNS::Header::QUERY
end
# Return the packet object in binary data, suitable
# for sending across a network stream.
#
# packet_data = packet.data
# puts "Packet is #{packet_data.size} bytes long"
#
def data
qdcount=ancount=nscount=arcount=0
data = @header.data
headerlength = data.length
@question.each do |question|
data += question.data
qdcount += 1
end
@answer.each do |rr|
data += rr.data#(data.length)
ancount += 1
end
@authority.each do |rr|
data += rr.data#(data.length)
nscount += 1
end
@additional.each do |rr|
data += rr.data#(data.length)
arcount += 1
end
@header.qdCount = qdcount
@header.anCount = ancount
@header.nsCount = nscount
@header.arCount = arcount
@header.data + data[Net::DNS::HFIXEDSZ..data.size]
end
# Same as Net::DNS::Packet#data, but implements name compression
# (see RFC1025) for a considerable save of bytes.
#
# packet = Net::DNS::Packet.new("www.example.com")
# puts "Size normal is #{packet.data.size} bytes"
# puts "Size compressed is #{packet.data_comp.size} bytes"
#
def data_comp
offset = 0
compnames = {}
qdcount=ancount=nscount=arcount=0
data = @header.data
headerlength = data.length
@question.each do |question|
str,offset,names = question.data
data += str
compnames.update(names)
qdcount += 1
end
@answer.each do |rr|
str,offset,names = rr.data(offset,compnames)
data += str
compnames.update(names)
ancount += 1
end
@authority.each do |rr|
str,offset,names = rr.data(offset,compnames)
data += str
compnames.update(names)
nscount += 1
end
@additional.each do |rr|
str,offset,names = rr.data(offset,compnames)
data += str
compnames.update(names)
arcount += 1
end
@header.qdCount = qdcount
@header.anCount = ancount
@header.nsCount = nscount
@header.arCount = arcount
@header.data + data[Net::DNS::HFIXEDSZ..data.size]
end
# Inspect method
def inspect
retval = ""
if @answerfrom != "0.0.0.0:0" and @answerfrom
retval += ";; Answer received from #@answerfrom (#{@answersize} bytes)\n;;\n"
end
retval += ";; HEADER SECTION\n"
retval += @header.inspect
retval += "\n"
section = (@header.opCode == "UPDATE") ? "ZONE" : "QUESTION"
retval += ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '' : 's'}):\n"
@question.each do |qr|
retval += ";; " + qr.inspect + "\n"
end
unless @answer.size == 0
retval += "\n"
section = (@header.opCode == "UPDATE") ? "PREREQUISITE" : "ANSWER"
retval += ";; #{section} SECTION (#{@header.anCount} record#{@header.anCount == 1 ? '' : 's'}):\n"
@answer.each do |rr|
retval += rr.inspect + "\n"
end
end
unless @authority.size == 0
retval += "\n"
section = (@header.opCode == "UPDATE") ? "UPDATE" : "AUTHORITY"
retval += ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '' : 's'}):\n"
@authority.each do |rr|
retval += rr.inspect + "\n"
end
end
unless @additional.size == 0
retval += "\n"
retval += ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '' : 's'}):\n"
@additional.each do |rr|
retval += rr.inspect + "\n"
end
end
retval
end
# Wrapper to Header#truncated?
#
def truncated?
@header.truncated?
end
# Assing a Net::DNS::Header object to a Net::DNS::Packet
# instance.
#
def header=(object)
if object.kind_of? Net::DNS::Header
@header = object
else
raise PacketArgumentError, "Argument must be a Net::DNS::Header object"
end
end
# Assign a Net::DNS::Question object, or an array of
# Questions objects, to a Net::DNS::Packet instance.
#
def question=(object)
case object
when Array
if object.all? {|x| x.kind_of? Net::DNS::Question}
@question = object
else
raise PacketArgumentError, "Some of the elements is not an Net::DNS::Question object"
end
when Net::DNS::Question
@question = [object]
else
raise PacketArgumentError, "Invalid argument, not a Question object nor an array of objects"
end
end
# Assign a Net::DNS::RR object, or an array of
# RR objects, to a Net::DNS::Packet instance answer
# section.
#
def answer=(object)
case object
when Array
if object.all? {|x| x.kind_of? Net::DNS::RR}
@answer = object
else
raise PacketArgumentError, "Some of the elements is not an Net::DNS::RR object"
end
when Net::DNS::RR
@answer = [object]
else
raise PacketArgumentError, "Invalid argument, not a RR object nor an array of objects"
end
end
# Assign a Net::DNS::RR object, or an array of
# RR objects, to a Net::DNS::Packet instance additional
# section.
#
def additional=(object)
case object
when Array
if object.all? {|x| x.kind_of? Net::DNS::RR}
@additional = object
else
raise PacketArgumentError, "Some of the elements is not an Net::DNS::RR object"
end
when Net::DNS::RR
@additional = [object]
else
raise PacketArgumentError, "Invalid argument, not a RR object nor an array of objects"
end
end
# Assign a Net::DNS::RR object, or an array of
# RR objects, to a Net::DNS::Packet instance authority
# section.
#
def authority=(object)
case object
when Array
if object.all? {|x| x.kind_of? Net::DNS::RR}
@authority = object
else
raise PacketArgumentError, "Some of the elements is not an Net::DNS::RR object"
end
when Net::DNS::RR
@authority = [object]
else
raise PacketArgumentError, "Invalid argument, not a RR object nor an array of objects"
end
end
# Iterate for every address in the +answer+ section of a
# Net::DNS::Packet object.
#
# packet.each_address do |ip|
# ping ip.to_s
# end
#
# As you can see in the documentation for Net::DNS::RR::A class,
# the address returned is an instance of IPAddr class.
#
def each_address
@answer.each do |elem|
next unless elem.class == Net::DNS::RR::A
yield elem.address
end
end
# Iterate for every nameserver in the +answer+ section of a
# Net::DNS::Packet object.
#
# packet.each_nameserver do |ns|
# puts "Nameserver found: #{ns}"
# end
#
def each_nameserver
@answer.each do |elem|
next unless elem.class == Net::DNS::RR::NS
yield elem.nsdname
end
end
# Iterate for every exchange record in the +answer+ section
# of a Net::DNS::Packet object.
#
# packet.each_mx do |pref,name|
# puts "Mail exchange #{name} has preference #{pref}"
# end
#
def each_mx
@answer.each do |elem|
next unless elem.class == Net::DNS::RR::MX
yield elem.preference,elem.exchange
end
end
# Iterate for every canonical name in the +answer+ section
# of a Net::DNS::Packet object.
#
# packet.each_cname do |cname|
# puts "Canonical name: #{cname}"
# end
#
def each_cname
@answer.each do |elem|
next unless elem.class == Net::DNS::RR::CNAME
yield elem.cname
end
end
# Iterate for every pointer in the +answer+ section of a
# Net::DNS::Packet object.
#
# packet.each_ptr do |ptr|
# puts "Pointer for resource: #{ptr}"
# end
#
def each_ptr
@answer.each do |elem|
next unless elem.class == Net::DNS::RR::PTR
yield elem.ptrdname
end
end
# Chacks whether a query has returned a NXDOMAIN error,
# meaning the domain name queried doesn't exists.
#
# %w[a.com google.com ibm.com d.com].each do |domain|
# response = Net::DNS::Resolver.new.send(domain)
# puts "#{domain} doesn't exist" if response.nxdomain?
# end
# #=> a.com doesn't exist
# #=> d.com doesn't exist
#
def nxdomain?
header.rCode == Net::DNS::Header::NAME
end
private
# New packet from binary data
def new_from_data(data, from = nil)
unless from
if data.kind_of? Array
data,from = data
else
from = [0,0,"0.0.0.0","unknown"]
end
end
@answerfrom = from[2] + ":" + from[1].to_s
@answersize = data.size
@logger = Logger.new $stdout
@logger.level = $DEBUG ? Logger::DEBUG : Logger::WARN
#------------------------------------------------------------
# Header section
#------------------------------------------------------------
offset = Net::DNS::HFIXEDSZ
@header = Net::DNS::Header.parse(data[0..offset-1])
@logger.debug ";; HEADER SECTION"
@logger.debug @header.inspect
#------------------------------------------------------------
# Question section
#------------------------------------------------------------
section = @header.opCode == "UPDATE" ? "ZONE" : "QUESTION"
@logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '': 's'})"
@question = []
@header.qdCount.times do
qobj,offset = parse_question(data,offset)
@question << qobj
@logger.debug ";; #{qobj.inspect}"
end
#------------------------------------------------------------
# Answer/prerequisite section
#------------------------------------------------------------
section = @header.opCode == "UPDATE" ? "PREREQUISITE" : "ANSWER"
@logger.debug ";; #{section} SECTION (#{@header.qdCount} record#{@header.qdCount == 1 ? '': 's'})"
@answer = []
@header.anCount.times do
rrobj,offset = Net::DNS::RR.parse_packet(data,offset)
@answer << rrobj
@logger.debug rrobj.inspect
end
#------------------------------------------------------------
# Authority/update section
#------------------------------------------------------------
section = @header.opCode == "UPDATE" ? "UPDATE" : "AUTHORITY"
@logger.debug ";; #{section} SECTION (#{@header.nsCount} record#{@header.nsCount == 1 ? '': 's'})"
@authority = []
@header.nsCount.times do
rrobj,offset = Net::DNS::RR.parse_packet(data,offset)
@authority << rrobj
@logger.debug rrobj.inspect
end
#------------------------------------------------------------
# Additional section
#------------------------------------------------------------
@logger.debug ";; ADDITIONAL SECTION (#{@header.arCount} record#{@header.arCount == 1 ? '': 's'})"
@additional = []
@header.arCount.times do
rrobj,offset = Net::DNS::RR.parse_packet(data,offset)
@additional << rrobj
@logger.debug rrobj.inspect
end
end # new_from_data
# Parse question section
def parse_question(data,offset)
size = (dn_expand(data,offset)[1]-offset) + 2*Net::DNS::INT16SZ
return [Net::DNS::Question.parse(data[offset,size]), offset+size]
rescue StandardError => err
raise PacketError, "Caught exception, maybe packet malformed => #{err}"
end
end # class Packet
end # module DNS
end # module Net
class PacketError < StandardError # :nodoc:
end
class PacketArgumentError < ArgumentError # :nodoc:
end

195
lib/net/dns/question.rb Normal file
View File

@ -0,0 +1,195 @@
#---
# $Id: Question.rb,v 1.8 2006/07/28 19:00:03 bluemonk Exp $
#+++
require 'net/dns/dns'
require 'net/dns/names/names'
require 'net/dns/rr/types'
require 'net/dns/rr/classes'
module Net # :nodoc:
module DNS
#
# =Name
#
# Net::DNS::Question - DNS packet question class
#
# =Synopsis
#
# require 'net/dns/question'
#
# =Description
#
# This class represent the Question portion of a DNS packet. The number
# of question entries is stored in the +qdCount+ variable of an Header
# object.
#
# A new object can be created passing the name of the query and the type
# of answer desired, plus an optional argument containing the class:
#
# question = Net::DNS::Question.new("google.com.", Net::DNS::A)
# #=> "google.com. A IN"
#
# Alternatevly, a new object is created when processing a binary
# packet, as when an answer is received.
# To obtain the binary data from a question object you can use
# the method Question#data:
#
# question.data
# #=> "\006google\003com\000\000\001\000\001"
#
# A lot of methods were written to keep a compatibility layer with
# the Perl version of the library, as long as methods name which are
# more or less the same.
#
# =Error classes
#
# Some error classes has been defined for the Net::DNS::Header class,
# which are listed here to keep a light and browsable main documentation.
# We have:
#
# * QuestionArgumentError: generic argument error
# * QuestionNameError: an error in the +name+ part of a Question entry
#
# =Copyright
#
# Copyright (c) 2006 Marco Ceresa
#
# All rights reserved. This program is free software; you may redistribute
# it and/or modify it under the same terms as Ruby itself.
#
class Question
include Net::DNS::Names
# +name+ part of a Question entry
attr_reader :qName
# +type+ part of a Question entry
attr_reader :qType
# +class+ part of a Question entry
attr_reader :qClass
# Creates a new Net::DNS::Question object:
#
# question = Net::DNS::Question.new("example.com")
# #=> "example.com A IN"
# question = Net::DNS::Question.new("example.com", Net::DNS::MX)
# #=> "example.com MX IN"
# question = Net::DNS::Question.new("example.com", Net::DNS::TXT, Net::DNS::HS)
# #=> "example.com TXT HS"
# If not specified, +type+ and +cls+ arguments defaults
# to Net::DNS::A and Net::DNS::IN respectively.
#
def initialize(name,type=Net::DNS::A,cls=Net::DNS::IN)
@qName = check_name name
@qType = Net::DNS::RR::Types.new type
@qClass = Net::DNS::RR::Classes.new cls
end
# Return a new Net::DNS::Question object created by
# parsing binary data, such as an answer from the
# nameserver.
#
# question = Net::DNS::Question.parse(data)
# puts "Queried for #{question.qName} type #{question.qType.to_s}"
# #=> Queried for example.com type A
#
def self.parse(arg)
if arg.kind_of? String
o = allocate
o.send(:new_from_binary,arg)
o
else
raise QuestionArgumentError, "Wrong argument format, must be a String"
end
end
# Known inspect method with nice formatting
def inspect
if @qName.size > 29 then
len = @qName.size + 1
else
len = 29
end
[@qName,@qClass.to_s,@qType.to_s].pack("A#{len} A8 A8")
end
# Outputs binary data from a Question object
#
# question.data
# #=> "\006google\003com\000\000\001\000\001"
#
def data
[pack_name(@qName),@qType.to_i,@qClass.to_i].pack("a*nn")
end
# Return the binary data of the objects, plus an offset
# and an Hash with references to compressed names. For use in
# Net::DNS::Packet compressed packet creation.
#
def comp_data
arr = @qName.split(".")
str = pack_name(@qName)
string = ""
names = {}
offset = Net::DNS::HFIXEDSZ
arr.size.times do |i|
x = i+1
elem = arr[-x]
len = elem.size
string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
names[string] = offset
offset += len
end
offset += 2 * Net::DNS::INT16SZ
str += "\000"
[[str,@qType.to_i,@qClass.to_i].pack("a*nn"),offset,names]
end
private
def build_qName(str)
result = ""
offset = 0
loop do
len = str.unpack("@#{offset} C")[0]
break if len == 0
offset += 1
result += str[offset..offset+len-1]
result += "."
offset += len
end
result
end
def check_name(name)
name.strip!
if name =~ /[^\w\.\-_]/
raise QuestionNameError, "Question name #{name.inspect} not valid"
else
name
end
rescue
raise QuestionNameError, "Question name #{name.inspect} not valid"
end
def new_from_binary(data)
str,type,cls = data.unpack("a#{data.size-4}nn")
@qName = build_qName(str)
@qType = Net::DNS::RR::Types.new type
@qClass = Net::DNS::RR::Classes.new cls
rescue StandardError => e
raise QuestionArgumentError, "Invalid data: #{data.inspect}\n{e.backtrace}"
end
end # class Question
end # class DNS
end # module Net
class QuestionArgumentError < ArgumentError # :nodoc:
end
class QuestionNameError < StandardError # :nodoc:
end

1232
lib/net/dns/resolver.rb Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
require 'socket'
require 'ipaddr'
class RawSocket # :nodoc:
@@id_arr = []
def initialize(src_addr,dest_addr)
# Define socket
begin
@socket = Socket.new PF_INET, SOCK_RAW, IPPROTO_RAW
rescue SystemCallError => e
raise SystemCallError, "You must be root to use raw sockets! #{e}"
end
@socket.setsockopt IPPROTO_IP, IP_HDRINCL, 1
# Checks addresses
@src_addr = check_addr src_addr
@dest_addr = check_addr dest_addr
# Source and destination port are zero
@src_port = 0
@dest_port = 0
# Set correct protocol version in the header
@version = @dest_addr.ipv4? ? "0100" : "0110"
# Total lenght: must be overridden by subclasses
@tot_lenght = 20
# Protocol: must be overridden by subclasses
@protocol = 1 # ICMP by default
# Generate a new id
# @id = genID
@id = 1234
# Generate peer sockaddr
@to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
end
def send(payload = '')
packet = make_ip_header([[ @version+'0101', 'B8' ], # version, hlen
[ 0, 'C' ], # tos
[ @tot_lenght + payload.size, 'n' ], # total len
[ @id, 'n' ], # id
[ 0, 'n' ], # flags, offset
[ 64, 'C' ], # ttl
[ @protocol, 'C' ], # protocol
[ 0, 'n' ], # checksum
[ @src_addr.to_i, 'N' ], # source
[ @dest_addr.to_i, 'N' ], # destination
])
packet << make_transport_header(payload.size)
packet << [payload].pack("a*")
@socket.send(packet,0,@to)
end
private
def check_addr addr
case addr
when String
IPAddr.new addr
when IPAddr
addr
else
raise ArgumentError, "Wrong address format: #{addr}"
end
end
def check_port port
if (1..65535).include? port and port.kind_of? Integer
port
else
raise ArgumentError, "Port #{port} not valid"
end
end
def genID
while (@@id_arr.include?(q = rand(65535)))
end
@@id_arr.push(q)
q
end
def ipchecksum(data)
checksum = data.unpack("n*").inject(0) { |s, x| s + x }
((checksum >> 16) + (checksum & 0xffff)) ^ 0xffff
end
def make_ip_header(parts)
template = ''
data = []
parts.each do |part|
data += part[0..-2]
template << part[-1]
end
data_str = data.pack(template)
checksum = ipchecksum(data_str)
data[-3] = checksum
data.pack(template)
end
def make_transport_header
""
end
end
class UdpRawSocket < RawSocket # :nodoc:
def initialize(src_addr,src_port,dest_addr,dest_port)
super(src_addr,dest_addr)
# Check ports
@src_port = check_port src_port
@dest_port = check_port dest_port
# Total lenght: must be overridden by subclasses
@tot_lenght = 20 + 8 # 8 bytes => UDP Header
# Protocol: must be overridden by subclasses
@protocol = 17 # UDP protocol
@to = Socket.pack_sockaddr_in @dest_port, @dest_addr.to_s
end
private
def make_udp_header(parts)
template = ''
data = []
parts.each do |part|
data += part[0..-2]
template << part[-1]
end
data.pack(template)
end
def make_transport_header(pay_size)
make_udp_header([
[ @src_port, 'n'], # source port
[ @dest_port, 'n' ], # destination port
[ 8 + pay_size, 'n' ], # len
[ 0, 'n' ] # checksum (mandatory)
])
end
end

View File

@ -0,0 +1,73 @@
require 'timeout'
module SecondsHandle #:nodoc: all
def transform(secs)
case secs
when 0
to_s
when 1..59
"#{secs} seconds"
when 60..3559
"#{secs/60} minutes and #{secs%60} seconds"
else
hours = secs/3600
secs -= (hours*3600)
"#{hours} hours, #{secs/60} minutes and #{secs%60} seconds"
end
end
end
class DnsTimeout # :nodoc: all
include SecondsHandle
def initialize(seconds)
if seconds.is_a? Numeric and seconds >= 0
@timeout = seconds
else
raise DnsTimeoutArgumentError, "Invalid value for tcp timeout"
end
end
def to_s
if @timeout == 0
@output
else
@timeout.to_s
end
end
def pretty_to_s
transform(@timeout)
end
def timeout
unless block_given?
raise DnsTimeoutArgumentError, "Block required but missing"
end
if @timeout == 0
yield
else
return Timeout.timeout(@timeout) do
yield
end
end
end
end
class TcpTimeout < DnsTimeout # :nodoc: all
def initialize(seconds)
@output = "infinite"
super(seconds)
end
end
class UdpTimeout < DnsTimeout # :nodoc: all
def initialize(seconds)
@output = "not defined"
super(seconds)
end
end
class DnsTimeoutArgumentError < ArgumentError # :nodoc: all
end

406
lib/net/dns/rr.rb Normal file
View File

@ -0,0 +1,406 @@
#
# $Id: RR.rb,v 1.19 2006/07/28 07:33:36 bluemonk Exp $
#
require 'net/dns/names/names'
require 'net/dns/rr/types'
require 'net/dns/rr/classes'
%w[a ns mx cname txt soa ptr aaaa mr].each do |file|
require "net/dns/rr/#{file}"
end
module Net # :nodoc:
module DNS
# =Name
#
# Net::DNS::RR - DNS Resource Record class
#
# =Synopsis
#
# require 'net/dns/rr'
#
# =Description
#
# The Net::DNS::RR is the base class for DNS Resource
# Record (RR) objects. A RR is a pack of data that represents
# resources for a DNS zone. The form in which this data is
# shows can be drawed as follow:
#
# "name ttl class type data"
#
# The +name+ is the name of the resource, like an canonical
# name for an +A+ record (internet ip address). The +ttl+ is the
# time to live, expressed in seconds. +type+ and +class+ are
# respectively the type of resource (+A+ for ip addresses, +NS+
# for nameservers, and so on) and the class, which is almost
# always +IN+, the Internet class. At the end, +data+ is the
# value associated to the name for that particular type of
# resource record. An example:
#
# # A record for IP address
# "www.example.com 86400 IN A 172.16.100.1"
#
# # NS record for name server
# "www.example.com 86400 IN NS ns.example.com"
#
# A new RR object can be created in 2 ways: passing a string
# such the ones above, or specifying each field as the pair
# of an hash. See the Net::DNS::RR.new method for details.
#
# =Error classes
#
# Some error classes has been defined for the Net::DNS::RR class,
# which are listed here to keep a light and browsable main documentation.
# We have:
#
# * RRArgumentError: Generic argument error for class Net::DNS::RR
# * RRDataError: Error in parsing binary data, maybe from a malformed packet
#
# =Copyright
#
# Copyright (c) 2006 Marco Ceresa
#
# All rights reserved. This program is free software; you may redistribute
# it and/or modify it under the same terms as Ruby itself.
#
class RR
include Net::DNS::Names
# Regexp matching an RR string
RR_REGEXP = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s+(" +
Net::DNS::RR::Classes.regexp +
"|CLASS\\d+)?\\s*(" +
Net::DNS::RR::Types.regexp +
"|TYPE\\d+)?\\s*(.*)$", Regexp::IGNORECASE)
# Dimension of the sum of class, type, TTL and rdlength fields in a
# RR portion of the packet, in bytes
RRFIXEDSZ = 10
# Name of the RR
attr_reader :name
# TTL time (in seconds) of the RR
attr_reader :ttl
# Data belonging to that appropriate class,
# not to be used (use real accessors instead)
attr_reader :rdata
# Create a new instance of Net::DNS::RR class, or an instance of
# any of the subclass of the appropriate type.
#
# Argument can be a string or an hash. With a sting, we can pass
# a RR resource record in the canonical format:
#
# a = Net::DNS::RR.new("foo.example.com. 86400 A 10.1.2.3")
# mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
# cname = Net::DNS::RR.new("www.example.com 300 IN CNAME www1.example.com")
# txt = Net::DNS::RR.new('baz.example.com 3600 HS TXT "text record"')
#
# Incidentally, +a+, +mx+, +cname+ and +txt+ objects will be instances of
# respectively Net::DNS::RR::A, Net::DNS::RR::MX, Net::DNS::RR::CNAME and
# Net::DNS::RR::TXT classes.
#
# The name and RR data are required; all other informations are optional.
# If omitted, the +TTL+ defaults to 10800, +type+ default to +A+ and the RR class
# defaults to +IN+. Omitting the optional fields is useful for creating the
# empty RDATA sections required for certain dynamic update operations.
# All names must be fully qualified. The trailing dot (.) is optional.
#
# The preferred method is however passing an hash with keys and values:
#
# rr = Net::DNS::RR.new(
# :name => "foo.example.com",
# :ttl => 86400,
# :cls => "IN",
# :type => "A",
# :address => "10.1.2.3"
# )
#
# rr = Net::DNS::RR.new(
# :name => "foo.example.com",
# :rdata => "10.1.2.3"
# )
#
# Name and data are required; all the others fields are optionals like
# we've seen before. The data field can be specified either with the
# right name of the resource (+:address+ in the example above) or with
# the generic key +:rdata+. Consult documentation to find the exact name
# for the resource in each subclass.
#
def initialize(arg)
case arg
when String
instance = new_from_string(arg)
when Hash
instance = new_from_hash(arg)
else
raise RRArgumentError, "Invalid argument, must be a RR string or an hash of values"
end
if @type.to_s == "ANY"
@cls = Net::DNS::RR::Classes.new("IN")
end
build_pack
set_type
instance
end
# Return a new RR object of the correct type (like Net::DNS::RR::A
# if the type is A) from a binary string, usually obtained from
# network stream.
#
# This method is used when parsing a binary packet by the Packet
# class.
#
def RR.parse(data)
o = allocate
obj,offset = o.send(:new_from_binary, data, 0)
return obj
end
# Same as RR.parse, but takes an entire packet binary data to
# perform name expansion. Default when analizing a packet
# just received from a network stream.
#
# Return an instance of appropriate class and the offset
# pointing at the end of the data parsed.
#
def RR.parse_packet(data,offset)
o = allocate
o.send(:new_from_binary,data,offset)
end
# Return the RR object in binary data format, suitable
# for using in network streams, with names compressed.
# Must pass as arguments the offset inside the packet
# and an hash of compressed names.
#
# This method is to be used in other classes and is
# not intended for user space programs.
#
# TO FIX in one of the future releases
#
def comp_data(offset,compnames)
type,cls = @type.to_i, @cls.to_i
str,offset,names = dn_comp(@name,offset,compnames)
str += [type,cls,@ttl,@rdlength].pack("n2 N n")
offset += Net::DNS::RRFIXEDSZ
return str,offset,names
end
# Return the RR object in binary data format, suitable
# for using in network streams.
#
# raw_data = rr.data
# puts "RR is #{raw_data.size} bytes long"
#
def data
type,cls = @type.to_i, @cls.to_i
str = pack_name(@name)
return str + [type,cls,@ttl,@rdlength].pack("n2 N n") + get_data
end
# Canonical inspect method
#
# mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
# #=> example.com. 7200 IN MX 10 mailhost.example.com.
#
def inspect
data = get_inspect
# Returns the preformatted string
if @name.size < 24
[@name, @ttl.to_s, @cls.to_s, @type.to_s,
data].pack("A24 A8 A8 A8 A*")
else
to_a.join(" ")
end
end
# Returns the RR in a string format.
#
# mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
# mx.to_s
# #=> "example.com. 7200 IN MX 10 mailhost.example.com."
#
def to_s
"#{self.inspect}"
end
# Returns an array with all the fields for the RR record.
#
# mx = Net::DNS::RR.new("example.com. 7200 MX 10 mailhost.example.com.")
# mx.to_a
# #=> ["example.com.",7200,"IN","MX","10 mailhost.example.com."]
#
def to_a
[@name,@ttl,@cls.to_s,@type.to_s,get_inspect]
end
# Type accessor
def type
@type.to_s
end
# Class accessor
def cls
@cls.to_s
end
private
#---
# New RR with argument in string form
#---
def new_from_string(rrstring)
unless rrstring =~ RR_REGEXP
raise RRArgumentError,
"Format error for RR string (maybe CLASS and TYPE not valid?)"
end
# Name of RR - mandatory
begin
@name = $1.downcase
rescue NoMethodError
raise RRArgumentError, "Missing name field in RR string #{rrstring}"
end
# Time to live for RR, default 3 hours
@ttl = $2 ? $2.to_i : 10800
# RR class, default to IN
@cls = Net::DNS::RR::Classes.new $3
# RR type, default to A
@type = Net::DNS::RR::Types.new $4
# All the rest is data
@rdata = $5 ? $5.strip : ""
if self.class == Net::DNS::RR
(eval "Net::DNS::RR::#@type").new(rrstring)
else
subclass_new_from_string(@rdata)
self.class
end
end
def new_from_hash(args)
# Name field is mandatory
unless args.has_key? :name
raise RRArgumentError, "RR argument error: need at least RR name"
end
@name = args[:name].downcase
@ttl = args[:ttl] ? args[:ttl].to_i : 10800 # Default 3 hours
@type = Net::DNS::RR::Types.new args[:type]
@cls = Net::DNS::RR::Classes.new args[:cls]
@rdata = args[:rdata] ? args[:rdata].strip : ""
@rdlength = args[:rdlength] || @rdata.size
if self.class == Net::DNS::RR
(eval "Net::DNS::RR::#@type").new(args)
else
hash = args - [:name,:ttl,:type,:cls]
if hash.has_key? :rdata
subclass_new_from_string(hash[:rdata])
else
subclass_new_from_hash(hash)
end
self.class
end
end # new_from_hash
def new_from_binary(data,offset)
if self.class == Net::DNS::RR
temp = dn_expand(data,offset)[1]
type = Net::DNS::RR::Types.new data.unpack("@#{temp} n")[0]
(eval "Net::DNS::RR::#{type}").parse_packet(data,offset)
else
@name,offset = dn_expand(data,offset)
rrtype,cls,@ttl,@rdlength = data.unpack("@#{offset} n2 N n")
@type = Net::DNS::RR::Types.new rrtype
@cls = Net::DNS::RR::Classes.new cls
offset += RRFIXEDSZ
offset = subclass_new_from_binary(data,offset)
build_pack
set_type
return [self,offset]
end
# rescue StandardError => err
# raise RRDataError, "Caught exception, maybe packet malformed: #{err}"
end
# Methods to be overridden by subclasses
def subclass_new_from_array(arr)
end
def subclass_new_from_string(str)
end
def subclass_new_from_hash(hash)
end
def subclass_new_from_binary(data,offset)
end
def build_pack
end
def set_type
end
def get_inspect
@rdata
end
def get_data
@rdata
end
# NEW new method :)
def self.new(*args)
o = allocate
obj = o.send(:initialize,*args)
if self == Net::DNS::RR
return obj
else
return o
end
end
end # class RR
end # module DNS
end # module Net
class RRArgumentError < ArgumentError # :nodoc:
end
class RRDataError < StandardError # :nodoc:
end
module ExtendHash # :nodoc:
# Performs a sort of group difference
# operation on hashes or arrays
#
# a = {:a=>1,:b=>2,:c=>3}
# b = {:a=>1,:b=>2}
# c = [:a,:c]
# a-b #=> {:c=>3}
# a-c #=> {:b=>2}
#
def -(oth)
case oth
when Hash
delete_if {|k,v| oth.has_key? k}
when Array
delete_if {|k,v| oth.include? k}
end
end
end
class Hash # :nodoc:
include ExtendHash
end

121
lib/net/dns/rr/a.rb Normal file
View File

@ -0,0 +1,121 @@
##
#
# Net::DNS::RR::A
#
# $id$
#
##
require 'ipaddr'
module Net # :nodoc:
module DNS
class RR
# =Name
#
# Net::DNS::RR::A DNS A resource record
#
# =Synopsis
#
# require "net/dns/rr"
#
# =Description
#
# Net::DNS::RR::A is the class to handle resource records of type A, the
# most common in a DNS query. Its resource data is an IPv4 (i.e. 32 bit
# long) address, hold in the instance variable +address+.
# a = Net::DNS::RR::A.new("localhost.movie.edu. 360 IN A 127.0.0.1")
#
# a = Net::DNS::RR::A.new(:name => "localhost.movie.edu.",
# :ttl => 360,
# :cls => Net::DNS::IN,
# :type => Net::DNS::A,
# :address => "127.0.0.1")
#
# When computing binary data to trasmit the RR, the RDATA section is an
# Internet address expressed as four decimal numbers separated by dots
# without any imbedded spaces (e.g.,"10.2.0.52" or "192.0.5.6").
#
class A < RR
attr_reader :address
# Assign to the RR::A object a new IPv4 address, which can be in the
# form of a string or an IPAddr object
#
# a.address = "192.168.0.1"
# a.address = IPAddr.new("10.0.0.1")
#
def address=(addr)
@address = check_address addr
build_pack
end # address=
private
def check_address(addr)
address = ""
case addr
when String
address = IPAddr.new addr
when Integer # Address in numeric form
tempAddr = [(addr>>24),(addr>>16)&0xFF,(addr>>8)&0xFF,addr&0xFF]
tempAddr = tempAddr.collect {|x| x.to_s}.join(".")
address = IPAddr.new tempAddr
when IPAddr
address = addr
else
raise RRArgumentError, "Unknown address type: #{addr}"
end
raise RRArgumentError, "Must specify an IPv4 address" unless address.ipv4?
address
rescue ArgumentError
raise RRArgumentError, "Invalid address #{addr}"
end
def build_pack
@address_pack = @address.hton
@rdlength = @address_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("A")
end
def get_data
@address_pack
end
def get_inspect
"#@address"
end
def subclass_new_from_hash(args)
if args.has_key? :address
@address = check_address args[:address]
elsif args.has_key? :rdata
@address = check_address args[:rdata]
else
# Address field is mandatory
raise RRArgumentError, ":address field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@address = check_address(str)
end
def subclass_new_from_binary(data,offset)
a,b,c,d = data.unpack("@#{offset} CCCC")
@address = IPAddr.new "#{a}.#{b}.#{c}.#{d}"
return offset + 4
end
end # class A
end # class RR
end # module DNS
end # module Net

92
lib/net/dns/rr/aaaa.rb Normal file
View File

@ -0,0 +1,92 @@
##
#
# Net::DNS::RR::AAAA
#
# $id$
#
##
require 'ipaddr'
module Net
module DNS
class RR
#
# RR type AAAA
#
class AAAA < RR
attr_reader :address
# Assign to the RR::AAAA object a new IPv6 address, which can be in the
# form of a string or an IPAddr object
#
# a.address = "::1"
# a.address = IPAddr.new("::1")
#
def address=(addr)
@address = check_address addr
build_pack
end # address=
private
def check_address(addr)
address = ""
case addr
when String
address = IPAddr.new addr
when IPAddr
address = addr
else
raise RRArgumentError, "Unknown address type: #{addr.inspect}"
end
raise RRArgumentError, "Must specify an IPv6 address" unless address.ipv6?
address
rescue ArgumentError
raise RRArgumentError, "Invalid address #{addr.inspect}"
end
def build_pack
@address_pack = @address.hton
@rdlength = @address_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("AAAA")
end
def get_data
@address_pack
end
def get_inspect
"#@address"
end
def subclass_new_from_hash(args)
if args.has_key? :address
@address = check_address args[:address]
else
raise RRArgumentError, ":address field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@address = check_address(str)
end
def subclass_new_from_binary(data,offset)
arr = data.unpack("@#{offset} n8")
@address = IPAddr.new sprintf("%x:%x:%x:%x:%x:%x:%x:%x",*arr)
return offset + 16
end
end # class AAAA
end # class RR
end # module DNS
end # module Net

148
lib/net/dns/rr/classes.rb Normal file
View File

@ -0,0 +1,148 @@
module Net # :nodoc:
module DNS
class RR
#
# This is an auxiliary class to hadle RR class field in a DNS packet.
#
class Classes
# An hash with the values of each RR class stored with the
# respective id number
Classes = {
'IN' => 1, # RFC 1035
'CH' => 3, # RFC 1035
'HS' => 4, # RFC 1035
'NONE' => 254, # RFC 2136
'ANY' => 255, # RFC 1035
}
# The default value when class is nil in Resource Records
@@default = Classes["IN"]
# Be able to control the default class to assign when
# cls argument is +nil+. Default to +IN+
def self.default=(str)
if Classes.has_key? str
@@default = Classes[str]
else
raise ClassArgumentError, "Unknown class #{str}"
end
end
# Checks whether +cls+ is a valid RR class.
def self.valid?(cls)
case cls
when String
return Classes.has_key?(cls)
when Fixnum
return Classes.invert.has_key?(cls)
else
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
end
end
# Returns the class in string format, as "IN" or "CH",
# given the numeric value
def self.to_str(cls)
case cls
when Fixnum
if Classes.invert.has_key? cls
return Classes.invert[cls]
else
raise ClassArgumentError, "Unknown class number #{cls}"
end
else
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
end
end
# Gives in output the keys from the +Classes+ hash
# in a format suited for regexps
def self.regexp
Classes.keys.join("|")
end
# Creates a new object representing an RR class. Performs some
# checks on the argument validity too. Il +cls+ is +nil+, the
# default value is +ANY+ or the one set with Classes.default=
def initialize(cls)
case cls
when String
# type in the form "A" or "NS"
new_from_string(cls.upcase)
when Fixnum
# type in numeric form
new_from_num(cls)
when nil
# default type, control with Classes.default=
@str = Classes.invert[@@default]
@num = @@default
else
raise ClassArgumentError, "Wrong cls class: #{cls.class}"
end
end
# Constructor for string data class,
# *PRIVATE* method
def new_from_string(cls)
case cls
when /^CLASS\\d+/
# TODO!!!
else
# String with name of class
if Classes.has_key? cls
@str = cls
@num = Classes[cls]
else
raise ClassesArgumentError, "Unknown cls #{cls}"
end
end
end
# Contructor for numeric data class
# *PRIVATE* method
def new_from_num(cls)
if Classes.invert.has_key? cls
@num = cls
@str = Classes.invert[cls]
else
raise ClassesArgumentError, "Unkown cls number #{cls}"
end
end
# Returns the class in number format
# (default for normal use)
def inspect
@num
end
# Returns the class in string format,
# i.d. "IN" or "CH" or such a string.
def to_s
@str
end
# Returns the class in numeric format,
# usable by the pack methods for data transfers
def to_i
@num.to_i
end
# Should be used only for testing purpouses
def to_str
@num.to_s
end
private :new_from_num, :new_from_string
end # class Classes
end # class RR
end # module DNS
end # module Net
class ClassArgumentError < ArgumentError # :nodoc:
end

69
lib/net/dns/rr/cname.rb Normal file
View File

@ -0,0 +1,69 @@
##
#
# Net::DNS::RR::CNAME
#
# $Id: CNAME.rb,v 1.7 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type CNAME
#------------------------------------------------------------
class CNAME < RR
attr_reader :cname
private
def check_name(name)
unless name =~ /(\w\.?)+\s*$/ and name =~ /[a-zA-Z]/
raise RRArgumentError, "Canonical Name not valid: #{name}"
end
name
end
def build_pack
@cname_pack = pack_name(@cname)
@rdlength = @cname_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("CNAME")
end
def get_data
@cname_pack
end
def get_inspect
"#@cname"
end
def subclass_new_from_hash(args)
if args.has_key? :cname
@cname = check_name args[:cname]
else
raise RRArgumentError, ":cname field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@cname = check_name(str)
end
def subclass_new_from_binary(data,offset)
@cname,offset = dn_expand(data,offset)
return offset
end
end # class CNAME
end # class RR
end # module DNS
end # module Net

74
lib/net/dns/rr/hinfo.rb Normal file
View File

@ -0,0 +1,74 @@
##
#
# Net::DNS::RR::HINFO
#
# $Id: HINFO.rb,v 1.4 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type HINFO
#------------------------------------------------------------
class HINFO < RR
attr_reader :cpu, :os
private
def check_hinfo(str)
if str.strip =~ /^["'](.*?)["']\s+["'](.*?)["']$/
return $1,$2
else
raise RRArgumentError, "HINFO section not valid: #{str.inspect}"
end
end
def build_pack
@hinfo_pack = [@cpu.size].pack("C") + @cpu
@hinfo_pack += [@os.size].pack("C") + @os
@rdlength = @hinfo_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("HINFO")
end
def get_data
@hinfo_pack
end
def get_inspect
"#@cpu #@os"
end
def subclass_new_from_hash(args)
if args.has_key? :cpu and args.has_key? :os
@cpu = args[:cpu]
@os = args[:os]
else
raise RRArgumentError, ":cpu and :os fields are mandatory but missing"
end
end
def subclass_new_from_string(str)
@cpu,@os = check_hinfo(str)
end
def subclass_new_from_binary(data,offset)
len = data.unpack("@#{offset} C")[0]
@cpu = data[offset+1..offset+1+len]
offset += len+1
len = @data.unpack("@#{offset} C")[0]
@os = data[offset+1..offset+1+len]
return offset += len+1
end
end # class HINFO
end # class RR
end # module DNS
end # module Net

68
lib/net/dns/rr/mr.rb Normal file
View File

@ -0,0 +1,68 @@
##
#
# Net::DNS::RR::MR
#
# $Id: MR.rb,v 1.4 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type MR
#------------------------------------------------------------
class MR < RR
attr_reader :newname
private
def check_name(name)
unless name =~ /(\w\.?)+\s*$/
raise RRArgumentError, "Name not valid: #{name.inspect}"
end
name
end
def build_pack
@newname_pack = pack_name(@newname)
@rdlength = @newname_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("MR")
end
def get_data
@newname_pack
end
def get_inspect
"#@newname"
end
def subclass_new_from_hash(args)
if args.has_key? :newname
@newname = check_name args[:newname]
else
raise RRArgumentError, ":newname field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@newname = check_name(str)
end
def subclass_new_from_array(data,offset)
@newname = dn_expand(data,offset)
return offset
end
end # class MR
end # class RR
end # module DNS
end # module Net

74
lib/net/dns/rr/mx.rb Normal file
View File

@ -0,0 +1,74 @@
##
#
# Net::DNS::RR::MX
#
# $Id: MX.rb,v 1.8 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type MX
#------------------------------------------------------------
class MX < RR
attr_reader :preference, :exchange
private
def check_mx(str)
if str.strip =~ /^(\d+)\s+(\S+)$/
return $1.to_i,$2
else
raise RRArgumentError, "MX section not valid"
end
end
def build_pack
@mx_pack = [@preference].pack("n") + pack_name(@exchange)
@rdlength = @mx_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("MX")
end
def get_data
@mx_pack
end
def get_inspect
"#@preference #@exchange"
end
def subclass_new_from_hash(args)
if args.has_key? :preference and args.has_key? :exchange
@preference = args[0][:preference].to_i
@exchange = args[0][:exchange]
else
raise RRArgumentError, ":preference and :exchange fields are mandatory but missing"
end
end
def subclass_new_from_string(str)
@preference,@exchange = check_mx(str)
end
def subclass_new_from_binary(data,offset)
@preference = data.unpack("@#{offset} n")[0]
offset += 2
@exchange,offset = dn_expand(data,offset)
return offset
end
end # class MX
end # class RR
end # module DNS
end # module Net

70
lib/net/dns/rr/ns.rb Normal file
View File

@ -0,0 +1,70 @@
##
#
# Net::DNS::RR::NS
#
# $Id: NS.rb,v 1.8 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type NS
#------------------------------------------------------------
class NS < RR
attr_reader :nsdname
private
def check_name(name)
unless name =~ /(\w\.?)+\s*$/ and name =~ /[a-zA-Z]/
raise RRArgumentError, "NS Domain Name not valid: #{name}"
end
name
end
def build_pack
@nsdname_pack = pack_name(@nsdname)
@rdlength = @nsdname_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("NS")
end
def get_data
@nsdname_pack
end
def get_inspect
"#@nsdname"
end
def subclass_new_from_hash(args)
if args.has_key? :nsdname
@nsdname = check_name args[:nsdname]
else
raise RRArgumentError, ":nsdname field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@nsdname = check_name(str)
end
def subclass_new_from_binary(data,offset)
@nsdname,offset = dn_expand(data,offset)
return offset
end
end # class NS
end # class RR
end # module DNS
end # module Net

61
lib/net/dns/rr/null.rb Normal file
View File

@ -0,0 +1,61 @@
##
#
# Net::DNS::RR::NULL
#
# $Id: NULL.rb,v 1.5 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type NULL
#------------------------------------------------------------
class NULL < RR
attr_reader :null
private
def build_pack
@null_pack = @null
@rdlength = @null_pack.size
end
def set_type
@type = Net::DNS::RR::RRTypes.new("NULL")
end
def get_data
@null_pack
end
def get_inspect
"#@null"
end
def subclass_new_from_hash(args)
if args.has_key? :null
@null = args[:null]
else
raise RRArgumentError, ":null field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@null = str.strip
end
def subclass_new_from_binary(data,offset)
@null = data[offset..offset+@rdlength]
return offset + @rdlength
end
end # class NULL
end # class RR
end # module DNS
end # module Net

71
lib/net/dns/rr/ptr.rb Normal file
View File

@ -0,0 +1,71 @@
##
#
# Net::DNS::RR::PTR
#
# $Id: PTR.rb,v 1.5 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type PTR
#------------------------------------------------------------
class PTR < RR
# Getter for PTR resource
def ptr
@ptrdname.to_s
end
alias_method :ptrdname, :ptr
private
def check_ptr(str)
IPAddr.new str
rescue
raise RRArgumentError, "PTR section not valid"
end
def build_pack
@ptrdname_pack = pack_name(@ptrdname)
@rdlength = @ptrdname_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("PTR")
end
def get_data
@ptrdname_pack
end
def get_inspect
"#@ptrdname"
end
def subclass_new_from_hash(args)
if args.has_key? :ptrdname or args.has_key? :ptr
@ptrdname = args[0][:ptrdname]
else
raise RRArgumentError, ":ptrdname or :ptr field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@ptrdname = check_ptr(str)
end
def subclass_new_from_binary(data,offset)
@ptrdname,offset = dn_expand(data,offset)
return offset
end
end # class PTR
end # class RR
end # module DNS
end # module Net

85
lib/net/dns/rr/soa.rb Normal file
View File

@ -0,0 +1,85 @@
##
#
# Net::DNS::RR::SOA
#
# $Id: SOA.rb,v 1.4 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type SOA
#------------------------------------------------------------
class SOA < RR
attr_reader :mname, :rname, :serial, :refresh, :retry, :expire, :minimum
private
def build_pack
@soa_pack = pack_name(@mname)
@soa_pack += pack_name(@rname)
@soa_pack += [@serial,@refresh,@retry,@expire,@minimum].pack("N5")
end
def set_type
@type = Net::DNS::RR::Types.new("SOA")
end
def get_data
@soa_pack
end
def get_inspect
"#@mname #@rname #@serial #@refresh #@retry #@expire #@minimum"
end
def subclass_new_from_hash(args)
if args.has_key? :rdata
subclass_new_from_string(args[:rdata])
else
[:mname,:rname,:serial,:refresh,:retry,:expire,:minimum].each do |key|
raise RRArgumentError, "Missing field :#{key}" unless args.has_key? key
end
@mname = args[:mname] if valid? args[:mname]
@rname = args[:rname] if valid? args[:rname]
@serial = args[:serial] if number? args[:serial]
@refresh = args[:refresh] if number? args[:refresh]
@retry = args[:retry] if number? args[:retry]
@expire = args[:expire] if number? args[:expire]
@minimum = args[:minimum] if number? args[:minimum]
end
end
def number?(num)
if num.kind_of? Integer and num > 0
true
else
raise RRArgumentError, "Wrong format field: #{num} not a number or less than zero"
end
end
def subclass_new_from_string(str)
mname,rname,serial,refresh,ret,expire,minimum = str.strip.split(" ")
@mname = mname if valid? mname
@rname = rname if valid? rname
@serial,@refresh,@retry,@expire,@minimum = [serial,refresh,ret,expire,minimum].collect do |i|
i.to_i if valid? i.to_i
end
end
def subclass_new_from_binary(data,offset)
@mname,offset = dn_expand(data,offset)
@rname,offset = dn_expand(data,offset)
@serial,@refresh,@retry,@expire,@minimum = data.unpack("@#{offset} N5")
return offset + 5*Net::DNS::INT32SZ
end
end # class SOA
end # class RR
end # module DNS
end # module Net

57
lib/net/dns/rr/srv.rb Normal file
View File

@ -0,0 +1,57 @@
##
#
# Net::DNS::RR::SRV
#
# $Id$
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type SRV
#------------------------------------------------------------
class SRV < RR
attr_reader :priority, :weight, :port, :host
private
def build_pack
str = ""
end
def set_type
@type = Net::DNS::RR::Types.new("SRV")
end
def subclass_new_from_binary(data,offset)
off_end = offset + @rdlength
@priority, @weight, @port = data.unpack("@#{offset} n n n")
offset+=6
@host=[]
while offset < off_end
len = data.unpack("@#{offset} C")[0]
offset += 1
str = data[offset..offset+len-1]
offset += len
@host << str
end
@host=@host.join(".")
offset
end
end # class SRV
end # class RR
end # module DNS
end # module Net

72
lib/net/dns/rr/txt.rb Normal file
View File

@ -0,0 +1,72 @@
##
#
# Net::DNS::RR::TXT
#
# $Id: TXT.rb,v 1.4 2006/07/28 07:33:36 bluemonk Exp $
#
##
module Net
module DNS
class RR
#------------------------------------------------------------
# RR type TXT
#------------------------------------------------------------
class TXT < RR
attr_reader :txt
private
def build_pack
str = ""
@txt.split(" ").each do |txt|
str += [txt.length,txt].pack("C a*")
end
@txt_pack = str
@rdlength = @txt_pack.size
end
def set_type
@type = Net::DNS::RR::Types.new("TXT")
end
def get_data
@txt_pack
end
def subclass_new_from_hash(args)
if args.has_key? :txt
@txt = args[:txt].strip
else
raise RRArgumentError, ":txt field is mandatory but missing"
end
end
def subclass_new_from_string(str)
@txt = str.strip
end
def subclass_new_from_binary(data,offset)
off_end = offset + @rdlength
@txt = ""
while offset < off_end
len = data.unpack("@#{offset} C")[0]
offset += 1
str = data[offset..offset+len-1]
offset += len
@txt << str << " "
end
return offset
end
end # class TXT
end # class RR
end # module DNS
end # module Net

200
lib/net/dns/rr/types.rb Normal file
View File

@ -0,0 +1,200 @@
module Net # :nodoc:
module DNS
class RR
#
# This is an auxiliary class to hadle RR type field in a DNS packet.
#
class Types
# :nodoc:
Types = { # :nodoc:
'SIGZERO' => 0, # RFC2931 consider this a pseudo type
'A' => 1, # RFC 1035, Section 3.4.1
'NS' => 2, # RFC 1035, Section 3.3.11
'MD' => 3, # RFC 1035, Section 3.3.4 (obsolete)
'MF' => 4, # RFC 1035, Section 3.3.5 (obsolete)
'CNAME' => 5, # RFC 1035, Section 3.3.1
'SOA' => 6, # RFC 1035, Section 3.3.13
'MB' => 7, # RFC 1035, Section 3.3.3
'MG' => 8, # RFC 1035, Section 3.3.6
'MR' => 9, # RFC 1035, Section 3.3.8
'NULL' => 10, # RFC 1035, Section 3.3.10
'WKS' => 11, # RFC 1035, Section 3.4.2 (deprecated)
'PTR' => 12, # RFC 1035, Section 3.3.12
'HINFO' => 13, # RFC 1035, Section 3.3.2
'MINFO' => 14, # RFC 1035, Section 3.3.7
'MX' => 15, # RFC 1035, Section 3.3.9
'TXT' => 16, # RFC 1035, Section 3.3.14
'RP' => 17, # RFC 1183, Section 2.2
'AFSDB' => 18, # RFC 1183, Section 1
'X25' => 19, # RFC 1183, Section 3.1
'ISDN' => 20, # RFC 1183, Section 3.2
'RT' => 21, # RFC 1183, Section 3.3
'NSAP' => 22, # RFC 1706, Section 5
'NSAP_PTR' => 23, # RFC 1348 (obsolete)
# The following 2 RRs are impemented in Net::DNS::SEC, TODO
'SIG' => 24, # RFC 2535, Section 4.1
'KEY' => 25, # RFC 2535, Section 3.1
'PX' => 26, # RFC 2163,
'GPOS' => 27, # RFC 1712 (obsolete)
'AAAA' => 28, # RFC 1886, Section 2.1
'LOC' => 29, # RFC 1876
# The following RR is impemented in Net::DNS::SEC, TODO
'NXT' => 30, # RFC 2535, Section 5.2
'EID' => 31, # draft-ietf-nimrod-dns-xx.txt
'NIMLOC' => 32, # draft-ietf-nimrod-dns-xx.txt
'SRV' => 33, # RFC 2052
'ATMA' => 34, # ???
'NAPTR' => 35, # RFC 2168
'KX' => 36, # RFC 2230
'CERT' => 37, # RFC 2538
'DNAME' => 39, # RFC 2672
'OPT' => 41, # RFC 2671
# The following 4 RRs are impemented in Net::DNS::SEC TODO
'DS' => 43, # draft-ietf-dnsext-delegation-signer
'SSHFP' => 44, # draft-ietf-secsh-dns (No RFC # yet at time of coding)
'RRSIG' => 46, # draft-ietf-dnsext-dnssec-2535typecode-change
'NSEC' => 47, # draft-ietf-dnsext-dnssec-2535typecode-change
'DNSKEY' => 48, # draft-ietf-dnsext-dnssec-2535typecode-change
'UINFO' => 100, # non-standard
'UID' => 101, # non-standard
'GID' => 102, # non-standard
'UNSPEC' => 103, # non-standard
'TKEY' => 249, # RFC 2930
'TSIG' => 250, # RFC 2931
'IXFR' => 251, # RFC 1995
'AXFR' => 252, # RFC 1035
'MAILB' => 253, # RFC 1035 (MB, MG, MR)
'MAILA' => 254, # RFC 1035 (obsolete - see MX)
'ANY' => 255, # RFC 1035
}
# The default value when type is nil in Resource Records
@@default = Types["A"]
# Be able to control the default type to assign when
# type is +nil+. Default to +A+
def self.default=(str)
if Types.has_key? str
@@default = Types[str]
else
raise TypeArgumentError, "Unknown type #{str}"
end
end
# Checks whether +type+ is a valid RR type.
def self.valid?(type)
case type
when String
return Types.has_key?(type)
when Fixnum
return Types.invert.has_key?(type)
else
raise TypeArgumentError, "Wrong type class: #{type.class}"
end
end
# Returns the type in string format, as "A" or "NS",
# given the numeric value
def self.to_str(type)
case type
when Fixnum
if Types.invert.has_key? type
return Types.invert[type]
else
raise TypeArgumentError, "Unknown type number #{type}"
end
else
raise TypeArgumentError, "Wrong type class: #{type.class}"
end
end
# Gives in output the keys from the +Types+ hash
# in a format suited for regexps
def self.regexp
Types.keys.join("|")
end
# Creates a new object representing an RR type. Performs some
# checks on the argument validity too. Il +type+ is +nil+, the
# default value is +ANY+ or the one set with Types.default=
def initialize(type)
case type
when String
# type in the form "A" or "NS"
new_from_string(type.upcase)
when Fixnum
# type in numeric form
new_from_num(type)
when nil
# default type, control with Types.default=
@str = Types.invert[@@default]
@num = @@default
else
raise TypeArgumentError, "Wrong type class: #{type.class}"
end
end
# Returns the type in number format
# (default for normal use)
def inspect
@num
end
# Returns the type in string format,
# i.d. "A" or "NS" or such a string.
def to_s
@str
end
# Returns the type in numeric format,
# usable by the pack methods for data transfers
def to_i
@num.to_i
end
# Should be used only for testing purpouses
def to_str
@num.to_s
end
private
# Constructor for string data type,
# *PRIVATE* method
def new_from_string(type)
case type
when /^TYPE\\d+/
# TODO!!!
else
# String with name of type
if Types.has_key? type
@str = type
@num = Types[type]
else
raise TypeArgumentError, "Unknown type #{type}"
end
end
end
# Contructor for numeric data type
# *PRIVATE* method
def new_from_num(type)
if Types.invert.has_key? type
@num = type
@str = Types.invert[type]
else
raise TypeArgumentError, "Unkown type number #{type}"
end
end
end # class Types
end # class RR
end # module DNS
end # module Net
class TypeArgumentError < ArgumentError # :nodoc:
end