IPCd: file ordering.
parent
da683e96fa
commit
01fdb317c1
329
src/ipcd.cr
329
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 <in|out> <service> <url>
|
||||
# --allow <in|ou> <service> <url>
|
||||
# --redirect <service> <service>
|
||||
# --redirect <service-name> <new-service-name> <origin-url> <dest-url>
|
||||
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 <in|out> <service> <url>
|
||||
# --allow <in|ou> <service> <url>
|
||||
# --redirect <service> <service>
|
||||
# --redirect <service-name> <new-service-name> <origin-url> <dest-url>
|
||||
|
||||
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
|
||||
|
|
|
@ -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 <service> <newservice> [<origin-url> <dest-url>]"
|
||||
end
|
||||
else
|
||||
raise "cannot understand flag: #{flag}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
|
@ -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 <service> <newservice> [<origin-url> <dest-url>]"
|
||||
end
|
||||
else
|
||||
raise "oh no"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
85
src/rules.cr
85
src/rules.cr
|
@ -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
|
||||
|
||||
|
Reference in New Issue