jueves, mayo 25, 2006

Los problemas de la IP dinamica en la Internet actual

Esta muy bien tener tus servidores en casa y poder usarlos desde cualquier lado. El CVS, un apache, SSH, ... vamos cosas utiles. El problema viene cuando tienes IP dinamica. Hay que hacer algún apaño para que eso no nos impida acceder a nuestros servidores cuando queramos y aqui es donde entra el tema de hoy.

¿Como encontrar nuestra IP dinamica en Internet?


Reflexionando un poco sobre como llevar esto a cabo se llega rapidamente a la conclusión de que hace falta un "punto fijo". Tiene que haber algo que se pueda encontrar siempre. Una idea que no llegue a desarrollar fue usar por ejemplo una dirección de correo. Pensar en como quedaría la bandeja de entrada fue suficiente para aparcar la idea.

La primera intentona que puse en práctica fue usar uno de esos servicios gratuitos que ofrecen DNS dinamico como DynDns o No-IP. Los resultados no fueron buenos. Cada vez que necesitaba acceder a mis archivos desde remoto tenía la duda de si podría hacerlo, y a menudo la respuesta era un no rotundo. Unas veces el programa cliente que se ejecuta en mi maquina no se habia arrancado como estaba programado, otras se había muerto de repente, otras era incapaz de contactar. Pero lo más sorprendente me lo encontre cuando el sistema fallaba en la parte del servidor, la configuración de mi cuenta desaparecia. No quiero dar mala imagen a estos servicios, es posible que fuera yo mismo, realizando excesivas peticiones de actualización en poco tiempo el que provocase este efecto. El resultado sin embargo es el mismo, estos servicios no me valian.

Me dió por el bricolaje y me monté un sistema propio. La solución consistia en dos partes al igual que los metodos de DNS dinanicos, una dirección bien conocida y un programa cliente.

Como dirección conocida use una página web que me regalaron con la conexión y que hasta ese momento tenia muerta de risa. Dicha página es todo un logro minimalista, consiste en cuatro números separados por puntos. Esos números son mi dirección IP actual.

Eso nos lleva a la parte cliente, que es el responsable de que la dirección IP que aparece en la página sea, efectivamente, mi dirección. Desgraciadamente, la web de regalo que tengo no admite php lo que complica mucho el tema. Así pues, una vez más he de recurrir a terceros para continuar.

Existen multitud de páginas web que te informan de la IP desde la que te conectas. Una simple búsqueda en Google ofrece un gran abanico para elegir. Nuestro programita cliente puede usar cualquiera de ellas para obtener la información que búscamos. Una vez que conoce ese dato crea la página web oportuna y la sube al servidor.

Ahora bien, al ser la IP dinamica puede cambiar con el tiempo así que es necesario ejecutar el programa de vez en vez. Si esto se hace a mano posiblemente cuando nos haga falta que este actualizado sera la vez que se nos olvido actualizar, aparte de lo incomodo que resultaría. Pero aquí es donde cron viene en nuestro rescate. Para los que no lo sepan cron es un demonio que corre en todos los Linux y se encarga de ejecutar tareas en momentos concretos. Basta con crear una tarea que invoque a nuestro programilla tres o cuatro veces al día para completar el último vertice de la solución.

Si se aloja la página en un servidor que admita php, la consulta para hallar la IP la podemos realizar contra una página nuestra y convertir este esquema en uno del tipo Juan Palomo.

En efecto, esto es un metodo rebuscado y cicatero de poder conectar con tu ordenador en cualquier momento. Los dominios son baratos y los servicios de DNS dinamicos suelen funcionar. Pero como ya he dicho, hoy tengo el día Bricomanía.

Para terminar os pongo el codigo fuente (en C) del programita. Mirandolo con calma, se ve que un shell script podria hacer lo mismo usando programas ya existentes y manejo de expresiones regulares. Hay dos motivos por los que no sea un script. La primera: Sería muy complicado hacer un script multiplataforma. En este blog hablo de mi ordenador, el cual usa GNU/Linux casi todo el tiempo, pero aun así tengo cierto afan de universalidad. Si puedo evitarlo prefiero no discriminar a nadie por el sistema operativo que use. La segunda: Me apetecía trastear un poco con los protocolos de internet.


