ipcd/src/ipcd.cr

242 lines
6.9 KiB
Crystal

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 Baguette::Configuration
class IPC < Base
property rules_directory : String = "/etc/baguette/ipcd/rules" # default
property redirections_directory : String = "/etc/baguette/ipcd/redirections" # default
property verbosity : Int32 = 2
property ipc_timer : Int32 = 30_000 # 30 seconds
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
module IPCd
class Service < IPC::Server
@rules = RuleSet.new
@redirections = RedirectionSet.new
def parse_cli (argv : Array(String))
CLIParser.parse_rules argv, @rules, @redirections
end
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
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
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
end
end
requested_service
end
# XXX: WIP
def service_lookup (message : IPC::Message, fd : Int32)
payload = String.new message.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}"
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
def main
simulation, no_configuration, configuration_file = Baguette::Configuration.option_parser
config = if no_configuration
Baguette::Configuration::IPC.new
elsif c = Baguette::Configuration::IPC.get configuration_file
c
else
Baguette::Configuration::IPC.new
end
ipcd = IPCd::Service.new "network"
# --deny <in|out> <service> <url>
# --allow <in|ou> <service> <url>
# --redirect <service> <service>
# --redirect <service-name> <new-service-name> <origin-url> <dest-url>
ipcd.parse_cli ARGV
ipcd.parse_rule_files config.rules_directory
ipcd.parse_redirection_files config.redirections_directory
Baguette::Log.debug ipcd.to_s
ipcd.base_timer = config.ipc_timer
ipcd.timer = config.ipc_timer
if simulation
pp! config
ipcd.close
exit 0
end
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
ipcd.service_lookup event.message, event.fd
rescue 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
end
# pp! rules.authorized? Rule::Direction::In, "authd", "tls:192.168.0.42"
end
main