package Cualsea::Server::MessageController; use v5.30.0; use strict; use warnings; use Data::Dumper; use Socket qw/SOL_SOCKET SO_PEERCRED/; use List::Util qw/any/; use Carp qw/carp/; use Params::ValidationCompiler qw/validation_for/; use Types::Standard qw/Object HashRef Str/; use Try::Tiny; use DateTime; use JSON; use Cualsea::MessageManager; use Cualsea::Server::DB; use Cualsea::Server::Service; sub handle_add { my %params = @_; my $name = $params{name}; my $init = $params{init}; my $pidfile = $params{pidfile}; my $binpath = $params{binpath}; if ( $init ne 'sysvinit' || !_validate_init_name($name) ) { return { is_error => 1, desc => "Parameters didn't pass contraints on add", status => 400 }; } my $dbh = Cualsea::Server::DB->dbh(); eval { $dbh->do( << 'EOF', undef, $name, $init, $pidfile, $binpath ); INSERT INTO services (name, init, pidfile, binpath) VALUES (?, ?, ?, ?); EOF }; if ($@) { if ( $@ =~ /UNIQUE/i ) { my $return_value = { status => 400, is_error => 1, desc => 'Endpoint already exists.', }; return $return_value; } carp $@; } return { is_error => 0, desc => "Succesful creation of init service $name.", status => 200 }; } sub handle_del { my %params = @_; my $name = $params{name}; my $dbh = Cualsea::Server::DB->dbh(); if ( !Cualsea::Server::Service::check_if_service_exists_in_db($name) ) { return { is_error => 1, desc => 'Service not found.', status => 404, }; } $dbh->do( <<'EOF', undef, $name ); DELETE FROM services WHERE name = ? EOF return { is_error => 0, desc => "Succesful deletion of init service $name.", status => 200, }; } sub handle_start { my %params = @_; my $name = $params{name}; if ( !Cualsea::Server::Service::check_if_service_exists_in_db($name) ) { return { is_error => 1, desc => 'Service not found.', status => 404, }; } if ( Cualsea::Server::Service::is_started_service($name) ) { return { is_error => 1, desc => 'Service already started.', status => 400, }; } system 'sudo', "/etc/init.d/$name", 'start'; return { is_error => 0, desc => 'Service started.', status => 200, }; } sub handle_stop { my %params = @_; my $name = $params{name}; if ( !Cualsea::Server::Service::check_if_service_exists_in_db($name) ) { return { is_error => 1, desc => 'Service not found.', status => 404, }; } if ( !Cualsea::Server::Service::is_started_service($name) ) { return { is_error => 1, desc => 'Service already stopped.', status => 400, }; } system 'sudo', "/etc/init.d/$name", 'stop'; return { is_error => 0, desc => 'Service stopped.', status => 200, }; } sub handle_restart { my %params = @_; my $name = $params{name}; if ( !Cualsea::Server::Service::check_if_service_exists_in_db($name) ) { return { is_error => 1, desc => 'Service not found.', status => 404, }; } system 'sudo', "/etc/init.d/$name", 'restart'; return { is_error => 0, desc => 'Service restarted.', status => 200, }; } my %COMMANDS = ( add => { params => [ 'name', 'init', # Only sysvinit supported 'pidfile', 'binpath' ], validator => validation_for( params => { name => { type => Str }, init => { type => Str }, pidfile => { type => Str }, binpath => { type => Str }, } ), handle => \&handle_add, }, del => { params => ['name'], validator => validation_for( params => { name => { type => Str }, } ), handle => \&handle_del, }, report => { params => [], validator => sub { }, }, start => { params => ['name'], validator => validation_for( params => { name => { type => Str }, } ), handle => \&handle_start, }, stop => { params => ['name'], validator => validation_for( params => { name => { type => Str }, } ), handle => \&handle_stop, }, restart => { params => ['name'], validator => validation_for( params => { name => { type => Str }, } ), handle => \&handle_restart, } ); sub _validate_init_name { my $init_name = shift; if ( $init_name =~ /^\.$/ ) { return 0; } if ( $init_name =~ /^\.\.$/ ) { return 0; } if ( $init_name =~ /\// ) { return 0; } if ( !-e "/etc/init.d/$init_name" ) { return 0; } return 1; } sub new { my $class = shift; return bless {}, $class; } { my $validator = validation_for( params => { socket => { type => Object }, } ); sub dispatch { my $self = shift; my %params = $validator->(@_); my $socket = $params{socket}; my $message_manager = Cualsea::MessageManager->new( socket => $socket ); my $message = $message_manager->read_message; if ( !$self->check_if_user_has_permissions( socket => $socket ) ) { $self->write_no_permission( message_manager => $message_manager ); return; } if ( !$self->check_is_command( message => $message ) ) { $self->write_malformed( message_manager => $message_manager ); return; } $message_manager->write_message( message => $self->_handle_command( message => $message ) ); } } { my $validator = validation_for( params => { message => { type => HashRef }, } ); sub _handle_command { my $self = shift; my %params = $validator->(@_); my $message = $params{message}; my $command = $message->{command}; my $params = $message->{arguments} // []; say "Got parameters:"; say Data::Dumper::Dumper $params; if ( !exists $COMMANDS{$command} ) { return { is_error => 1, status => 404, desc => 'Command don\'t exists.' }; } my $command_info = $COMMANDS{$command}; my $params_info = $command_info->{params}; print Data::Dumper::Dumper $params_info; # We want to forget about $params arrayref # and previous hash as soon as possible. %params = (); for my $param_info_index ( scalar @$params_info ? ( 0 .. $#$params_info ) : () ) { my $param_info = $params_info->[$param_info_index]; # May not come defined, if ( $param_info_index > scalar $#$params ) { last; } say "$param_info => @{[$params->[$param_info_index]]}"; $params{$param_info} = $params->[$param_info_index]; } my $validator = $command_info->{validator}; my $return_error; try { $validator->(%params); } catch { $return_error = { is_error => 1, status => 400, desc => $_->error }; }; if ( defined $return_error ) { return $return_error; } my $handle = $command_info->{handle}; if ( !defined $handle ) { return { is_error => 1, status => 501 }; } my $result = $handle->(%params); my $dbh = Cualsea::Server::DB->dbh(); $dbh->do(<<'EOF', undef, DateTime->now().'', encode_json($message), encode_json($result)); INSERT INTO log (date_execution, parameters, result) VALUES (?, ?, ?); EOF return $result; } } { my $validator = validation_for( params => { socket => { type => Object }, } ); sub check_if_user_has_permissions { my $self = shift; my %params = $validator->(@_); my $socket = $params{socket}; my ( $pid, $uid, $gid ) = unpack 'LLL', $socket->sockopt(SO_PEERCRED); my $user = getpwuid($uid); my ( undef, undef, undef, $members ) = getgrnam('cualsea'); return any { $_ eq $user } split ' ', $members; } } { my $validator = validation_for( params => { message_manager => { type => Object } } ); sub write_no_permission { my $self = shift; my %params = $validator->(@_); my $message_manager = $params{message_manager}; $message_manager->write_message( message => { is_error => 1, status => 403 } ); } } { my $validator = validation_for( params => { message_manager => { type => Object } } ); sub write_malformed { my $self = shift; my %params = $validator->(@_); my $message_manager = $params{message_manager}; $message_manager->write_message( message => { is_error => 1, status => 400 } ); } } { my $validator = validation_for( params => { message => { type => HashRef }, } ); sub check_is_command { my $self = shift; my %params = $validator->(@_); my $message = $params{message}; return 1 if exists $message->{command}; } } 1