Servidor local Tronco

Será necesario tener un servidor web que acceda a los servicios que ofrece el servidor y muchos de estos servicios sólo podrán ser ejecutados en modo administrador. Para realizar esto sin necesidad de que el usuario bajo el que se ejecuta el servidor web necesite privilegios de administración utilizamos la siguiente estrategia.

Estrategia

Configuración del GRUB

Tronco será un servidor escrito en perl que se ejecuta con privilegios de administrador y acepta peticiones XML del servidor web. A su vez, tronco hará uso de diferentes scripts que implementarán las acciones que se deben realizar sobre el propio servidor y comunicará el resultado al servidor web.

De este modo, el servidor web irá atendiendo las peticiones de sus clientes y se comunicará con el servidor Tronco para acceder a los servicios que este ofrece.

La comunicación se realizará mediante un socket Unix. En el socket solo podra leer y escribir el usuario con el que se ejecuta el servidor web, consiguiendo así un sistema seguro. Ningún usuario tendrá que ejecutar los scripts como administrador, ni siquiera el servidor web, y además se contestarán las peticiones según llegan.

A continuación se explican las partes relevantes del código correspondiente al servidor Tronco, que se encuentra en tronco.pl

Comunicación por socket

Un socket UNIX, o socket de comunicación interprocesos (IPC) es un socket similar a los que se utilizan en conexiones en Internet. En este caso el socket se utilizará para la comunicación entre procesos de una máquina UNIX, por lo que los datos escritos al socket no saldrán de la máquna local.

El socket es visto por los procesos como un archivo más del sistema de ficheros, por lo que tendrá que abrirlo y después podrá escribir o leer.

Para realizar las comunicaciones por el socket UNIX se utilizará el modulo de perl IO::Socket::UNIX. Por lo que en habrá que incluirlo en el código.

use IO::Socket::UNIX

Posteriormente se definen algunas constantes que nos serán útiles.:

my $SERVERUID =  getpwnam("root");
my $SERVERGUID = getgrnam("cactus-web");
my $TIMEOUT_RECV = 1;
my $socket_path = "/var/run/tronco";
  • $SERVERUID y $SERVERGUID son el UID y GUID del usuario con el que se ejecutará el servidor web y se usarán para especificar la propiedad del socket UNIX.
  • $TIMEOUT_RECV será el tiempo máximo que se esperará por un paquete que no esté correctamente terminado, para evitar errores en caso de fragmentación del envío.
  • $socket_path será la ruta del socket.

Tras esto se utiliza unlink con el fin de eliminar el socket si ya existía.

unlink $socket_path if -e $socket_path;

Y se crea el socket.

my $socket = IO::Socket::UNIX->new(
    Local  => $socket_path,
    Type   => SOCK_STREAM,
    Listen => SOMAXCONN,
);
die "Can't create socket: $!" unless $socket;

Se indica la ruta del socket, el tipo (STREAM, orientado a conexión, en este caso) y que se tenga una cola de espera tan larga como sea posible (SOMAXCONN).

Una vez el socket existe se cambia su propiedad al usuario y grupo en que se ejecuta el servidor web para que este pueda leer y escribir.

chown $SERVERUID, $SERVERGUID, $socket_path;

Tras esto es importante tratar dos señales que se producirán en la recepción de mensajes.:

$SIG{'PIPE'} = 'IGNORE';

Cuando se trata de enviar mensajes a un socket conectado cuyo cliente se ha desconectado se produce la señal SIGPIPE y en caso de no asignarle un manejador el programa se detendría. Para que esto no se produzca y dado que no se desea tomar ninguna acción especial cuando ocurra, basta con ignorar la señal.

Por otro lado se asignará a la señal SIGALARM un manejador que pondrá una condición de salida al valor verdadero.

$SIG{ALRM} = sub {$end=1;die "timeout";};

Una vez todo está listo se comienza un bucle infinito que espera por una petición del cliente y envía una respuesta.

while(1)
{
    AceptarConexion()

    Recibir()

    Enviar()

}

En primer lugar se acepta la conexión.

next unless my  $connection = $socket->accept;

Y después se entra en un bucle con la condición de salida $end == 1. Esto tiene como fin recibir peticiones que se hayan fragmentado y se reciban en varias llamadas a recv.

Dado que no hay manera de saber cuando acaba un mensaje y se ha establecido que los mensajes válidos estén terminados por el caracter nulo. Es decir, se recibirán fragmentos hasta que se reciba el caracter nulo o pase un tiempo $TIMEOUT_RECV sin recibir nada.

De este modo se irán recibiendo y concatenando los mensajes hasta poder contestar y se controlará el tiempo de espera con la señal SIGALARM definida antes.

El código es el siguiente.

    while (!$end)
{
    eval{
        alarm $TIMEOUT_RECV;
        $connection->recv($rcvd, 1024);
        alarm 0;
        $buf .= $rcvd;

        if($buf =~ /\x00/)
        {
            my @split = split('\x00', $buf, 2);
            $xmlin = $split[0];

            $end = 1;
            my $xmlout = generate_response($xmlin);
            my $strout = $xmlout->toString . "\0";
            $connection->send($strout);
        }

    };
    alarm 0;
}

Recepción de XML

En el código anterior veíamos que se usaba la subrutina generate_response. Esta acepta una petición en formato xml, la interpreta, hace uso de los scripts de perl necesarios para llevarla acabo y genera una respuesta.

