123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- 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!
|