From 01fdb317c199057083a7fd75778d0fd5d042ac5f Mon Sep 17 00:00:00 2001 From: Karchnu Date: Tue, 3 Nov 2020 19:01:16 +0100 Subject: [PATCH] IPCd: file ordering. --- src/ipcd.cr | 329 +++++++++++++++++++++++++---------------- src/ipcd/cli-parser.cr | 39 +++++ src/ipcd/rules.cr | 90 +++++++++++ src/ipcd_cli_parser.cr | 38 ----- src/rules.cr | 85 ----------- 5 files changed, 327 insertions(+), 254 deletions(-) create mode 100644 src/ipcd/cli-parser.cr create mode 100644 src/ipcd/rules.cr delete mode 100644 src/ipcd_cli_parser.cr delete mode 100644 src/rules.cr diff --git a/src/ipcd.cr b/src/ipcd.cr index a095940..44ac99d 100644 --- a/src/ipcd.cr +++ b/src/ipcd.cr @@ -1,174 +1,241 @@ - -require "baguette-crystal-base" -require "./rules" -require "./ipcd_cli_parser" +require "option_parser" require "ipc" require "uri" +require "baguette-crystal-base" + +require "./lib_modifications" # All standard library modifications. +require "./ipcd/*" + Baguette::Context.verbosity = 4 -class IPCd::Service < IPC::Server +class Baguette::Configuration + class IPC < Base + property rules_directory : String = "/etc/baguette/ipcd/rules" # default + property redirections_directory : String = "/etc/baguette/ipcd/redirections" # default - @rules = RuleSet.new - @redirections = RedirectionSet.new + property verbosity : Int32 = 2 + property ipc_timer : Int32 = 30_000 # 30 seconds - def parse_cli (argv : Array(String)) - IPCdCLIParser.parse_rules argv, @rules, @redirections + property print_ipc_timer : Bool = false + property print_ipc_connection : Bool = false + property print_ipc_disconnection : Bool = false + property print_ipc_extra_socket : Bool = false + property print_ipc_message_received : Bool = false + property print_ipc_message_sent : Bool = false + property print_ipc_switch : Bool = false + + def initialize + end end +end - def to_s(io : IO) - io << "ipcd\n" - io << @rules.to_s + "\n" + @redirections.to_s - end +module IPCd + class Service < IPC::Server - # The client asks to ipcd to open a connection to a service - # the service name is used unless there is a redirection that is provided to the client through - # a IPC_NETWORK environment variable - # This environment variable is sent from the client to ipcd, that's what is parsed here + @rules = RuleSet.new + @redirections = RedirectionSet.new - # parse_lookup_payload extract the right service URI to use from the IPC_NETWORK content - # sent by the client in the form: - # `requested-service-name;service1 uri;service2 uri;...` etc. - def self.parse_lookup_payload (payload : String) : URI - items = payload.split (";") - requested_service_name = items.delete_at(0).chomp() - requested_service = URI.parse "unix:///#{requested_service_name}" + def parse_cli (argv : Array(String)) + CLIParser.parse_rules argv, @rules, @redirections + end - services_redirections = {} of String => URI + def parse_rule_files(directory : String) + unless File.directory? directory + raise "not a valid directory: #{directory}" + end + Array(Rule).from_yaml_files(Dir.new(directory).entries).each do |r| + @rules << r + end + end - # from each item (separated by a semicolon), get the service name and the new uri to use - # format: `service-name uri;service-name uri` - # uri can be: - # * distant service (with protocol to use): https://some.example.com/pong - # * local service: "local:newpong" or simply "newpong" - items.each do |item| - x = /([^ ]+) ([^ ]+)/.match(item) - unless x.nil? - service, newuri = x.captures() - if service.nil? - next - elsif newuri.nil? - next + def parse_redirection_files(directory : String) + unless File.directory? directory + raise "not a valid directory: #{directory}" + end + Array(Redirection).from_yaml_files(Dir.new(directory).entries).each do |r| + @redirections << r + end + end + + + def to_s(io : IO) + io << "ipcd\n" + io << @rules.to_s + "\n" + @redirections.to_s + end + + # The client asks to ipcd to open a connection to a service + # the service name is used unless there is a redirection that is provided to the client through + # a IPC_NETWORK environment variable + # This environment variable is sent from the client to ipcd, that's what is parsed here + + # parse_lookup_payload extract the right service URI to use from the IPC_NETWORK content + # sent by the client in the form: + # `requested-service-name;service1 uri;service2 uri;...` etc. + def self.parse_lookup_payload (payload : String) : URI + items = payload.split (";") + requested_service_name = items.delete_at(0).chomp() + requested_service = URI.parse "unix:///#{requested_service_name}" + + services_redirections = {} of String => URI + + # from each item (separated by a semicolon), get the service name and the new uri to use + # format: `service-name uri;service-name uri` + # uri can be: + # * distant service (with protocol to use): https://some.example.com/pong + # * local service: "local:newpong" or simply "newpong" + items.each do |item| + x = /([^ ]+) ([^ ]+)/.match(item) + unless x.nil? + service, newuri = x.captures() + if service.nil? + next + elsif newuri.nil? + next + end + + Baguette::Log.debug "service: #{service} redirection uri: #{newuri}" + uri = URI.parse newuri + services_redirections[service] = uri end - - Baguette::Log.debug "service: #{service} redirection uri: #{newuri}" - uri = URI.parse newuri - services_redirections[service] = uri end - end - services_redirections.each do |k, v| - Baguette::Log.debug "possible redirection (from env. var.): service: #{k} uri: #{v}" - if k == requested_service_name - requested_service = v + services_redirections.each do |k, v| + Baguette::Log.debug "possible redirection (from env. var.): service: #{k} uri: #{v}" + if k == requested_service_name + requested_service = v + end end + + requested_service end - requested_service - end + # XXX: WIP + def service_lookup (message : IPC::Message, fd : Int32) + payload = String.new message.payload - # XXX: WIP - def service_lookup (message : IPC::Message, fd : Int32) - payload = String.new message.payload + requested_service = IPCd::Service.parse_lookup_payload payload - requested_service = IPCd::Service.parse_lookup_payload payload - - scheme = requested_service.scheme - if scheme.nil? - raise "no SCHEME in redirection" - end - - service_name = scheme - if scheme == "unix" - # scheme == unix => simple redirection - - # the URI is "unix:///service" so the path is "/service" - # first, remove its slash prefix - service_name = requested_service.path.lchop - end - - Baguette::Log.info "service name: #{service_name}" - service = IPC::Client.new service_name - service_fd = service.fd.not_nil! - - # TODO: for remote services, we have to connect to communication service - # these communication services need an URI to work on - # The protocol: - # ipcd sends an URI to the communication service, which responds with a "OK" message - if scheme != "unix" - service.send service_fd, 1.to_u8, "#{requested_service.to_s}\n" - response = service.read - payload = String.new response.payload - if payload.chomp != "OK" - raise "service #{service_name} response was #{payload.chomp}" + scheme = requested_service.scheme + if scheme.nil? + raise "no SCHEME in redirection" end - end - # Then we provide the file descriptor to the client - r = LibIPC.ipc_provide_fd(fd, service_fd) - if r.error_code != 0 - m = String.new r.error_message.to_slice - raise Exception.new "cannot send the file descriptor of the requested service: #{m}" - end + service_name = scheme + if scheme == "unix" + # scheme == unix => simple redirection - # finally, the service should be closed in ipcd - service.close + # the URI is "unix:///service" so the path is "/service" + # first, remove its slash prefix + service_name = requested_service.path.lchop + end + + Baguette::Log.info "service name: #{service_name}" + service = IPC::Client.new service_name + service_fd = service.fd.not_nil! + + # TODO: for remote services, we have to connect to communication service + # these communication services need an URI to work on + # The protocol: + # ipcd sends an URI to the communication service, which responds with a "OK" message + if scheme != "unix" + service.send service_fd, 1.to_u8, "#{requested_service.to_s}\n" + response = service.read + payload = String.new response.payload + if payload.chomp != "OK" + raise "service #{service_name} response was #{payload.chomp}" + end + end + + # Then we provide the file descriptor to the client + r = LibIPC.ipc_provide_fd(fd, service_fd) + if r.error_code != 0 + m = String.new r.error_message.to_slice + raise Exception.new "cannot send the file descriptor of the requested service: #{m}" + end + + # finally, the service should be closed in ipcd + service.close + end end end -ipcd = IPCd::Service.new "network" +def main -# --deny -# --allow -# --redirect -# --redirect + simulation, no_configuration, configuration_file = Baguette::Configuration.option_parser -ipcd.parse_cli ARGV -Baguette::Log.debug ipcd.to_s + config = if no_configuration + Baguette::Configuration::IPC.new + elsif c = Baguette::Configuration::IPC.get configuration_file + c + else + Baguette::Configuration::IPC.new + end -ipcd.base_timer = 30_000 -ipcd.timer = 30_000 + ipcd = IPCd::Service.new "network" -ipcd.loop do |event| - case event - when IPC::Event::Connection - Baguette::Log.debug "Connection: fd #{event.fd}" + # --deny + # --allow + # --redirect + # --redirect - when IPC::Event::Disconnection - Baguette::Log.debug "Disconnection: fd #{event.fd}" + ipcd.parse_cli ARGV + ipcd.parse_rule_files config.rules_directory + ipcd.parse_redirection_files config.redirections_directory + Baguette::Log.debug ipcd.to_s - when IPC::Event::ExtraSocket - Baguette::Log.debug "Extrasocket: fd #{event.fd}" + ipcd.base_timer = config.ipc_timer + ipcd.timer = config.ipc_timer - when IPC::Event::Switch - Baguette::Log.debug "Switch" + if simulation + pp! config + ipcd.close + exit 0 + end - when IPC::Event::MessageReceived - Baguette::Log.debug "Message received: #{event.fd}" - Baguette::Log.debug event.message.to_s - begin - ipcd.service_lookup event.message, event.fd - rescue e - Baguette::Log.error "#{e}" - # when a problem occurs, close the client connection + ipcd.loop do |event| + case event + when IPC::Event::Connection + Baguette::Log.debug "Connection: fd #{event.fd}" if config.print_ipc_connection + + when IPC::Event::Disconnection + Baguette::Log.debug "Disconnection: fd #{event.fd}" if config.print_ipc_disconnection + + when IPC::Event::ExtraSocket + Baguette::Log.debug "Extrasocket: fd #{event.fd}" if config.print_ipc_extra_socket + + when IPC::Event::Switch + Baguette::Log.debug "Switch" if config.print_ipc_switch + + when IPC::Event::MessageReceived + Baguette::Log.debug "Message received: #{event.fd}" if config.print_ipc_message_received + Baguette::Log.debug event.message.to_s begin - # LibIPC.ipc_connections_print pointerof(@connections) - ipcd.remove_fd event.fd + ipcd.service_lookup event.message, event.fd rescue e - Baguette::Log.error "Exception during a client removal: #{e}" + Baguette::Log.error "#{e}" + # when a problem occurs, close the client connection + begin + # LibIPC.ipc_connections_print pointerof(@connections) + ipcd.remove_fd event.fd + rescue e + Baguette::Log.error "Exception during a client removal: #{e}" + end end + + when IPC::Event::MessageSent + Baguette::Log.debug "Message sent: #{event.fd}" if config.print_ipc_message_sent + + when IPC::Event::Timer + Baguette::Log.debug "Timer" if config.print_ipc_timer + + when IPC::Exception + Baguette::Log.debug "IPC::Exception: #{event}" end - - when IPC::Event::MessageSent - Baguette::Log.debug "Message sent: #{event.fd}" - - when IPC::Event::Timer - Baguette::Log.debug "Timer" - - when IPC::Exception - Baguette::Log.debug "IPC::Exception: #{event}" end + + # pp! rules.authorized? Rule::Direction::In, "authd", "tls:192.168.0.42" end -# pp! rules.authorized? Rule::Direction::In, "authd", "tls:192.168.0.42" +main diff --git a/src/ipcd/cli-parser.cr b/src/ipcd/cli-parser.cr new file mode 100644 index 0000000..e85e5bd --- /dev/null +++ b/src/ipcd/cli-parser.cr @@ -0,0 +1,39 @@ + +module IPCd + class CLIParser + def self.pack_args (argv : Array(String)) + last_flag = nil : String? + + argv.chunks do |x| + if x[0..1] == "--" + last_flag = x + end + + last_flag + end + end + + def self.parse_rules (argv : Array(String), rules : RuleSet, redirections : RedirectionSet) + args = IPCd::CLIParser.pack_args argv + + args.each do |flag, parameters| + # puts "flag: #{flag}, params: #{parameters.join(' ')}" + if flag == "--allow" || flag == "--deny" + parameters[3] # will crash if non-existant + + rules << Rule.from_args parameters + elsif flag == "--redirect" + if parameters.size == 3 + redirections << Redirection.new parameters[1], parameters[2] + elsif parameters.size == 5 + raise "--redirect with 4 parameters not implemented, yet" + else + raise "--redirect [ ]" + end + else + raise "cannot understand flag: #{flag}" + end + end + end + end +end diff --git a/src/ipcd/rules.cr b/src/ipcd/rules.cr new file mode 100644 index 0000000..818a47e --- /dev/null +++ b/src/ipcd/rules.cr @@ -0,0 +1,90 @@ + +module IPCd + class Redirection + include YAML::Serializable + + property origin : String + property destination : String + + property originurl : String? + property destinationurl : String? + + def initialize (@origin, @destination) + end + + def initialize (@origin, @destination, @originurl, @destinationurl) + end + + def to_s + "Redirection #{origin} #{destination}" + # if @originurl + # "#{origin} #{destination}" + # else + # "#{origin} #{destination} #{originurl} #{destinationurl}" + # end + end + end + + class RedirectionSet < Array(Redirection) + def to_s + map(&.to_s).join("\n") + end + end + + class Rule + include YAML::Serializable + + enum Type + Allow + Deny + end + + enum Direction + In + Out + end + + getter service : String + getter url : String + getter type : Type + getter direction : Direction + + def initialize (@type, @direction, @service, @url) + end + + def matches_service?(service : String) + @service == "*" || @service == service + end + + def matches_uri?(uri) + u = Regex.new @url + !@u.match(uri).nil? + end + + def self.from_args(args : Array(String)) + Rule.new( + Type.parse(args[0][2..]), + Rule::Direction.parse(args[1]), + args[2], + args[3] + ) + end + + def to_s + "#{type} #{direction} #{service} #{url}" + end + end + + class RuleSet < Array(Rule) + def authorized?(direction : Rule::Direction, service : String, uri : String) : Bool + self.select(&.direction.==(direction)) + .select(&.matches_service?(service)) + .select(&.matches_uri?(uri)) + .[0]?.try &.type.allow? || false + end + + def to_s + map(&.to_s).join("\n") + end + end +end diff --git a/src/ipcd_cli_parser.cr b/src/ipcd_cli_parser.cr deleted file mode 100644 index f801348..0000000 --- a/src/ipcd_cli_parser.cr +++ /dev/null @@ -1,38 +0,0 @@ - -class IPCdCLIParser - def self.pack_args (argv : Array(String)) - last_flag = nil : String? - - argv.chunks do |x| - if x[0..1] == "--" - last_flag = x - end - - last_flag - end - end - - def self.parse_rules (argv : Array(String), rules : RuleSet, redirections : RedirectionSet) - args = IPCdCLIParser.pack_args argv - - args.each do |flag, parameters| - # puts "flag: #{flag}, params: #{parameters.join(' ')}" - if flag == "--allow" || flag == "--deny" - parameters[3] # will crash if non-existant - - rules << Rule.from_args parameters - elsif flag == "--redirect" - if parameters.size == 3 - redirections << Redirection.new parameters[1], parameters[2] - elsif parameters.size == 5 - raise "--redirect with 4 parameters not implemented, yet" - else - raise "--redirect [ ]" - end - else - raise "oh no" - end - end - end -end - diff --git a/src/rules.cr b/src/rules.cr deleted file mode 100644 index ee447b5..0000000 --- a/src/rules.cr +++ /dev/null @@ -1,85 +0,0 @@ - -class Redirection - property origin : String - property destination : String - - property originurl : String | Nil - property destinationurl : String | Nil - - def initialize (@origin, @destination) - end - - def initialize (@origin, @destination, @originurl, @destinationurl) - end - - def to_s - "Redirection #{origin} #{destination}" - # if @originurl - # "#{origin} #{destination}" - # else - # "#{origin} #{destination} #{originurl} #{destinationurl}" - # end - end -end - -class RedirectionSet < Array(Redirection) - def to_s - map(&.to_s).join("\n") - end -end - -class Rule - enum Type - Allow - Deny - end - - enum Direction - In - Out - end - - getter service : String - getter url : Regex - getter type : Type - getter direction : Direction - - def initialize (@type, @direction, @service, @url) - end - - def matches_service?(service : String) - @service == "*" || @service == service - end - - def matches_uri?(uri) - !@url.match(uri).nil? - end - - def self.from_args(args : Array(String)) - Rule.new( - Type.parse(args[0][2..]), - Rule::Direction.parse(args[1]), - args[2], - Regex.new args[3] - ) - end - - def to_s - "#{type} #{direction} #{service} #{url}" - end -end - -class RuleSet < Array(Rule) - def authorized?(direction : Rule::Direction, service : String, uri : String) : Bool - self.select(&.direction.==(direction)) - .select(&.matches_service?(service)) - .select(&.matches_uri?(uri)) - .[0]?.try &.type.allow? || false - end - - def to_s - map(&.to_s).join("\n") - end -end - -