Para facilitar esta tarea se hace uso de la libreria libXML, que será incluida con la linea:

use IO::XML::LibXML;

En primer lugar se aceptará el mensaje, que está en formato texto, y se creará un objeto del xml mediante load_xml.

sub generate_response($)
{
    my ($strin) = @_;
    my $xmlout;
    if( eval{
            $xmlin = XML::LibXML->load_xml(
                string => $strin );
        })
    {

Posteriormente se define el xpath, que es la expresión necesaria para acceder a una parte del xml recibido.

Todas las peticiones deberán tener la forma.

<action type=tipo_de_petición>
<!-- Campos de la petición -->
</action>

Así, para acceder al tipo de la petición usaremos:

my $type_xpath = '/action/@type';

Y con él podremos recuperar su valor.:

myy $type =  $xmlin->findvalue($type_xpath);

A partir de este valor elegiremos la petición a realizar. Habrá tantas de estas como servicios proporcione nuestro servidor Tronco.:

given($type)
{
    when(/^authuser$/) {$xmlout =  login($xmlin);}
    when(/^adduser$/) {$xmlout =  adduser($xmlin);}
    when(/^deluser/) {$xmlout =  deluser($xmlin);}
    when(/^changepass/) {$xmlout =  changepass($xmlin);}
    ...
    default {$xmlout = invalid_action($type)}
}

O en caso de que haya algún error en el xml devolveremos un mensaje informando del error.

else
{
   $xmlout = bad_xml();
}

Después de esto solo queda devolver el xml generado.

return $xmlout;

Como ejemplo, para la petición de autenticar a un usuario el xml el proceso sería el siguiente.

Se recibe el xml de la petición.

sub login($)
{
    my ($xmlin) =  @_;

Se definen las rutas de los campos nombre y contraseña del usuario que desea autenticarse y se recuperan sus valores.

my $user_xpath = '/action/username';
my $pass_xpath = '/action/password';

my $user =  $xmlin->findvalue($user_xpath);
my $passw =  $xmlin->findvalue($pass_xpath);

Se utiliza el script perl del paquete Cactus::Users auth($user, $passw) que hace la comprobación. Este paquete, así como el resto de los utilizados serán explicados en los capitulos correspondientes.

my $check =  Cactus::Users::auth($user, $passw);

Y por último se genera el xml de respuesta.:

my $inform = $xmlout->createElement("inform");
$xmlout->addChild($inform);
$inform->addChild($xmlout->createAttribute(type => "authuser"));
$inform->addChild($xmlout->createTextNode($check));
return $xmlout;
}

Que tendrá la forma siguiente.:

<inform type="authuser>
<--! 1 si era correcto o 0 si era incorrecto-->
</inform>

Este será el xml con el que se responda al cliente. Y de igual manera se atenderán las demás peticiones.

Iniciar el servidor Tronco con el sistema

Una vez que el servidor está listo colocamos el archivo tronco.pl junto a los módulos propios necesarios en el directorio /usr/local/tronco.

Como queremos que utilice nuestros modulos de Cactus añadiremos su ruta al inicio del script.

use lib "/us/local/tronco/"

Después escribimos el siguiente script de inicio.

### BEGIN INIT INFO
# Provides:             tronco
# Required-Start:       $syslog
# Required-Stop:        $syslog
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Tronco server
### END INIT INFO

NAME=tronco
LONG_NAME="tronco local server"
CACTUS_BIN=/usr/local/tronco/tronco.pl
CACTUS_PIDFILE="/var/run/$NAME.pid"

test -x $CACTUS_BIN || exit 1


# Define LSB log_* functions.
. /lib/lsb/init-functions


d_start() {
    start-stop-daemon --background --make-pidfile --start --quiet --pidfile $CACTUS_PIDFILE \
                --startas $CACTUS_BIN
}


d_stop() {
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $CACTUS_PIDFILE \
                --startas $CACTUS_BIN
}

case "$1" in
start)
        log_daemon_msg "Starting " $NAME
        d_start
        log_end_msg $?
        ;;
stop)
        log_daemon_msg "Stopping " $NAME
        d_stop
        log_end_msg $?
        ;;

restart)
        log_daemon_msg "Restarting " $NAME
        d_stop
        d_start
        echo "."
        ;;
status)
        echo -n "$NAME "
        status_of_proc -p $TUNNEL_PIDFILE $TUNNEL_BIN $TUNNEL || exit $?
        ;;

*)
        log_action_msg "Usage: $NAME {start|stop|status|restart}" >&2
        exit 2
        ;;
esac

exit 0

Solo queda copiar este archivo a /etc/init.d, darle permisos de ejecución y añadirlo al inicio del sistema.

# chmod +x tronco
# insserv tronco

Comprobamos que se ha añadido a nuestro nivel de ejecución.

# ls /etc/rc2.d | grep tronco
  S03tronco

Lo iniciamos para que comience a ejecutarse.

# /etc/init.d/tronco start

Y comprobamos que está ejecutandose correctamente.

# ps aux  | grep tronco
root     11432  9.0  0.4  12372  9376 ?        S    00:04   0:00 /usr/bin/perl -w /usr/local/tronco/tronco.pl

Contenidos

Tema anterior

Instalación de un certificado TLS

Próximo tema

Configuración del servidor de correo

Esta página