Syntax: Ruby

Ruby Script #1

#!/usr/bin/env ruby

# 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 <http://www.gnu.org/licenses/>.

require 'rubygems'

require 'rexec'
require 'rexec/daemon'

require 'rubygems'
require 'rubydns'

# To run this command, use the standard daemon syntax as root
# ./daemon2.rb start

# You should be able to see that the server has dropped priviledges
#   # ps aux | grep daemon2.rb
#   daemon   16555   0.4  0.0    81392   2024   ??  S     3:35am   0:00.28 ruby ../test/daemon2.rb start

# Test using the following command
# dig @localhost test.mydomain.org

# You might need to change the user name "daemon". This can be a user name or a user id.
RUN_AS = "daemon"

# UDP Socket does per packet reverse lookups unless this is set.
UDPSocket.do_not_reverse_lookup = true

# We need to be root in order to bind to privileged port
if RExec.current_user != "root"
  $stderr.puts "Sorry, this command needs to be run as root!"
  exit 1
end

# The Daemon itself
class Server < RExec::Daemon::Base
  @@var_directory = File.dirname(__FILE__)

  def self.run
    # Bind to port 53 (UDP)
    socket = UDPSocket.new
    socket.bind("0.0.0.0", 53)

    # Drop priviledges
    RExec.change_user(RUN_AS)

    # Don't buffer output (for debug purposes)
    $stderr.sync = true

    # Use upstream DNS for name resolution (These ones are Orcon DNS in NZ)
    $R = Resolv::DNS.new(:nameserver => ["60.234.1.1", "60.234.2.2"])

    # Start the RubyDNS server
    RubyDNS::run_server(:listen => [socket]) do
      match("test.mydomain.org", :A) do |transaction|
        transaction.respond!("10.0.0.80")
      end

      # Default DNS handler
      otherwise do |transaction|
        logger.info "Passthrough: #{transaction}"
        transaction.passthrough!($R)
      end
    end
  end
end

# RExec daemon runner
Server.daemonize

Ruby Script #2

#!/usr/bin/env ruby
# Simple Operator Expression Parser

require 'set'

DEBUG = false

class Array
  def map_to(with)
    r = {}

    each_with_index { |v,i| r[v] = with[i] }

    return r
  end
end

module Expression
  class Context
    def initialize(operators, values)
      @values = values
      @operators = operators
    end

    def value_of (key)
      @values[key]
    end

    def call (op, args)
      @operators.call(op, args)
    end
  end

  class Constant
    def initialize(value)
      @value = value
    end

    def evaluate (ctx)
      return @value
    end
  end

  class Identifier
    def initialize(name)
      @name = name
    end

    def evaluate (ctx)
      ctx.value_of(@name)
    end
  end

  class Operator
    def initialize(name, args = [])
      @name = name
      @args = args
    end

    def name
      @name
    end

    def evaluate (ctx)
      ctx.call(@name, @args.collect { |a| a.evaluate(ctx) })
    end

    def args
      @args
    end
  end

  class Brackets
    def initialize(node)
      @node = node
    end

    def evaluate (ctx)
      @node.evaluate(ctx)
    end
  end

  class Parser
    def initialize(ops, expr)
      @identifiers = []
      @operators = ops

      # Tokens and expressions line up
      @expressions = []
      @tokens = []

      @top = nil

      parse(expr)
    end

    def evaluate (ctx)
      @expressions.collect do |expr|
        expr != nil ? expr.evaluate(ctx) : nil
      end
    end

    def identifiers
      @identifiers
    end

    def tokens
      @tokens
    end
  private
    def parse(expr)
      symbols = @operators.keys + ["(", ")"]
      tokenizer = Regexp.union(Regexp.union(*symbols), /[A-Z]+/)

      @tokens = expr.scan(tokenizer)
      @expressions = [nil] * @tokens.size

      @identifiers = Set.new(expr.scan(/[A-Z]+/))

      @top, i = process_expression

      if DEBUG
        puts "Processed #{i} tokens..."
        puts "Tokens: " + @tokens.join(" ")
        puts @top.inspect
        puts @expressions.inspect
      end
    end

    def process_expression(i = 0)
      ast = []
      ops = {}
      while i < @tokens.size
        t = @tokens[i]

        if t == "("
          result, i = process_expression(i+1)
          ast += result
        elsif t == ")"
          break
        else        
          result = process_token(i)
          ast << result
        end

        if result.class == Operator
          ops[result.name] ||= []
          # Store the index
          ops[result.name] << (ast.size - 1)
        end

        i += 1
      end

      #puts ast.inspect

      # We need to sort the list of operators now
      # [c, infix, prefix, c]

      @operators.order.each do |name|
        op_kind = @operators.kind(name)
        next unless ops[name]

        ops[name].each do |loc|
          op = ast[loc]

          if op_kind == :prefix
            rhs = find_subexpression(ast, loc, RHS_SEARCH)
            op.args << ast[rhs]
            ast[rhs] = nil
          elsif op_kind == :infix
            lhs = find_subexpression(ast, loc, LHS_SEARCH)
            rhs = find_subexpression(ast, loc, RHS_SEARCH)
            op.args << ast[lhs]
            op.args << ast[rhs]
            ast[lhs] = ast[rhs] = nil
          elsif op_kind == :postfix
            lhs = find_subexpression(ast, loc, LHS_SEARCH)
            op.args << ast[lhs]
            ast[rhs] = nil
          end
        end
      end

      return [ast.uniq, i]
    end

    RHS_SEARCH = 1
    LHS_SEARCH = -1

    def find_subexpression(ast, loc, dir)
      while loc >= 0 && loc < ast.size
        loc += dir
        return loc if ast[loc] != nil
      end

      return nil
    end

    def process_token(i) 
      t = @tokens[i]

      if @operators.key? t
        tok = Operator.new(t)
      elsif t.match /[A-Z]+/
        tok = Identifier.new(t)
      else
        tok = Constant.new(t)
      end

      @expressions[i] = tok
      return tok
    end
  end

  TYPE = 0
  FUNC = 1
  class Operators    
    def initialize
      @operators = {}
      @order = []
    end

    def add(sym, kind, &block)
      @operators[sym] = [kind, block]
      @order << sym
    end

    def order
      @order
    end

    def keys
      @operators.keys
    end

    def key? k
      @operators.key? k
    end

    def kind k
      @operators[k][0]
    end

    def call(k, args)
      @operators[k][1].call(*args)
    end
  end
end