310 lines
9.2 KiB
Crystal
310 lines
9.2 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 ServiceUnavailable < ::Exception
|
|
end
|
|
class NoScheme < ::Exception
|
|
end
|
|
class NoFileDescriptor < ::Exception
|
|
end
|
|
class ServiceError < ::Exception
|
|
end
|
|
class SharingFileDescriptor < ::Exception
|
|
end
|
|
|
|
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
|
|
files = Dir.new(directory).children.map {|f| directory + "/" + f }
|
|
Array(Rule).from_yaml_files(files).each do |r|
|
|
@rules << r
|
|
end
|
|
rescue e
|
|
Baguette::Log.error "reading directory #{directory}: #{e}"
|
|
end
|
|
|
|
def parse_redirection_files(directory : String)
|
|
unless File.directory? directory
|
|
raise "not a valid directory: #{directory}"
|
|
end
|
|
files = Dir.new(directory).children.map {|f| directory + "/" + f }
|
|
Array(Redirection).from_yaml_files(files).each do |r|
|
|
@redirections << r
|
|
end
|
|
rescue e
|
|
Baguette::Log.error "reading directory #{directory}: #{e}"
|
|
end
|
|
|
|
|
|
def to_s(io : IO)
|
|
io << "ipcd\n"
|
|
@rules.to_s(io)
|
|
@redirections.to_s(io)
|
|
end
|
|
|
|
# Client => ipcd => Service.
|
|
# ipcd creates a network connection to the remote host,
|
|
# then provides a file descriptor to the client.
|
|
# Besides ipcd configuration, the client shares its IPC_NETWORK
|
|
# environment variable to ipcd to understand how to contact the service.
|
|
|
|
# IPC_NETWORK environment variable contains pairs of URIs (redirections).
|
|
# Format: "requested-service new-URI"
|
|
# Example: "pong tcp://example.com:8000/pong"
|
|
#
|
|
# The URI indicates the scheme, the domain (or directly the IP address) and the port
|
|
# to contact the remote service. The path indicates the remote service to use.
|
|
#
|
|
# Several redirections can be put into the IPC_NETWORK variable, using a semi-colon.
|
|
# Example: "pong tcp://example.com/pong;auth tls://example.com:9000/auth"
|
|
|
|
# ipcd prioritises IPC_NETWORK over its own configuration for redirections.
|
|
# This environment variable is sent from the client to ipcd, that's what is parsed here.
|
|
|
|
# parse_lookup_payload extracts the right service URI to use from the message sent by
|
|
# a client, containing the service to contact and the IPC_NETWORK content.
|
|
# Format: "requested-service;IPC_NETWORK content"
|
|
# Example: "pong;pong tcp://example.com:3000/pong-8"
|
|
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
|
|
|
|
# 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|
|
|
case item
|
|
when /(?<origin>[^ ]+) +(?<destination_uri>[^ ]+)/
|
|
origin, destination_uri = $~["origin"], $~["destination_uri"]
|
|
uri = URI.parse destination_uri
|
|
if uri.scheme.nil?
|
|
uri = URI.parse "unix:///#{destination_uri}"
|
|
end
|
|
services_redirections[origin] = uri
|
|
else
|
|
Baguette::Log.error "cannot understand configuration #{item}"
|
|
end
|
|
end
|
|
|
|
services_redirections.each do |k, v|
|
|
if k == requested_service_name
|
|
Baguette::Log.info "prefered redirection (from IPC_NETWORK): #{k} => #{v}"
|
|
requested_service = v
|
|
end
|
|
end
|
|
|
|
requested_service
|
|
end
|
|
|
|
# XXX: WIP
|
|
def service_lookup (message : IPC::Message, client_fd : Int32)
|
|
requested_service = IPCd::Service.parse_lookup_payload String.new(message.payload)
|
|
|
|
# Now, we want to get the service used for transport.
|
|
# Network services used to carry libipc messages are named based on the scheme.
|
|
# Examples: tcp, udp, ws, etc.
|
|
network_service_name = requested_service.scheme
|
|
|
|
if network_service_name.nil?
|
|
raise NoScheme.new
|
|
end
|
|
|
|
# In case of "unix", then it's a simple redirection to do, no network required.
|
|
if requested_service.scheme == "unix"
|
|
# The URI is "unix:///service" so the path is "/service".
|
|
# Service to contact is simply the path minus the slash.
|
|
network_service_name = requested_service.path.lchop
|
|
end
|
|
|
|
Baguette::Log.debug "connecting to #{network_service_name}"
|
|
|
|
service = begin
|
|
IPC::Client.new network_service_name
|
|
rescue e
|
|
# Better raise message.
|
|
raise ServiceUnavailable.new network_service_name
|
|
end
|
|
|
|
# Will probably never happen in practice.
|
|
if service.fd.nil?
|
|
raise NoFileDescriptor.new network_service_name
|
|
end
|
|
|
|
service_fd = service.fd.not_nil!
|
|
|
|
# A network service is necessary for remote communications, and it requires an URI.
|
|
# TODO: protocol between ipcd and network services.
|
|
# The (draft) protocol:
|
|
# - ipcd sends an URI to the network service
|
|
# - network service responds with a "OK" message
|
|
if requested_service.scheme != "unix"
|
|
service.send_now service_fd, 1.to_u8, requested_service.to_s
|
|
response = service.read
|
|
payload = String.new response.payload
|
|
if payload.chomp != "OK"
|
|
raise ServiceError.new "service #{network_service_name} response was #{payload.chomp}"
|
|
end
|
|
end
|
|
|
|
Baguette::Log.debug "providing the fd from #{network_service_name} to the client #{client_fd}"
|
|
|
|
# Inform the client that we successfully contacted the service.
|
|
send_now client_fd, 3.to_u8, "OK"
|
|
|
|
# Provide the file descriptor to the client.
|
|
r = LibIPC.ipc_provide_fd(client_fd, service_fd)
|
|
if r.error_code != 0
|
|
m = String.new r.error_message.to_slice
|
|
raise SharingFileDescriptor.new "cannot send the file descriptor of the requested service: #{m}"
|
|
end
|
|
|
|
Baguette::Log.debug "everything went well, closing the service fd"
|
|
|
|
# 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
|
|
was_ok = begin
|
|
ipcd.service_lookup event.message, event.fd
|
|
true
|
|
rescue e : IPCd::ServiceUnavailable
|
|
Baguette::Log.error "cannot contact #{e}"
|
|
false
|
|
rescue e : IPCd::NoScheme
|
|
Baguette::Log.error "#{e}"
|
|
false
|
|
rescue e : IPCd::NoFileDescriptor
|
|
Baguette::Log.error "#{e}"
|
|
false
|
|
rescue e : IPCd::ServiceError
|
|
Baguette::Log.error "#{e}"
|
|
false
|
|
rescue e : IPCd::SharingFileDescriptor
|
|
Baguette::Log.error "#{e}"
|
|
false
|
|
rescue e
|
|
Baguette::Log.error "#{e}"
|
|
false
|
|
end
|
|
|
|
unless was_ok
|
|
# Inform the client that we successfully contacted the service.
|
|
ipcd.send_now event.fd, 3.to_u8, "NOT OK"
|
|
end
|
|
|
|
# # Job done: disconnection.
|
|
# ipcd.remove_fd event.fd
|
|
|
|
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
|