391 lines
13 KiB
Ruby
391 lines
13 KiB
Ruby
|
|
require "ipaddr"
|
|
require "psych"
|
|
require "logger"
|
|
|
|
class String
|
|
def blank?
|
|
return self.strip.empty?
|
|
end
|
|
end
|
|
|
|
$logger = Logger.new $stdout
|
|
$logger.level = Logger::INFO
|
|
|
|
# hardenize logger
|
|
formatter = Logger::Formatter.new
|
|
$logger.formatter = proc { |severity, datetime, progname, msg|
|
|
formatter.call(severity, datetime, progname, msg.dump[1...-1])
|
|
}
|
|
|
|
class Interpreter
|
|
|
|
attr_accessor :recurLimit
|
|
|
|
def self.loadFile filename = "script.json"
|
|
$logger.info "Load script from file #{filename}."
|
|
|
|
raise "The script file #{filename} does not exist." if ! File.exists? filename
|
|
$logger.debug "File exists."
|
|
|
|
raise "The script file #{filename} is not readable." if ! File.readable? filename
|
|
$logger.debug "File readable."
|
|
|
|
$logger.info "Read file."
|
|
fileContent = File.read filename
|
|
|
|
$logger.info "Parse script."
|
|
code = Psych.safe_load fileContent
|
|
raise "An associative array is needed to process the script." if code.is_a? Array
|
|
raise "The script file does not contain valid JSON/YAML." if ! code.is_a? Hash
|
|
|
|
return code
|
|
end
|
|
|
|
def initialize code, stream = $stdout
|
|
@code = code
|
|
@parsed = false
|
|
@stream = stream
|
|
@recur = 0
|
|
@recurLimit = 128
|
|
end
|
|
|
|
def parse!
|
|
$logger.warn "The script has already been parsed." if @parsed
|
|
@variables = {}
|
|
parseBlock @code
|
|
raise "Internal error. A recursion was not completed." if @recur != 0
|
|
@parsed = true
|
|
end
|
|
|
|
def isParsed?
|
|
return @parsed
|
|
end
|
|
|
|
private
|
|
|
|
def generatePrefix(lengths, master, exclude)
|
|
available_prefixes = []
|
|
lengths.each do |len|
|
|
# Generiere alle möglichen Präfixe der gegebenen Länge im Master-Präfix
|
|
master.mask(len).to_range.each do |p|
|
|
# Füge das Präfix nur hinzu, wenn es nicht in exclude enthalten ist
|
|
available_prefixes << p.to_s #unless exclude.include?(p.to_s)
|
|
end
|
|
end
|
|
|
|
# Wähle ein zufälliges Präfix aus den verbleibenden Präfixen aus
|
|
return available_prefixes.sample
|
|
end
|
|
|
|
def generate args
|
|
raise "No prefix lengths given." if ! args.has_key? "lengths"
|
|
lengths = getValue args["lengths"]
|
|
|
|
raise "The prefix lengths are not an array." if ! lengths.is_a? Array
|
|
raise "No length was specified." if lengths.empty?
|
|
lengths.each do |len|
|
|
raise "The prefix length is not valid." if len < 1 || len > 128
|
|
end
|
|
|
|
raise "The master prefix was not specified." if ! args.has_key? "in"
|
|
master = getValue args["in"]
|
|
raise "The master prefix is not from the type prefix." if ! master.is_a? IPAddr
|
|
|
|
count = 1
|
|
if args.has_key? "count"
|
|
count = getValue args["count"]
|
|
raise "Count is not an integer" if ! count.is_a? Integer
|
|
raise "The number must be greater than zero." if count == 0
|
|
end
|
|
|
|
exclude = []
|
|
if args.has_key? "exclude"
|
|
exclude = getValue args["exclude"]
|
|
raise "The list of prefixes to exclude is not an array." if ! exclude.is_a? Array
|
|
exclude.each do |prefix|
|
|
raise "An item of the array, the list of prefixes to be excluded, is not a prefix." if ! prefix.is_a? IPAddr
|
|
end
|
|
end
|
|
|
|
pp generatePrefix(lengths, master, exclude)
|
|
end
|
|
|
|
def parseVariable var, optionalName = false
|
|
@recur += 1
|
|
if @recur > @recurLimit
|
|
raise "Recursion limit of #{@recurLimit} reached."
|
|
end
|
|
$logger.debug "Parse variable."
|
|
|
|
raise "The variable must be of type assosicated array." if ! var.is_a? Hash
|
|
|
|
name = nil
|
|
if ! optionalName
|
|
raise "Missing variable name." if ! var.has_key? "name"
|
|
raise "Variable name of wrong type." if ! var["name"].is_a? String
|
|
raise "Empty variable names are not allowed." if var["name"].blank?
|
|
name = var["name"]
|
|
$logger.debug "Variable name: #{name}"
|
|
else
|
|
$logger.debug "Variable has no name."
|
|
end
|
|
|
|
raise "Missing variable type." if ! var.has_key? "type"
|
|
raise "Variable type of wrong type." if ! var["type"].is_a? String
|
|
raise "Empty variable types are not allowed." if var["type"].blank?
|
|
type = var["type"]
|
|
$logger.debug "Variable type: #{type}"
|
|
|
|
res = nil
|
|
type, subtype = type.split("/")
|
|
case type
|
|
when "prefix"
|
|
raise "Miss CIDR of variable." if ! var.has_key? "cidr"
|
|
raise "CIDR content is of the wrong type. Expect type string." if ! var["cidr"].is_a? String
|
|
raise "Unknown subtype." if subtype
|
|
begin
|
|
res = IPAddr.new(var["cidr"])
|
|
rescue IPAddr::InvalidAddressError
|
|
raise "Invalid prefix."
|
|
end
|
|
raise "IPv4 is obsolete and is not supported." if res.ipv4?
|
|
when "prefixLength"
|
|
raise "Miss value of variable." if ! var.has_key? "value"
|
|
raise "prefixLength content is of the wrong type. Expect type integer." if ! var["value"].is_a? Integer
|
|
raise "Prefix length must be greater than zero." if var["value"] < 0
|
|
raise "Prefix length must not be greater than 128." if var["value"] > 128
|
|
raise "Unknown subtype." if subtype
|
|
res = var["value"]
|
|
when "array"
|
|
res = []
|
|
raise "Miss value of variable." if ! var.has_key? "value"
|
|
raise "Array content is of the wrong type. Expect type array." if ! var["value"].is_a? Array
|
|
|
|
subtypeMapping = {
|
|
"string" => "value",
|
|
"prefixLength" => "value",
|
|
"prefix" => "cidr"
|
|
}
|
|
|
|
raise "Subtype is not supported." if subtype && (! subtypeMapping.keys.include?(subtype))
|
|
|
|
$logger.debug "Iterate through array."
|
|
var["value"].each do |arrVar|
|
|
if subtype
|
|
newVar = {
|
|
"type" => subtype,
|
|
subtypeMapping[subtype] => arrVar
|
|
}
|
|
res << parseVariable(newVar, true)[1]
|
|
else
|
|
res << parseVariable(arrVar, true)[1]
|
|
end
|
|
end
|
|
when "string"
|
|
raise "Miss value of variable." if ! var.has_key? "value"
|
|
raise "The value of the type String must be a string." if ! var["value"].is_a? String
|
|
raise "Unknown subtype." if subtype
|
|
|
|
res = var["value"]
|
|
else
|
|
raise "Unknown variable type."
|
|
end
|
|
|
|
@recur -= 1
|
|
return [name, res]
|
|
end
|
|
|
|
def getValue var
|
|
if var.is_a? String
|
|
case var[0]
|
|
when "$"
|
|
varname = var[1..]
|
|
raise "The variable #{varname} could not be found." if ! @variables.has_key? varname
|
|
return @variables[varname]
|
|
when "%"
|
|
return var[1..]
|
|
else
|
|
raise "Unknown instruction to read the value."
|
|
end
|
|
elsif var.is_a? Hash
|
|
return parseVariable(var, true)[1]
|
|
elsif var.is_a? Integer
|
|
return var
|
|
else
|
|
raise "Unknown instruction to read the value."
|
|
end
|
|
end
|
|
|
|
def prettyPrintValue prefix, postfix, val
|
|
# do a bit pretty print for known types
|
|
if val.is_a? IPAddr
|
|
@stream.print "#{prefix}"
|
|
@stream.print "#{val.to_s}/#{val.prefix}"
|
|
@stream.puts "#{postfix}"
|
|
elsif val.is_a? Array
|
|
val.each do |entry|
|
|
prettyPrintValue prefix, postfix, entry
|
|
end
|
|
else
|
|
@stream.print "#{prefix}"
|
|
@stream.print val.to_s
|
|
@stream.puts "#{postfix}"
|
|
end
|
|
end
|
|
|
|
def prettyPrint args
|
|
raise "Value to be printed not specified." if ! args.has_key? "value"
|
|
raise "The value must be a string." if ! args["value"].is_a? String
|
|
var = getValue args["value"]
|
|
|
|
prefix = nil
|
|
if args.has_key? "prefix"
|
|
raise "The prefix must be a string." if ! args["prefix"].is_a? String
|
|
prefix = getValue args["prefix"]
|
|
raise "The prefix variable must be a string." if ! prefix.is_a? String
|
|
end
|
|
|
|
postfix = nil
|
|
if args.has_key? "postfix"
|
|
raise "The postfix must be a string." if ! args["postfix"].is_a? String
|
|
postfix = getValue args["postfix"]
|
|
raise "The postfix variable must be a string." if ! postfix.is_a? String
|
|
end
|
|
|
|
prettyPrintValue prefix, postfix, var
|
|
end
|
|
|
|
def includeFile args
|
|
raise "File to be included not specified." if ! args.has_key? "file"
|
|
filename = getValue args["file"]
|
|
raise "The filename must be a string." if ! filename.is_a? String
|
|
|
|
to_catch = true
|
|
if args.has_key? "catch"
|
|
raise "The catch parameter must be a boolean." if ! (args["catch"].is_a?(TrueClass) || args["catch"].is_a?(FalseClass))
|
|
to_catch = args["catch"]
|
|
end
|
|
|
|
$logger.info "Include the file #{filename}."
|
|
begin
|
|
instructions = Interpreter.loadFile filename
|
|
parseBlock instructions
|
|
rescue RuntimeError => e
|
|
puts "An error occurred while including the file #{filename}: #{e.message}"
|
|
raise e if ! to_catch
|
|
end
|
|
end
|
|
|
|
def forLoop args
|
|
$logger.debug "In for loop."
|
|
raise "The array to be iterated must be specified." if ! args.has_key? "through"
|
|
array = getValue args["through"]
|
|
raise "The array to be interacted by is not of type array." if ! array.is_a? Array
|
|
|
|
raise "The variable name must be specified." if ! args.has_key? "as"
|
|
raise "The variable name must be specified as a string." if ! args["as"].is_a? String
|
|
raise "The variable name must not be empty." if args["as"].blank?
|
|
varName = args["as"]
|
|
|
|
raise "No instructions were given." if ! args.has_key? "do"
|
|
raise "The instructions must be stored as an array." if ! args["do"].is_a? Array
|
|
|
|
array.each do |value|
|
|
@variables[varName] = value
|
|
args["do"].each do |action|
|
|
raise "An action consists of a name and arguments. Invalid input detected." if action.length < 1 || action.length > 2
|
|
|
|
name = action[0]
|
|
raise "The action name must be a string." if ! name.is_a? String
|
|
raise "The action name must not be nothing." if name.blank?
|
|
|
|
args = {}
|
|
if action.length == 1
|
|
$logger.debug "No arguments were passed to the action #{name}."
|
|
else
|
|
raise "The arguments must be of the type assosicated array." if ! action[1].is_a? Hash
|
|
args = action[1]
|
|
end
|
|
|
|
performAction name, args
|
|
end
|
|
end
|
|
end
|
|
|
|
def performAction action, args
|
|
@recur += 1
|
|
if @recur > @recurLimit
|
|
raise "Recursion limit of #{@recurLimit} reached."
|
|
end
|
|
$logger.debug "Perform action."
|
|
|
|
case action
|
|
when "generate"
|
|
generate args
|
|
when "for"
|
|
forLoop args
|
|
when "include"
|
|
includeFile args
|
|
when "print"
|
|
prettyPrint args
|
|
else
|
|
raise "Unknown action."
|
|
end
|
|
|
|
@recur -= 1
|
|
end
|
|
|
|
def parseBlock block
|
|
$logger.info "Parse variables."
|
|
|
|
if block.has_key? "variables"
|
|
raise "The variables were not specified as an array." if ! block["variables"].is_a? Array
|
|
$logger.debug "The variables are correctly present as a type array."
|
|
|
|
block["variables"].each do |var|
|
|
name, value = parseVariable var
|
|
@variables[name] = value
|
|
end
|
|
else
|
|
$logger.debug "The script does not contain any variables."
|
|
end
|
|
|
|
if block.has_key? "actions"
|
|
$logger.debug "Perform actions."
|
|
raise "The actions were not specified as an array." if ! block["actions"].is_a? Array
|
|
$logger.debug "The actions are correctly present as a type array."
|
|
|
|
block["actions"].each do |action|
|
|
raise "An action consists of a name and arguments. Invalid input detected." if action.length < 1 || action.length > 2
|
|
|
|
name = action[0]
|
|
raise "The action name must be a string." if ! name.is_a? String
|
|
raise "The action name must not be nothing." if name.blank?
|
|
|
|
args = {}
|
|
if action.length == 1
|
|
$logger.debug "No arguments were passed to the action #{name}."
|
|
else
|
|
raise "The arguments must be of the type assosicated array." if ! action[1].is_a? Hash
|
|
args = action[1]
|
|
end
|
|
|
|
performAction name, args
|
|
|
|
end
|
|
else
|
|
$logger.debug "The script does not contain any actions."
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
$logger.info "Load code."
|
|
code = Interpreter.loadFile
|
|
|
|
$logger.debug "Create interpreter."
|
|
i = Interpreter.new code
|
|
|
|
$logger.debug "Give instruction to parse."
|
|
i.parse! |