#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_IP <servidor web>
#define MAX_COM 2048
#define CMD_USER "USER <usuario>"
#define CMD_PASSWD "PASS <password>"
#define CMD_TYPE "TYPE I"
#define CMD_PASV "PASV"
#define CMD_STOR "STOR /<pagina web>"
#define CR 13
#define LF 10
#define HTTP_IP <servidor dice IP>
#define GET "GET <pagina dice IP>"
#define HD_HOST "Host: <servidor dice IP>:80"
#define HD_CLOSE "Connection: close"
#define HD_CACHE "Cache-Control: No-cache"

long puerto_datos;

int conectar_ftp( long puerto );
int comunicar( int fd );
char comando( int fd, char* cmd, char** respuesta );
void obtener_puerto( char* buffer );
int determinar_ip( int fd, char** ip );
void encontrar_ip( char bruto[], char** ip );
int actualizar_ip( char* ip, int fd_datos );
int conectar( char* nombre, long puerto );

int main() {
int error = 0;
char* ip;
int fd_control;
int fd_datos;
int fd_http;
char fin;

ip = (char *)malloc( 16 );

fd_http = conectar( HTTP_IP, 80 );
if( fd_http < 0 ) {
printf("No se puede conectar
mediante http\n");
exit( -1 );
}

determinar_ip( fd_http, &ip );
close( fd_http );

if( strlen( ip ) > 6 ) {
fd_control = conectar( SERVER_IP, 21 );
if( fd_control < 0 ) {
printf("No se puede conectar al
ftp (control)\n");
exit( -1 );
}
error = comunicar( fd_control );
if( error < 0 ) {
printf("Fallo en la comunicacion
con FTP\n");
}
fd_datos = conectar( SERVER_IP, puerto_datos );
if( fd_datos < 0 ) {
printf("No se puede conectar al
ftp (datos)\n");
exit( -1 );
}
error = send( fd_datos, ip, strlen( ip ), 0 );
if( error < 0 ) {
printf("No se puede escribir en el
ftp (datos)\n");
exit( -1 );
}
/* cerramos fds */
close( fd_datos );
close( fd_control );
}

if( error < 0 ) {
printf("No se ha podido actualizar la ip\n");
} else {
if( strlen( ip ) > 6 ) {
printf("Se ha actualizado la ip\n");
} else {
printf("Se ha mantenido la ip
anterior\n");
}
}

exit( error );
}


int comunicar( int fd ) {
char buffer[MAX_COM];
int numbytes;
char resultado;
int error = 0;
char* res;

res = (char *)malloc( MAX_COM );
numbytes = recv( fd, buffer, MAX_COM, 0 );
if( numbytes == -1){
printf("Error en recv() \n");
return(-1);
}

if( buffer[0] !='2' ) {
printf("No se puede conectar\n");
return( -1 );
}

resultado = comando( fd, CMD_USER, &res );
if( resultado !='3' ) {
printf("Nombre de usuario no valido\n");
return( -1 );
}

resultado = comando( fd, CMD_PASSWD, &res );
if( resultado !='2' ) {
printf("Contraseña incorrecta\n");
return( -1 );
}

resultado = comando( fd, CMD_TYPE, &res );
if( resultado !='2' ) {
return( -1 );
}

resultado = comando( fd, CMD_PASV, &res );
if( resultado !='2' ) {
printf("No admite modo pasivo\n");
return( -1 );
}

obtener_puerto( res );

resultado = comando( fd, CMD_STOR, &res );
if( resultado =='4' || resultado == '5' ) {
printf("No se puede subir el fichero\n");
return( -1 );
}

return 0;
}

char comando( int fd, char* cmd, char** respuesta ) {
char buffer[MAX_COM];
int numbytes;

bzero( buffer, sizeof( buffer) );
strcpy( buffer, cmd );
buffer[strlen( cmd )] = CR;
buffer[strlen( cmd ) + 1] = LF;
numbytes = send( fd, &buffer, strlen( cmd ) + 2, 0 );
if( numbytes == -1){
printf("Error en send() \n");
return(-1);
}

if( strcmp( cmd , CMD_STOR ) != 0 ) {
numbytes = recv( fd, buffer, MAX_COM, 0);
if( numbytes == -1){
printf("Error en recv() \n");
return(-1);
}
}

strcpy( *respuesta, buffer );

return buffer[0];
}

void obtener_puerto( char* buffer ) {
char c;
int i;
int acumulado;
long puerto_g;
long puerto_p;

i = 0;
c = buffer[i];
while( c !='(' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
while( c !=',' ) {
++i;
c = buffer[i];
}
++i;
c = buffer[i];
acumulado = 0;
while( c !=',' ) {
acumulado = (acumulado * 10) + ( c - '0' );
++i;
c = buffer[i];
}
puerto_g = acumulado;
++i;
c = buffer[i];
acumulado = 0;
while( c !=')' ) {
acumulado = (acumulado * 10) + ( c - '0' );
++i;
c = buffer[i];
}
puerto_p = acumulado;
puerto_datos = ((puerto_g * 256) + puerto_p);
}

int conectar( char* nombre, long puerto ) {
int fd;
struct sockaddr* addr;

/* estructura que recibir�informaci� sobre el nodo remoto */
struct hostent *he;

/* informaci� sobre la direcci� del servidor */
struct sockaddr_in server;

int error = 0;

he = gethostbyname( nombre );
if( he == NULL ) {
/* llamada a gethostbyname() */
printf("gethostbyname() error\n");
return(-1);
}

fd = socket(AF_INET, SOCK_STREAM, 0);
if( fd == -1 ) {
printf("socket() error\n");
return(-1);
}

server.sin_family = AF_INET;
server.sin_port = htons(puerto);
/*he->h_addr pasa la informaci� de ``*he'' a "h_addr" */
server.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(server.sin_zero),8);

if(connect(fd, (struct sockaddr *)&server,
sizeof(struct sockaddr))==-1){
printf("connect() error\n");
return(-1);
}

return fd;
}

int determinar_ip( int fd, char** ip ) {
char buffer[MAX_COM];
int numbytes;

bzero( buffer, sizeof( buffer) );
sprintf( buffer, "%s%c%c%s%c%c%s%c%c%s%c%c%c%c", GET, CR,
LF, HD_HOST, CR, LF, HD_CLOSE, CR, LF,
HD_CACHE, CR, LF,CR, LF );
numbytes = send( fd, &buffer, strlen( buffer ), 0 );
if( numbytes == -1){
printf("Error en send() \n");
return(-1);
}

numbytes = recv( fd, buffer, MAX_COM, 0);
if( numbytes == -1){
printf("Error en recv() \n");
return(-1);
}

encontrar_ip( buffer, ip );

return 0;
}

void encontrar_ip( char bruto[], char** ip ) {
int estado = 0; //No se ha encontrado nada
char str_ip[16];
int indice = 0;
int indice_ip = 0;
char actual;

actual = bruto[indice];

while( estado != 4 && actual != 0 ) {
switch( estado ) {
case 0:
if( actual == 'I' ) {
estado = 1;
}
break;
case 1:
if( actual == 'P' ) {
estado = 2;
} else {
estado = 0;
}
break;
case 2:
if( actual == ':' ) {
estado = 3;
} else {
estado = 0;
}
break;
case 3:
if( actual == '.' ) {
str_ip[indice_ip] = actual;
indice_ip++;
break;
}
if( actual >= '0' && actual <= '9' ) {
str_ip[indice_ip] = actual;
indice_ip++;
break;
}
if( actual == ' ' ) {
break;
}
estado = 4;
break;
case 4:
break;
}
indice++;
actual = bruto[indice];
}
str_ip[indice_ip] = 0;
memcpy( *ip, str_ip, strlen( str_ip ) + 1 );
}

martes, mayo 23, 2006

Control de versiones para documentos III: Usando docbook

Hoy toca la tercera y última entrega de este tema. Voy a escribir muy poco ya que el Tito Fortran ha escrito un extenso artículo al respecto.

El trabajo colaborativo en un documento, que es lo que comentamos aquí, es un objetivo muy interesante pero Fortran ha ido más lejos. Su intención es realizar una documentación inteligente de las fases de análisis y diseño de un producto software, lo cual requiere algo más. El método que él explica consiste en separar el contenido de la estructura. ¿Cuantos requisitos habrán desparecido (o se han colocado de forma que son difíciles de encontrar que es casi lo mismo) por no renumerar los ya existentes?

El articulo es muy completo y, sobre todo, práctico. Alguno puede sentirse intimidado ya que es largo y tarata temas muy diferentes: Makefiles, scripts de python, hojas de cálculo y editores extraños. Tranquilidad que no pasa nada, no es necesario meterse con todo de golpe. Esta escrito de tal modo que cada tema es medianamente independiente, de modo que cada uno puede sacar lo que necesite. Lo básico para el tema que tratamos aquí es saber manejar Docbook, ver que editores hay disponibles (mi elección es Jedit) y el uso del CVS. A partir de los ejemplos dados no resulta complicado realizar un documento maestro, dividir el contenido en diferentes archivos y usar un Makefile para unirlo todo mediante xmllint.

Una vez que se empiezan a hacer bien las cosas, es difícil volver a los métodos antiguos. La curva de aprendizaje puede ser un poco empinada para aquellos que no han tenido contacto con ninguna de estas tecnologías pero en poco tiempo las ventajas se harán evidentes por si mismas.

miércoles, mayo 10, 2006

Control de versiones para documentos II: Usando un programa WYSIWYG

Ingredientes necesarios:
  • Procesador de textos que soporte el formato Oasis (por ejemplo OpenOffice.org).

  • Un script para convertir un .odt en un sistema de directorios.

  • Un script para convertir un sistema de directorios en un .odt

  • Si tienes más de un documento puede ser recomendable (no es necesario) un Makefile o archivo de procesamiento por lotes.
Como ya adelanté en este artículo, se pueden combinar las ventajas de usar un procesador de textos WYSIWYG con las de un CVS.

El truco consista en elegir el formato del documento juiciosamente. En mi caso me decante por el estandar Oasis, que consiste en un conjunto de ficheros en XML. Lamentablemente el estándar solo obliga a implementar soporte para la versión comprimida que es el fichero resultado de comprimir los diferentes archivos que componen el documento. De hecho la mayoría solo soporta este modo de almacenarlo y entre estos se encuentra el Openoffice.org, que es el procesador de textos que yo uso.

Razonable. Resulta incomodo que para copiar un documento de una localización a otra tener que copiar varios archivos. Pero lo que es una ventaja para el uso normal es una desventaja para realizar un control de versiones correcto ya que el fichero comprimido deja de ser texto plano, con lo cual volvemos al principio.

Bueno, según la documentación un fichero .odt no es más que un archivo de tipo zip que contiene los ficheros que componen el documento. Por tanto, basta con descomprimir el .odt y subirlo al CVS para almacenarlo y comprimir los ficheros que se descargan. Cierto, pero hay que tener cuidado con los directorios para que no haya pegas. Si se usa Ark, winzip o algún programa del estilo para descomprimir y volver a comprimir lo más probable es que se acabe con un fichero corrupto que no sirve para nada.

La solución vino de la mano de Edward Holness quien en este artículo indicaba (de forma indirecta) como realizar la descompresión y posterior compresión del fichero de tal modo que no se corrompiera. A partir de ese artículo he creado dos scripts de shell para realizar esta tarea. Se podría realizar igualmente mediante archivos de proceso por lotes de DOS pero actualmente no tengo mucho interés en ello. Si a alguien le apetece pegarse con ello puede ponerlos en los comentarios.


Recapitulando,
  1. Se crea, configura, etc un repositorio CVS.

  2. Se crea un fichero opendocument.

  3. El documento se añade al cvsignore.

  4. Se aplica el script Oasis2Dir.

  5. La estructura resultante se añade al control de versiones.
A partir de este punto, nuestro documento esta disponible para compartir con el resto del equipo. Los conflictos en el contenido se deben poder resolver facilmente, pero cuidado con los conflictos en las definiciones de estilos.

Para obtener la versión más actual se realiza un update y luego aplicar Dir2Oasis.

Para actualizar el repositorio se aplica el script Oasis2Dir seguido de un commit.

Por cierto, que el método aquí descrito vale para cualquier fichero de la familia Opendocument. Eso incluye textos, gráficos, presentaciones, hojas de calculo y bases de datos. Solo que resolver conflictos a mano de estos tipos de fichero puede ser más duro.

Los scripts los pongo a continuación:


Oasis2Dir
#!/bin/bash 

# Este script transforma un documento oasis representado por una estructura
# de directorios en el mismo documento en forma de un único fichero
# comprimido.
# Se puede repartir, modificar y hacer lo que se quiera con este script siempre
# y cuando no cobreis por ello.

# Se obtienen el fichero origen y el directorio destino de los parámetros
if [ $# -ne 2 ]
then
echo "Uso: $0 <fichero oasis> <directorio>"
exit
else
dir=$2
fich=$1
fi

# El fichero origen tiene que existir
if [ ! -f $fich ]
then
echo "No existe el fichero $fich"
exit
fi

# Si el directorio ya existe se elimina para evitar posibles problemas
if [ -d $dir ]
then
rm -rf $dir
fi

# Se descomprime el fichero en el directorio especificado
unzip -oq $fich -d $dir

Dir2Oasis
#!/bin/bash 

# Este script transforma un documento oasis representado por una estructura
# de directorios en el mismo documento en forma de un único fichero
# comprimido.
# Se puede repartir, modificar y hacer lo que se quiera con este script siempre
# y cuando no cobreis por ello.

# Se guarda el directorio actual para poder volver a él.
base=$PWD
# Crear un fichero temporal es una chapuza que espero eliminar pronto.
temp='/tmp/tmp.dir2odt.$$'

# Se obtienen el directorio origen y el fichero destino de los parámetros
if [ $# -ne 2 ]
then
echo "Uso: $0 <directorio> <fichero oasis>"
exit
else
dir=$1
fich=$2
fi

# El directorio tiene que existir
if [ ! -d $dir ]
then
echo "No se encuentra el directorio $dir"
exit
fi

# Si el fichero ya existe se elimina para evitar posibles problemas
if [ -f $fich ]
then rm $fich
fi

# Se crea el fichero en una ubicación temporal
cd $dir
zip -rq9 $temp *

# Se vuelve al directorio de partida
cd $base

# El fichero temporal se mueve a donde corresponde
mv $temp $fich

lunes, mayo 01, 2006

Control de versiones para documentos I: Planteando el problema

Los sistemas de control de versiones son increíblemente cómodos. Producir código en equipo con ellos tiene muchas ventajas: tienes siempre localizable la última versión, se pueden realizar cambios en diferentes partes por diferentes personas y la integración resulta trivial, incluso, si los cambios son en la misma parte te ofrece ayuda para mezclar las dos versiones.

El problema viene cuando en vez de producir código fuente se quiere crear un documento. Antes de escribir una sola linea de código hay que producir una cantidad de documentos que depende de la metodología seguida. En el caso más extremo se trata de tan solo un documento, algo que recoja las necesidades del cliente o los objetivos marcados. En los documentos que se crean probablemente se quieran añadir tablas y gráficos aclaratorios. Así pues, o eres muy bueno con el ASCII Art o tienes que trabajar con algo más que texto plano.

Lo ideal sería poder seguir usando este tipo de programas con la documentación. Un inconveniente es que muchas de las ventajas de los programas de los CVS se pierden cuando en vez de con texto plano se trabaja con binarios. El problema, pues, queda reducido a un modo de hacer documentos que no limiten nuestra expresividad mediante texto plano. Hasta ahora he trabajado con dos técnicas que permiten esto. Una de ellas incluye manejar un procesador de textos WYSIWYG y la otra incluye el uso de docbook. En próximas entregas explicare cada una de esas soluciones.

domingo, abril 30, 2006

Leit motiv

Las bitácoras, en general, caen en dos variantes típicas: Aquellas que dicen que no son nada sin su público y aquellas que el autor dice que escribe tan solo para si. Como en casi todo, encontrar espécimenes de una raza pura es muy raro y siempre hay textos para agradar a los lectores en los blogs "egocéntricos" y miradas al ombligo en los blogs "comunicativos".

Esta bitácora nace con una vocación principalmente egocéntrica, lo escribo para mi. Actualizare cuando me de la gana, si un artículo me parece que ha sobrevivido su utilidad no tendré reparos en eliminarlo, etc. Eso no quiere decir que los visitantes no sean bienvenidos. En absoluto, el estilo y el contenido de los artículos esta pensado para ayudar al mayor número de personas posibles. Pero no nos engañemos, si no es útil para mi no lo publicare.

La idea de esta bitácora la he plagiado de uno de los ejemplares más puros de bitácoras "egocéntricas" que he encontrado. Le pondría un enlace pero no me acuerdo de su nombre ni, obviamente, de su URL. Su autor era una persona que, como yo, hace apaños ad hoc en su sistema y luego le cuesta mucho recordar que ha hecho. La solución que tomó esta persona fue crear un blog donde apuntar ese tipo de cosas. En eso va a consistir este blog, en anotar cosas que hago y saber donde encontrarlas. Para suavizar un poco el fusilamiento de su idea además voy a incluir esas cosas interesantes que voy encontrando por internet y luego me resulta difícil encontrar: Juegos, artículos, vídeos y lo que se me ocurra.

Como decía aquel: "Queda inaugurado este pantano".