# Copyright (c) 2009 Samuel Williams. Released under the GNU GPLv3. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . module RubyDNS # Turn a symbol or string name into a resource class. For example, # convert :A into Resolv::DNS::Resource::IN::A def self.lookup_resource_class(klass) return nil if klass == nil if Symbol === klass klass = klass.to_s end if String === klass if Resolv::DNS::Resource.const_defined?(klass) return Resolv::DNS::Resource.const_get(klass) elsif Resolv::DNS::Resource::IN.const_defined?(klass) return Resolv::DNS::Resource::IN.const_get(klass) end end return klass end # This class provides all details of a single DNS question and answer. This # is used by the DSL to provide DNS related functionality. class Transaction def initialize(server, query, question, resource_class, answer) @server = server @query = query @question = question @resource_class = resource_class @answer = answer @question_appended = false end # The resource_class that was requested. This is typically used to generate a # response. attr :resource_class # The incoming query which is a set of questions. attr :query # The question that this transaction represents. attr :question # The current full answer to the incoming query. attr :answer # Return the type of record (eg. A, MX) as a String. def record_type @resource_class.name.split("::").last end # Return the name of the question, which is typically the requested hostname. def name @question.to_s end # Suitable for debugging purposes def to_s "#{name} #{record_type}" end # Run a new query through the rules with the given name and resource type. The # results of this query are appended to the current transactions answer. def append_query!(name, resource_type = nil) Transaction.new(@server, @query, name, RubyDNS.lookup_resource_class(resource_type) || @resource_class, @answer).process end def process @server.process(name, record_type, self) end # Use the given resolver to respond to the question. This will query # the resolver and merge! the answer if one is received. If recursion is # not requested, the result is failure!(:Refused). If the resolver does # not respond, the result is failure!(:NXDomain) # # If a block is supplied, this function yields with the reply and reply_name if # successful. This could be used, for example, to update a cache. def passthrough! (resolver, &block) # Were we asked to recursively find this name? if @query.rd reply, reply_name = resolver.query(name, resource_class) if reply if block_given? yield(reply, reply_name) end @answer.merge!(reply) else failure!(:NXDomain) end else failure!(:Refused) end true end # Respond to the given query with a resource record. The arguments to this # function depend on the resource_class requested. The last argument # can optionally be a hash of options. # # options[:resource_class]:: Override the default resource_class # options[:ttl]:: Specify the TTL for the resource # options[:name]:: Override the name (question) of the response. # # for A records:: respond!("1.2.3.4") # for MX records:: respond!("mail.blah.com", 10) # # This function instantiates the resource class with the supplied arguments, and # then passes it to append!. # # See Resolv::DNS::Resource for more information about the various # resource_classs available. # http://www.ruby-doc.org/stdlib/libdoc/resolv/rdoc/index.html def respond! (*data) options = data.last.kind_of?(Hash) ? data.pop : {} case options[:resource_class] when nil append!(@resource_class.new(*data), options) when Class append!(options[:resource_class].new(*data), options) else raise ArgumentError, "Could not instantiate resource!" end end # Append a given set of resources to the answer. The last argument can # optionally be a hash of options. # # options[:ttl]:: Specify the TTL for the resource # options[:name]:: Override the name (question) of the response. # # This function can be used to supply multiple responses to a given question. # For example, each argument is expected to be an instantiated resource from # Resolv::DNS::Resource module. def append! (*resources) append_question! options = resources.last.kind_of?(Hash) ? resources.pop.dup : {} options[:ttl] ||= 16000 options[:name] ||= @question.to_s + "." resources.each do |resource| @answer.add_answer(options[:name], options[:ttl], resource) end @answer.encode true end # This function indicates that there was a failure to resolve the given # question. The single argument must be an integer error code, typically # given by the constants in Resolv::DNS::RCode. # # The easiest way to use this function it to simply supply a symbol. Here is # a list of the most commonly used ones: # # :NoError:: No error occurred. # :FormErr:: The incoming data was not formatted correctly. # :ServFail:: The operation caused a server failure (internal error, etc). # :NXDomain:: Non-eXistant Domain (domain record does not exist). # :NotImp:: The operation requested is not implemented. # :Refused:: The operation was refused by the server. # :NotAuth:: The server is not authoritive for the zone. # # See http://www.rfc-editor.org/rfc/rfc2929.txt for more information # about DNS error codes (specifically, page 3). def failure! (rcode) append_question! if rcode.kind_of? Symbol @answer.rcode = Resolv::DNS::RCode.const_get(rcode) else @answer.rcode = rcode.to_i end true end protected def append_question! if @answer.question.size == 0 @answer.add_question(@question, @resource_class) unless @question_appended end end end end