\documentclass[12pt,a4paper]{article}
\usepackage[spanish]{babel}
\usepackage[latin1]{inputenc}
\date{16 de Julio de 2003}

\title{Perl como herramienta de administración}
\author{Martín Ferrari}

\begin{document}

\maketitle

\begin{abstract}
Perl nació como herramienta de uso general para un ambiente UNIX, nada
más apropiado para un administrador de sistemas. Por supuesto, Perl
creció tanto como UNIX, y así las posibilidades ridículas de
simplificar nuestro trabajo.
\end{abstract}

\section{Introducción}
Perl, en sus inicios fue el fruto de las frustraciones provocadas por
las limitaciones de sed, awk y el shell. No es de extrañar que quienes
usamos estas herramientas por años, cuando descubrimos Perl lo amamos
instantáneamente.

La primera gran emoción sea quizás olvidarse para siempre del
encomillado de variables, que nos ha hecho perder valiosos momentos
que pudimos usar jugando nethack. En Perl, hay cosas propias de un
lenguaje de {\it scripts} que nos permiten hacer el trabajo rápido y
sencillo; a la vez que tenemos la confiabilidad de un lenguaje que
puede manejar sin pestanear variables de contenido y tamaño
irrestrictos, nos brinda velocidades increíbles y para colmo tenemos
bibliotecas para casi toda utilidad imaginable.

\section{Rápido y sucio}
Muchas veces nos encontraremos escribiendo programas de una línea sin
siquiera grabarlos en un archivo. Para eso usamos el modificador -e
del comando perl que toma el siguiente parámetro cómo código fuente a
ejecutar, también podemos importar módulos con el modificador -M.

Supongamos que necesitamos bajar el contenido de una página de manera
automática, por ejemplo para verificar funcionamiento de un servidor
web. Usamos del módulo \verb!LWP::Simple! la función \verb!getprint!
que simplemente obtiene una URL y la imprime:

\begin{verbatim}
# perl -MLWP::Simple -e 'getprint "http://www.cpan.org/"'
\end{verbatim}

No tenemos por qué limitarnos a páginas web, ni siquiera al protocolo
HTTP. Supongamos que necesitamos mantener una copia local actualizada
de definiciones de virus, por supuesto no para nosotros sino para los
pobres usuarios que tienen que convivir con Windows; podríamos
programar para que se ejecute diariamente el siguiente comando, que
además muestra que perl puede manejar datos binarios de manera
transparente:

\begin{verbatim}
# perl -MLWP::Simple -e 'open(OUT, ">", "Definiciones.dat");
    $contenido = get "ftp://ftp.antivirus.com/ultimo.dat";
    print OUT $contenido; close(OUT)'
\end{verbatim}

\section{Un poco de poder}

Veamos ahora algunos ejemplos un poco más complejos

\subsection{Envío de mail con archivos adosados}

Enviar un mail con archivos de manera automatizada siempre es un
problema, ya que el manejo de MIME es complejo y no es recomendable
intentar hacerlo en cada script que escribamos.

Este programa toma como argumentos el destinatario, el motivo del
mensaje y el/los archivos a enviar y se encarga diligentemente de la
tarea. Para ello usa el módulo MIME::Entity, perteneciente a
MIME-tools.

\begin{verbatim}
#!/usr/bin/perl
# sendattach.pl: Envío de mail con archivos adosados

# Nos forzamos a ser prolijos
use strict;
use warnings;

# Módulo que hace la magia
use MIME::Entity;

# Recibo los argumentos de línea de comando
die "Uso: $0 <email> <subject> <archivo1> <archivo2> ...\n"
    unless (@ARGV >= 3);
my($email, $subject, @files) = @ARGV;

# Compongo el cuerpo vacío
my($mail) = MIME::Entity->build(
    To => $email,
    Type => "text/plain",
    Subject => $subject,
    Data => "");

# Agrego cada archivo
foreach(@files) {
    $mail->attach(
        Disposition => "attachment",
        Type => "application/octet-stream",
        Path => $_)
        or die "Problemas con $_";
}

# Envío el mail listo
$mail->smtpsend();
\end{verbatim}

\subsection{Backups por la red}

A veces sucede que necesitamos hacer un backup de una máquina en el
disco rígido de otra. El problema es cómo mandar los datos, FTP es
incómodo, SCP es lento, RCP es peligroso.

Este ejemplo implementa un servidor TCP que no necesita correr con
privilegios, que recibe por la red el nombre de archivo a grabar y su
contenido, toma el recaudo mínimo de limpiar al nombre de archivos de
caracteres comprometedores (caracteres de control, barras y espacios)
y graba los datos en el archivo solicitado dentro de un directorio
predeterminado.

Código del servidor:

\begin{verbatim}
#!/usr/bin/perl

use warnings;
use strict;

use IO::Socket;

# Puerto en el que voy a escuchar
my($server_port) = 8000;
# Directorio donde voy a guardar los archivos
my($outdir) = "./backups";

my($server, $cliente, $arch);

# Creo el servidor
$server = IO::Socket::INET->new(
        LocalPort => $server_port,
        Type      => SOCK_STREAM,
        Listen    => 5,
        Reuse     => 1)
    or die "No puedo tomar el puerto $server_port: $@\n";

# Espero una conexión
while ($cliente = $server->accept()) {
    print STDERR "Conexión de ", $cliente->peerhost(), "\n";
    # Leo la primera línea, reinicio si no lei nada.
    $arch = <$cliente>;
    unless($arch) {
        warn "Conexión cerrada prematuramente\n";
        next;
    }
    # Limpio de caracteres de control
    $arch =~ tr#\000-\037##d;
    # Reemplazo barras y espacios
    $arch =~ tr#\\/ #___#;
    # Abro el archivo de salida
    print STDERR "Grabando en $outdir/$arch\n";
    unless(open(FILE, ">", "$outdir/$arch")) {
        warn "No puedo crear $outdir/$arch: $!\n";
        next;
    }
    # Bajo el contenido
    while(<$cliente>) {
        print FILE $_;
    }
    close(FILE);
}

close($server);
\end{verbatim}

El cliente es muy sencillo, toma por línea de comando los datos del
servidor y el nombre de archivo a grabar y luego copia la entrada
estándar al servidor remoto.

Código del cliente:

\begin{verbatim}
#!/usr/bin/perl

use warnings;
use strict;

use IO::Socket;

die "Uso: $0 <host> <puerto> <archivo a grabar>\n"
    unless(@ARGV == 3);
my($host, $port, $file) = @ARGV;

my($remote) = IO::Socket::INET->new(
    Proto => "tcp",
    PeerAddr => $host,
    PeerPort => $port
    )
    or die "error conectando a $host/$port :$!";

print $remote "$file\n";
while ( <STDIN> ) { print $remote $_ }
\end{verbatim}

\end{document}
