Monthly Archives: October 2010

Cifrado y descifrado simétrico con Rijndael (AES) utilizando C#/Mono

Introducción.

Advanced Encryption Standard (AES), también conocido como Rijndael (pronunciado "Rain Doll" en inglés), es un esquema de cifrado por bloques adoptado como un estándar de cifrado por el gobierno de los Estados Unidos. El AES fue anunciado por el Instituto Nacional de Estándares y Tecnología (NIST) como FIPS PUB 197 de los Estados Unidos (FIPS 197) el 26 de noviembre de 2001 después de un proceso de estandarización que duró 5 años.  Se transformó en un estándar efectivo el 26 de mayo de 2002. Desde 2006, el AES es uno de los algoritmos más populares usados en criptografía simétrica.

El cifrador fue desarrollado por dos criptólogos belgas, Joan Daemen y Vincent Rijmen, ambos estudiantes de la Katholieke Universiteit Leuven, y enviado al proceso de selección AES bajo el nombre "Rijndael".

Tomado del artículo Advanced Encryption Standard de Wikipedia.

Implementación con strings.

La aplicación de demostración de esta técnica requiere del uso de por lo menos los siguientes namespaces.

using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;

Establecer la clave y el vector de inicio.

Estos valores pueden ser especificados manualmente o de manera automática por el framework.  La implementación para permitir que se definan automáticamente estos valores es la siguiente.

Rijndael rijndael = Rijndael.Create();
byte[] key = rijndael.Key;
byte[] iv  = rijndael.IV;

Es posible forzar la generación de nuevas claves y nuevos vectores de inicio para el algoritmo utilizando los métodos rijndael.GenerateKey() y rijndael.GenerateIV() respectivamente.

Si por el contrario se desea especificar estos valores manualmente su implementación es la siguiente siendo strKey y strIv, la clave y el vector de inicialización como cadenas de texto.

byte[] key = UTF8Encoding.UTF8.GetBytes(strKey);
byte[] iv  = UTF8Encoding.UTF8.GetBytes(strIv);

Especificando estos valores manualmente es necesario garantizar que sus longitudes sean válidas para el algoritmo.  En este caso se utilizará una longitud de clave de 32 bits y una longitud de vector de inicio de 16 bits.

int keySize = 32;
int ivSize = 16;
Array.Resize(ref key, keySize);
Array.Resize(ref iv, ivSize);

Cifrado de cadenas de texto.

Para cifrar la información se requiere de los siguientes parámetros.

  1. Cadena de texto con los datos a cifrar.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos cifrados.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	string		        texto cifrado
 */
public static string encryptString(String plainMessage, byte[] Key, byte[] IV)
{
    // Crear una instancia del algoritmo de Rijndael
    Rijndael RijndaelAlg = Rijndael.Create();
    // Establecer un flujo en memoria para el cifrado
    MemoryStream memoryStream = new MemoryStream();
    // Crear un flujo de cifrado basado en el flujo de los datos
    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);
    // Obtener la representación en bytes de la información a cifrar
    byte[] plainMessageBytes = UTF8Encoding.UTF8.GetBytes(plainMessage);
    // Cifrar los datos enviándolos al flujo de cifrado
    cryptoStream.Write(plainMessageBytes, 0, plainMessageBytes.Length);
    cryptoStream.FlushFinalBlock();
    // Obtener los datos datos cifrados como un arreglo de bytes
    byte[] cipherMessageBytes = memoryStream.ToArray();
    // Cerrar los flujos utilizados
    memoryStream.Close();
    cryptoStream.Close();
    // Retornar la representación de texto de los datos cifrados
    return Convert.ToBase64String(cipherMessageBytes);
}

Descifrado de cadenas de texto.

El proceso inverso, el de descifrado, se realiza de manera antagónica.  Para hacerlo es necesario contar con los siguientes parámetros.

  1. Cadena de texto con los datos cifrados.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos descifrados.

/**
 * Descifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	encryptedMessage	mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			texto descifrado (plano)
 */
public static string decryptString(String encryptedMessage, byte[] Key, byte[] IV)
{
    // Obtener la representación en bytes del texto cifrado
    byte[] cipherTextBytes = Convert.FromBase64String(encryptedMessage);
    // Crear un arreglo de bytes para almacenar los datos descifrados
    byte[] plainTextBytes = new byte[cipherTextBytes.Length];
    // Crear una instancia del algoritmo de Rijndael
    Rijndael RijndaelAlg = Rijndael.Create();
    // Crear un flujo en memoria con la representación de bytes de la información cifrada
    MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
    // Crear un flujo de descifrado basado en el flujo de los datos
    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);
    // Obtener los datos descifrados obteniéndolos del flujo de descifrado
    int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
    // Cerrar los flujos utilizados
    memoryStream.Close();
    cryptoStream.Close();
    // Retornar la representación de texto de los datos descifrados
    return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}

Implementación con archivos.

El cifrado y descifrado de mensajes en archivos se realiza de manera similar al expuesto anteriormente con cadenas, sin embargo varían los flujos (streams) utilizados para obtener y dirigir el flujo de la información.

Cifrado a archivos.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael y lo almacena en un archivo
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	filename	        nombre del archivo donde se almacenará el mensaje cifrado
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	void
 */
public static void encryptToFile(String plainMessage, String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse
    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);
    // Crear una instancia del algoritmo Rijndael
    Rijndael RijndaelAlg = Rijndael.Create();
    // Crear un flujo de cifrado basado en el flujo de los datos
    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);
    // Crear un flujo de escritura basado en el flujo de cifrado
    StreamWriter streamWriter = new StreamWriter(cryptoStream);
    // Cifrar el mensaje a través del flujo de escritura
    streamWriter.WriteLine(plainMessage);
    // Cerrar los flujos utilizados
    streamWriter.Close();
    cryptoStream.Close();
    fileStream.Close();
}

Descifrado de archivos.

/**
 * Descifra el contenido de un archivo con el algoritmo de Rijndael y lo retorna
 * como una cadena de texto plano
 *
 * @param	filename		nombre del archivo donde se encuentra el mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			mensaje descifrado (plano)
 */
public static string decryptFromFile(String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse
    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);
    // Crear una instancia del algoritmo Rijndael
    Rijndael RijndaelAlg = Rijndael.Create();
    // Crear un flujo de cifrado basado en el flujo de los datos
    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);
    // Crear un flujo de lectura basado en el flujo de cifrado
    StreamReader streamReader = new StreamReader(cryptoStream);
    // Descifrar el mensaje a través del flujo de lectura
    string plainMessage = streamReader.ReadLine();
    // Cerrar los flujos utilizados
    streamReader.Close();
    cryptoStream.Close();
    fileStream.Close();
    return plainMessage;
}

Aplicación de demostración.

La aplicación de demostración incluye los conceptos y el código expuestos en este artículo.  Con ella es posible cifrar y descifrar un mensaje que consiste en una cadena de texto arbitraria en memoria y en un archivo.

Aplicación de demostración del uso del algoritmo Rijndael

Aplicación de demostración del uso del algoritmo Rijndael

Construír la aplicación.

La aplicación de demostración puede construírse utilizando la solución incluída en la distribución con MonoDevelop o Visual Studio.  También es posible construírla desde línea de comando (Mono) mediante la siguiente instrucción.

$ gmcs "/out:RijndaelSample.exe" "/r:/usr/lib/mono/2.0/System.dll" /t:exe "RijndaelSample/Main.cs"

Enlaces.

Instalación de DokuWiki 2010

Introducción.

Dokuwiki es un excelente software para la creación de Wikis.  Ha sido desarrollado por Andreas Gohr en PHP, no requiere de una base de datos por lo que es muy cómo para administrar, especialmente las copias de datos y la migración de los sitios.

Es utilizado por muchos usuarios al rededor del mundo, incluyéndome por supuesto.  El proceso de instalación (que se describirá a continuación) es simple y sólo es necesaria una breve introducción a su sintaxis para empezar a disfrutar de sus bondades.

Instalación básica.

Obtener la versión mas reciente de la distribución en la siguiente ubicación.

http://www.splitbrain.org/projects/dokuwiki

En este caso, la versión mas reciente era la rc2010-10-27.

$ wget http://www.splitbrain.org/_media/projects/dokuwiki/dokuwiki-rc2010-10-27.tgz

Descomprimir el paquete y remover la versión comprimida.

$ tar zxvf dokuwiki-*.tgz

$ rm dokuwiki-*.tgz

Copiar los archivos de la distribución de DokuWiki a su ubicación final, en este caso es el directorio wiki/ bajo el DOCUMENT_ROOT de Apache.

$ cp -rf dokuwiki-*/{*,.??*} wiki/

$ rm -rf dokuwiki-*

Establecer los permisos básicos necesarios para el funcionamiento del wiki.  En este caso se utilizará 777 para los directorios específicos ya que los archivos serán propiedad de un usuario y un grupo distintos al utilizado por Apache.  Es preferible utilizar los permisos mas restrictivos posibles según la configuración que se tenga.  Consulte mayor información acerca de los permisos de archivos en el manual de DokuWiki.

$ cd wiki

$ chmod 777 conf data data/pages data/attic data/media data/meta data/cache data/locks data/index data/tmp

Configuración inicial.

La configuración inicial del sitio se realiza vía web y corresponde con los parámetros iniciales de identificación y seguridad del sitio.

Acceder a la aplicación de instalación de DokuWiki en el sitio recién instalado.

http://mi.sitio.com/wiki/install.php

Especificar la siguiente información según sus necesidades específicas.

  • Nombre del wiki.
  • Activar ACL.
  • Información del usuario administrador.
  • Política inicial ACL.
    • Open Wiki: todos pueden acceder al sitio y colaborar en él.
    • Public Wiki: todos pueden acceder al sitio pero sólo los usuarios registrados pueden colaborar en él.
    • Closed Wiki: sólo los usuarios registrados pueden acceder a él.
  • Licenciamiento del contenido.

Una vez finalizada esta etapa de configuración, por motivos de seguridad es necesario remover la aplicación de instalación.

$ rm wiki/install.php

Desactivar el registro automático de usuarios.

Este paso es opcional y es útil cuando no se desea que los usuarios se puedan registrar por ellos mismos en el wiki y por ende realizar sus propias modificaciones.  Para realizar este ajuste a la configuración es necesario que se cuente con acceso de administrador al wiki.

Ingresar al menú de administración > Opciones de configuración.

http://mi.sitio.com/wiki/doku.php?id=start&do=admin&page=config

En la sección Disable DokuWiki actions activar la casilla Register y presionar el botón Save al final del formulario.

Activar los nice URLs.

Este paso es opcional y es útil cuando se desea mejorar la apariencia de los URL.  Para realizar este ajuste a la configuración es necesario adaptar el servidor de páginas (Apache en este caso) y el wiki al nuevo estilo de URLs, para esto último se debe contar con acceso de administrador al wiki.

Para mas información acerca de los posibles estilos de URLs consulte la sección URL Rewriting del manual de DokuWiki.

Configuración de DokuWiki.

Ingresar al menú de administración > Opciones de configuración.

http://mi.sitio.com/wiki/doku.php?id=start&do=admin&page=config

En la sección Advanced Settings seleccionar la opción .htaccess para el campo Use nice URLs y presionar el botón Save al final del formulario.

Configuración de Apache.

$ cp .htaccess.dist .htaccess

Active (remueva el # de comentario) las líneas indicadas en el archivo .htaccess.

$ vi .htaccess

Options -Indexes -MultiViews +FollowSymLinks
<Files ~ "^([\._]ht|README$|VERSION$|COPYING$)">
    Order allow,deny
    Deny from all
    Satisfy All
</Files>
RewriteEngine on
RewriteBase /wiki
RewriteRule ^_media/(.*)              lib/exe/fetch.php?media=$1  [QSA,L]
RewriteRule ^_detail/(.*)             lib/exe/detail.php?media=$1  [QSA,L]
RewriteRule ^_export/([^/]+)/(.*)     doku.php?do=export_$1&id=$2  [QSA,L]
RewriteRule ^$                        doku.php  [L]
RewriteCond %{REQUEST_FILENAME}       !-f
RewriteCond %{REQUEST_FILENAME}       !-d
RewriteRule (.*)                      doku.php?id=$1  [QSA,L]
RewriteRule ^index.php$               doku.php

No olvide ajustar el valor de la opción RewriteBase al correspondiente con el directorio donde se ubica la instalación de DokuWiki, /wiki en este caso.

Instalar un nuevo tema (template).

Este paso es opcional y es útil para instalar nuevas plantillas y modificar así la apariencia del sitio.  Para realizar este ajuste a la configuración se debe contar con acceso de administrador al wiki.

Visite el sitio de templates de DokuWiki, elija uno y descargue su distribución comprimida.  Ubique el archivo descargado en el directorio wiki/lib/tpl del wiki.

Descomprima el nuevo tema a instalarse.  Utilice la herramienta según corresponda con el tipo de compresión del nuevo tema (tar, zip, …).

$ cd wiki/lib/tpl

$ tar zcvf nuevo_template.tgz

$ rm nuevo_template.tgz

Ingresar al menú de administración > Opciones de configuración.

http://mi.sitio.com/wiki/doku.php?id=start&do=admin&page=config

Seleccionar el tema deseado en el campo Template y presionar el botón Save al final del formulario.

Instalar un nuevo plugin.

Este paso es opcional y es útil para aumentar la funcionalidad del wiki mediante módulos adjuntos.  Para realizar este ajuste a la configuración se debe contar con acceso de administrador al wiki.

Visite el sitio de plugins de DokuWiki, elija uno y descargue su distribución comprimida.  Ubique el archivo descargado en el directorio wiki/lib/plugins del wiki.

Descomprima el nuevo plugin a instalarse.  Utilice la herramienta según corresponda con el tipo de compresión del nuevo plugin (tar, zip, …).

$ cd wiki/lib/plugins

$ tar zcvf nuevo_plugin.tgz

$ rm nuevo_plugin.tgz

Ingresar al menú de administraciónAdministración de plugins.

http://mi.sitio.com/wiki/doku.php?id=start&do=admin&page=plugin

Activar  el plugin recién instalado seleccionando la casilla de verificación frente a él y presionar el botón Save al final del formulario.

Ingresar al menú de administraciónOpciones de configuración.

http://mi.sitio.com/wiki/doku.php?id=start&do=admin&page=config

Busque la sección Plugin Settings en el formulario y realice los ajustes de configuración que requiera específicamente el nuevo plugin.

Solución de problemas.

Invalid command 'RewriteEngine'.

Al intentar activar los nice URLs y crear el archivo .htaccess, no es posible acceder al sitio web del Wiki ya que se genera un error 500 y en el log de errores de Apache (ubicado por defecto en /var/log/apache2/error.log) se evidencia el siguiente mensaje de error.

Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration

Esto sucede porque el servidor de páginas no cuenta con el soporte requerido para utilizar el módulo de rewrite urls.  Para activarlo es necesario ejecutar el siguiente comando.

$ sudo a2enmod rewrite

Finalmente es necesario reiniciar a Apache para que se tenga en cuenta el cambio en la configuración recién hecho.

$ /etc/init.d/apache2 restart

Enlaces.

Calculando hashes (MD5 y SHA1) con C#/Mono

Introducción.

Una función de hashing recibe un bloque de datos arbitrarios y genera de manera determinística un arreglo de bytes con el valor de su hash, el cual tiene como característica el ser único para los datos procesados.

Los valores de hash tienen múltiples usos, entre ellos se utilizan en la criptografía para garantizar que la información de un mensaje no ha sido modificada.  Con este mismo uso, comúnmente se utiliza para determinar si la descarga de archivos ha sido exitosa o no (md5sum).

Existen varios algortimos de hashing, los mas conocidos son MD5 de 128 bits y SHA1 de 160 bits.  El primero de ellos es muy utilizado actualmente, especialmente para el almacenamiento de contraseñas, sin embargo ya no se considera seguro por lo cual es conveniente utilizar el segundo mencionado.

Implementación en C#/Mono.

Para la implementaciones que se describen a continuación se utilizaron los siguientes namespaces.

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

Calcular el hash de una cadena.

El primer paso es crear instancias de los algoritmos de hashing.

HashAlgorithm md5  = HashAlgorithm.Create("MD5");
HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");

Posteriormente se calcula el valor del hash de la cadena texto (text) especificada.

Byte[] md5Data  = md5.ComputeHash(UTF8Encoding.UTF8.GetBytes(text));
Byte[] sha1Data = sha1.ComputeHash(UTF8Encoding.UTF8.GetBytes(text));

Finalmente su valor puede ser manipulado o entregado al usuario.

Console.WriteLine("MD5:  " + BitConverter.ToString(md5Data).Replace("-", ""));
Console.WriteLine("SHA1: " + BitConverter.ToString(sha1Data).Replace("-", ""));

Calcular el hash de un archivo.

El procedimiento es muy similar al cálculo de hash de una cadena, pero a diferencia de este, los datos provienen de un flujo de archivo al cual se le calcula su valor.

FileStream fs = File.OpenRead(filename);
md5Data  = md5.ComputeHash(fs);
sha1Data = sha1.ComputeHash(fs);
fs.Close ();

Verificar la seguridad de las contraseñas de GNU/Linux con John the Ripper

Introducción.

Una de las primeras cosas que debemos verificar quienes estamos preocupados por la seguridad de los servidores y aplicaciones es la seguridad de las contraseñas que eligen y utilizan a diario los usuarios.  A nivel de aplicación y de sistema operativo se establecen políticas que les obligan a los usuarios a elegir combinaciones de caracteres con una confiabilidad que consideramos suficiente.  Pero cuan seguras son realmente esas claves ?

John the Ripper es una aplicación que pretende averiguar las contraseñas a través de un ataque de fuerza bruta.  Para esto es necesario contar con acceso a la base de datos de los usuarios (/etc/passwd) y de las contraseñas cifradas (/etc/shadow) del sistema operativo.  Su uso es muy simple y como se mencionó anteriormente, es muy útil para establecer si las contraseñas son fáciles de establecer o no mediante este tipo de ataques.

Instalación en Ubuntu.

Simplemente es necesario instalar el paquete que se encuentra en los repositorios estándar.

$ sudo aptitude install john

Utilizar JTR.

El primer paso consiste en generar un archivo intermedio entre la lista de usuarios y sus contraseñas cifradas.

$ sudo unshadow /etc/passwd /etc/shadow > usuarios.db

A continuación se inicia el proceso de verificación de contraseñas por fuerza bruta.

$ john usuarios.db

Este proceso puede tomar una cantidad considerable de tiempo y recursos de procesamiento.  En cualquier momento es posible consultar las contraseñas que ya han sido determinadas y la cantidad de contraseñas pendientes mediante la siguiente instrucción.

$ john -show usuarios.db

Es posible continuar con una sesión previa que fue interrumpida ubicándose en el mismo directorio donde se encuentra el archivo de datos y ejecutando la siguiente instrucción.

$ john -restore

La información del proceso y las contraseñas encontradas se almacenan bajo la ruta ~/.john.

Enlaces.

Acceder a un servicio RDP de Windows a través de un túnel SSH

Introducción.

Esta semana fue necesario acceder remotamente a unos servidores para desplegar en ellos un proyecto.  El servidor web es visible a través de Internet y utiliza GNU/Linux, este expone además del http el servicio ssh al exterior utilizando el puerto 45729.  En la red interna (por obvias razones) se encuentra un segundo servidor con Windows XP SP3 sobre el cual se ejecuta la base de datos MSSQL.

Estructura general de la red

Estructura general de la red

Trivialmente el acceso al servidor web es muy sencillo gracias al SSH.  La transmisión de archivos al servidor Windows también puede ser fácilmente implementada gracias al cliente de Samba que permite una interacción transparente con el protocolo SMB de este sistema operativo.  Pero qué pasa si es necesario acceder a la consola grafica de Windows ?  Desafortunadamente en esta etapa de despliegue es necsario hacer verificaciones y algunos cambios en la base de datos MSSQL y no fue posible encontrar herramientas de administración remotas o web que fueran realmente efectivas.  La única opción sería acceder al escritorio a través de Internet.

Para hacer esto, el primer paso es activar el servicio de escritorio remoto (Remote Desktop Protocol) en Windows XP y autorizar su acceso desde la red LAN.

El segundo paso consiste en garantizar el acceso remoto a través de Internet.  Para esto se deberá aprovechar el protocolo SSH del servidor GNU/Linux.

Una primera aproximación es utilizar el ForwardX11 del servicio SSH a través del cual es posible unrrutar el protocolo de la interfaz gráfica de usuario (X11) hacia el cliente a través de la conexión segura.  Después de establecida la conexión se utiliza la aplicación rdesktop, local al servidor web, y su presentación es redirigida hacia el cliente gracias al protocolo SSH.

Esta aproximación fue exitosa, sin embargo los tiempos de respuesta aunque tolerables, fueron muy largos.

Una segunda aproximación consiste en la creación de un túnel SSH entre el servicio de RDP del servidor con Windows XP y el servidor GNU/Linux que pueda ser accedido desde Internet a través de conexiones seguras.  Esta aproximación, que se describe a continuación, nos dió unos mejores tiempos de respuesta a través de una implementación simple pero mas elegante que la anterior.

Procedimiento.

Como se mencionó anteriormente, el primer paso es la activación del servicio de escritorio remoto en la máquina Windows XP SP3 que se encuentra en la red privada, esto se debe realizar localmente.

El siguiente paso consiste en establecer el túnel entre el cliente y el servidor web (GNU/Linux) a través de Internet involucrando al servidor de bases de datos (Windows XP) asociándolo a un puerto específico del primero.  Para hacer esto se debe ejecutar el siguiente comando desde el cliente.

$ ssh -L 33389:192.168.3.1:3389 -l usuario mi.servidor.com -p 45729 -Nf

mi.servidor.com es el nombre FQDN con el que se accede al servidor GNU/Linux que expone a Internet el servicio de SSH a través del cual se ingresa a la red remota.  El puerto que utiliza en este caso el servicio SSH es el 45729.  De manera similar, la dirección del servidor Windows XP es la 192.168.3.1 y el puerto (por defecto) que utiliza el servicio de RPD es el 3389 con el cual se establece un túnel con el puerto local 33389.

El proceso después de autenticado se envía automáticamente a background retornando el control del shell gracias al parámetro -N especificado, sin embargo es posible verificar el establecimiento del túnel de la siguiente manera.

$ ps -fea | grep ssh

jimezam  25009     1  0 14:46 ?        00:00:00 ssh -L 33389:192.168.3.1:3389 -l usuario mi.servidor.com -p 45729 -Nf

Esquema general del túnel SSH

Esquema general del túnel SSH

En términos generales, el túnel SSH le permite al cliente acceder al servicio RDP remoto (192.168.3.1:3389) desde localhost:33389 de manera transparente a través del servicio SSH de mi.servidor.com.

Finalmente en el cliente se accede al escritorio remoto utilizando una aplicación como rdesktop de la siguiente manera.

$ rdesktop -z localhost:33389

La ejecución de este comando mostrará localmente la consola gráfica de la máquina Windows XP.

Escritorio remoto del servidor Windows XP

Escritorio remoto del servidor Windows XP

Una nota acerca de clientes en Windows.

Este procedimiento puede adaptarse para el uso de clientes Windows utilizando herramientas adicionales como el caso de Putty para el establecimiento del túnel SSH y la aplicación para conexión a escritorios remotos de Windows.

El primero de ellos es software libre y puede obtenerse en la página web de Putty mientras que el segundo hace parte de las herramientas incluídas en Windows XP y se accede a través de los siguientes menúes.

Inicio > Accesorios > Conexión a escritorio remoto.

Conexión al escritorio remoto de Windows XP

Conexión al escritorio remoto de Windows XP

Enlaces.

Georreferenciar inversamente con el API v3 de Google Maps utilizando Javascript

Introducción.

La georreferenciación inversa es el proceso contrario al descrito anteriormente, corresponde al obtener el nombre de una ubicación (topónimo) a partir de su ubicación geográfica, en términos de latitud y longitud.

La aplicación de demostración.

Aplicación de demostración para la georreferenciación inversa con el API 3 de Google Maps

Aplicación de demostración para la georreferenciación inversa con el API 3 de Google Maps

En el ejemplo equivale a conocer la ubicación del sitio: latitud = 5.073375longitud = -75.495155, y con esta información determinar que los siguientes sitios corresponden con esa posición.

  • Carrera 12, Manizales, Caldas, Colombia.
  • Manizales, Caldas, Colombia.
  • Caldas, Colombia.
  • Colombia.

La implementación.

Su implementación es una versión ligeramente mas simple que la georreferenciación directa.

A continuación se detallan a nivel general las variaciones de esta modalidad de georreferenciación con respecto a la expuesta en un artículo anterior.

Del lado de Javascript.

Se asocia el evento de clic sobre el mapa con la función que realizará la georreferenciación inversa (processReverseGeocoding).

google.maps.event.addListener(map, 'click', function(event) {
    processReverseGeocoding(event.latLng, showMarkerInfo);
});

En esta función se realiza el proceso de georreferenciación inversa el cual, al igual que su contraparte directa, es asíncrono y por este motivo se utiliza la misma estrategia del callback.

var request = {
    latLng: location
}
geocoder.geocode(request, function(results, status) {
    // ...
});

La función showMarkerInfo es ejecutada cuando se finaliza la georreferenciación inversa y esta ha obtenido resultados válidos.  Su misión es la mostrar la información resultante en el mapa.

    function showMarkerInfo(locations)
    {
        // Centra el mapa en la ubicación especificada
        map.setCenter(locations[0].geometry.location);
        // Crea el mensaje para mostrar la información georreferenciada
        var infoWindow = new google.maps.InfoWindow();
        infoWindow.setPosition(locations[0].geometry.location);
        // Prepara el mensaje con la información obtenida del proceso
        // de georreferenciación inversa
        var content = 'Latitud:  ' + locations[0].geometry.location.lat() + '<br />' +
                      'Longitud:  ' + locations[0].geometry.location.lng() + '<br />' +
                      '<br />Topónimos:<br /><ul>';
        for (var i=0; i<locations.length; i++)
        {
            if (locations[i].formatted_address)
                content += '<li>' + locations[i].formatted_address + '</li>';
            else
                content += '<li>No se encontró información.</li>';
        }
        content += "</ul>";
        infoWindow.setContent(content);
        // Muestra el mensaje sobre el mapa
        infoWindow.open(map);
    }

Enlaces.

Georreferenciar con el API v3 de Google Maps utilizando Javascript

Introducción.

La georreferenciación es el proceso de determinar la ubicación (latitud, longitud) de una ubicación en la tierra según su nombre (topónimo).

Si estoy en Manizales, Caldas, Colombia estaré realmente en latitud 5.07, longitud -75.52056.

Existen varias formas de realizar este proceso dependiendo de los lenguajes de programación, interfaces y fuentes de información utilizados.  Anteriormente expliqué como hacerlo utilizando C#/Mono y el servicio web de Geonames, en este caso voy a utilizar el API Javascript versión 3 de Google Maps.

La aplicación de demostración.

Aplicación de demostración para la georreferenciación con el API 3 de Google Maps

Aplicación de demostración para la georreferenciación con el API 3 de Google Maps

Como se mencionó anteriormente, la funcionalidad es precisa: convertir el nombre de una ubicación en su correspondiente localización en términos de latitud y longitud, y mostrarla en el mapa.

La implementación.

Del lado de Javascript.

Incluír la referencia al API de Google Maps.

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&language=es"></script>

Instanciar el geocodificador.

var geocoder = new google.maps.Geocoder();

Crear el mapa.

window.onload = function() {
    var options = {
        zoom: 5,
        center: new google.maps.LatLng(4, -72),
        mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    map = new google.maps.Map(document.getElementById('map'), options);
    // ...

Establecer la funcionalidad del botón Buscar.

    document.getElementById('search').onclick = function() {
            // Obtiene la ubicación (string) a georreferenciar
            var location = document.getElementById('location').value;
            // Inicia el proceso de georreferenciación (asíncrono)
            processGeocoding(location, addMarkers);
            // Detiene el procesamiento del evento
            return false;
        }
    } // Fin del método window.onload

El proceso de georreferenciación es asíncrono así que es necesario especificar una función (callback) para que se ejecute si la georreferenciación termina exitosamente.

El núcleo de la georreferenciación se realiza en la invocación al método geocoder.geocode(opciones, implementacion).  Es necesario determinar el estado final (status) después de la ejecución asíncrona para determinar que acción puede realizarse con los datos resultantes.

    function processGeocoding(location, callback)
    {
        // Propiedades de la georreferenciación
        var request = {
            address: location
        }
        // Invocación a la georreferenciación (proceso asíncrono)
        geocoder.geocode(request, function(results, status) {
            /*
             * OK
             * ZERO_RESULTS
             * OVER_QUERY_LIMIT
             * REQUEST_DENIED
             * INVALID_REQUEST
             */
            // Notifica al usuario el resultado obtenido
            document.getElementById('message').innerHTML = "Respuesta obtenida: " + status;
            // En caso de terminarse exitosamente el proyecto ...
            if(status == google.maps.GeocoderStatus.OK)
            {
                // Invoca la función de callback
                callback (results);
                // Retorna los resultados obtenidos
                return results;
            }
            // En caso de error retorna el estado
            return status;
        });
    }

La función enviada como callback (addMarkers) es la responsable de tomar las ubicaciones retornadas por la georreferenciación, crearles marcadores, ventanas de información, asociarlas al evento de clic y mostrarlas en el mapa.

   function addMarkers(geocodes)
    {
        for(i=0; i<geocodes.length; i++)
        {
            // Centra el mapa en la nueva ubicación
            map.setCenter(geocodes[i].geometry.location);
            // Valores iniciales del marcador
            var marker = new google.maps.Marker({
                map: map,
                title: geocodes[i].formatted_address
            });
            // Establece la ubicación del marcador
            marker.setPosition(geocodes[i].geometry.location);
            // Establece el contenido de la ventana de información
            var infoWindow = new google.maps.InfoWindow();
            content = "Ubicación: " + geocodes[i].formatted_address + "<br />" +
                      "Tipo: " + geocodes[i].types + "<br />" +
                      "Latitud: " + geocodes[i].geometry.location.lat() + "<br />" +
                      "Longitud: " + geocodes[i].geometry.location.lng();
            infoWindow.setContent(content);
            // Asocia el evento de clic sobre el marcador con el despliegue
            // de la ventana de información
            google.maps.event.addListener(marker, 'click', function(event) {
                infoWindow.open(map, marker);
            });
        }
    }

Del lado de HTML.

Se implementa una estructura muy simple para desplegar la información.  Una primera parte permite desplegar el mensaje de información acerca del estado del proceso y le permite al usuario ingresar el nombre de la ubicación a georreferenciar.

    <!-- Mensaje de estado -->
    <div id="message"></div>
    <!-- Ubicación a georreferenciarse -->
    <label for="location">Ubicación:</label>
    <input type="text" id="location" name="location" value="" size="40" />
    <!-- Botón para inciar la georreferenciación -->
    <input type="button" id="search" name="search" value="Buscar" />

Una segunda parte establece la ubicación para desplegar el mapa.

<!-- Lugar de despliegue del mapa -->
<div id="map"></div>

Enlaces.

Desactivar el envío de SMS promocionales en Tigo

Introducción.

Una característica exageradamente molesta y común de los proveedores de servicios telefónicos celulalres es el atacarnos con mensajes SMS no solictados o SPAM.  Esta práctica no sólo es ilegal sino inmoral pero igual sucede frecuentemente.

En lo personal me deshice de ellos hace varios años cuando, por extraño que parezca, era necesario enviar una carta certificando que uno no estaba interesado en recibir su desafortunado SPAM.

Sin embargo hace unos pocos días cometí del error de probar las opciones de una nueva supuesta oficina virtual y gracias a esto empecé a recibir entre 3 y 4 veces por día mensajes no solicitados acerca de concursos y de como ganarme un Blackberry.  Molesto llamé al servicio al cliente para deshacerme de ellos y encontré que ahora es posible desactivar los mensajes de manera automatizada a través de un menú de opciones.

Procedimiento.

El procedimiento es por suerte muy simple.

  1. Llamar al servicio al cliente: *300.
  2. Seleccionar la opción de servicios: 4.
  3. Seleccionar la opción de cancelar los mensajes promocionales: 3.
  4. Aceptar la cancelación de los mensajes promocionales: 1.

Georreferenciando de manera fácil y simple con GeoNames utilizando C#/Mono

Introducción.

Últimamente he tenido ganas de desarrollar en C# para retomar este lenguaje que tengo un poco abandonado y al mismo tiempo probar los avances que ha tenido el proyecto Mono.  Que mejor inicio que desarrollar una aplicación muy simple para georreferenciar ciudades aprovechando el servicio web de Geonames, esta a su vez me servirá para complementarla y solucionar un problema de información que tengo para otro proyecto.

Como se podrá apreciar, el código es muy sencillo ya que no utilizo ninguna librería adicional a las estándar, sin embargo es interesante y puede serle de utilidad a alguien mas.

Cuál es la necesidad ?

El problema que se tiene es la necesidad de georreferenciar ubicaciones, es decir, conociendo su nombre obtener su ubicación en términos de latitud y longitud.

Si estoy en Manizales, Caldas, Colombia estaré realmente en latitud 5.07, longitud -75.52056.

Es necesario tener en cuenta que muchas veces el servicio de georreferenciación retorna múltiples resultados ya que pueden haberse referenciado varios sitios con el mismo nombre, haberse cambiado el nombre de la ubicación o tenerse varios hitos de diferente nivel administrativo.  En Geonames esto último se identifica mediante los campos de características.  Consulte el sitio web de Geonames para mas información acerca de los feature codes.

Cómo es el servicio web ?

El servicio web de búsquedas de Geonames recibe una cadena de texto con la ubicación que se desea georreferenciar y retorna una cadena de texto en formato XML (en este caso) con los resultados obtenidos.

Por ejemplo, la consulta de Manizales, Caldas, Colombia se realizaría a través del siguiente URL.

http://ws.geonames.org/search?lang=es&type=xml&style=FULL&q=Manizales,Caldas,Colombia

Retornará el siguiente resultado.

<geonames style="FULL">
    <totalResultsCount>2</totalResultsCount>
    <geoname>
        <toponymName>Manizales</toponymName>
        <name>Manizales</name>
        <lat>5.07</lat>
        <lng>-75.52056</lng>
        <geonameId>3675443</geonameId>
        <countryCode>CO</countryCode>
        <countryName>Colombia</countryName>
        <fcl>P</fcl>
        <fcode>PPLA</fcode>
        <fclName>city, village,...</fclName>
        <fcodeName>seat of a first-order administrative division</fcodeName>
        <population>357814</population>
        <alternateNames>Manisales,Manisalesas,Manizales,Манисалес</alternateNames>
        <elevation/>
        <continentCode>SA</continentCode>
        <adminCode1>37</adminCode1>
        <adminName1>Caldas</adminName1>
        <adminCode2/>
        <adminName2/>
        <alternateName lang="en">Manizales</alternateName>
        <alternateName lang="pl">Manizales</alternateName>
        <alternateName lang="lt">Manisalesas</alternateName>
        <alternateName lang="bg">Манисалес</alternateName>
        <alternateName lang="cs">Manizales</alternateName>
        <alternateName lang="link">http://en.wikipedia.org/wiki/Manizales</alternateName>
        <alternateName lang="de">Manizales</alternateName>
        <alternateName lang="sv">Manizales</alternateName>
        <alternateName lang="es">Manizales</alternateName>
        <alternateName lang="no">Manizales</alternateName>
        <alternateName lang="pt">Manizales</alternateName>
        <timezone dstOffset="-5.0" gmtOffset="-5.0">America/Bogota</timezone>
        <score>1.0</score>
    </geoname>
    <geoname>
        <toponymName>Manizales</toponymName>
        <name>Manizales</name>
        <lat>5.08333</lat>
        <lng>-75.5</lng>
        <geonameId>3675444</geonameId>
        <countryCode>CO</countryCode>
        <countryName>Colombia</countryName>
        <fcl>A</fcl>
        <fcode>ADM2</fcode>
        <fclName>country, state, region,...</fclName>
        <fcodeName>second-order administrative division</fcodeName>
        <population/>
        <alternateNames/>
        <elevation/>
        <continentCode>SA</continentCode>
        <adminCode1>37</adminCode1>
        <adminName1>Caldas</adminName1>
        <adminCode2>3675444</adminCode2>
        <adminName2>Manizales</adminName2>
        <timezone dstOffset="-5.0" gmtOffset="-5.0">America/Bogota</timezone>
        <score>0.3448537290096283</score>
    </geoname>
</geonames>

Finalmente la tarea de la aplicación que consuma el servicio será procesar el documento XML y obtener la información que necesite.

Cómo es la implementación ?

Las librerías (namespaces) utilizados son los siguientes.

using System;
using System.Net;
using System.Text;
using System.Xml;
using System.Collections.Generic;

Se prepara el URL para consumir el servicio con la ubicación a geocodificar.  En este caso, especifico una por defecto si no suministra una.

string location = (args.Length == 0) ? "Manizales, Caldas, Colombia" : args[0];
string url = "http://ws.geonames.org/search?lang=es&type=xml&style=FULL&q=" + location;

Se consume el servicio web y se obtiene la cadena de texto con la respuesta en formato XML.

WebClient client = new WebClient();
string xml = client.DownloadString(url);

Se crea un objeto para manipular el documento XML recién recibido, se obtienen los nodos relacionados con la respuesta de la georreferenciación (geoname)  y se verifica que haya registros, de lo contrario no habrá nada que hacer.

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
XmlNodeList geonames = doc.GetElementsByTagName("geoname");
if(geonames.Count == 0)
 throw new Exception("There is no geonames in the result!");
else
 Console.WriteLine("I found " + geonames.Count + " places!\n");

Se recorren los resultados obtenidos extrayéndoles la información georreferenciada que se requiera.

foreach(XmlElement place in geonames)
{
 Console.WriteLine("* LUGAR: " + place["name"].InnerText + "\n");
 Console.WriteLine("\tNombre: "          + place["name"].InnerText);
 Console.WriteLine("\tDepartamento: "    + place["adminName1"].InnerText);
 Console.WriteLine("\tLatitud: "         + place["lat"].InnerText);
 Console.WriteLine("\tLongitud: "        + place["lng"].InnerText);
 Console.WriteLine("\tPaís: "            + place["countryName"].InnerText +
                   " (" + place["countryCode"].InnerText + ")");
 Console.WriteLine("\tPoblación: "       + place["population"].InnerText + " habitantes");
 Console.WriteLine("\tPaís: "            + place["countryName"].InnerText +
                   " (" + place["countryCode"].InnerText + ")");
 Console.WriteLine("\tCaracterísticas: " + place["fcl"].InnerText + "/" + place["fcode"].InnerText +
                   "> " + place["fclName"].InnerText + " (" + place["fcodeName"].InnerText + ")");
 Console.WriteLine();
}

Cómo construyo el programa ?

Si se utiliza VisualStudio/MonoDevelop sólo es necesario abrir la solución (incluída en el paquete de demostración) y ejecutar el comando de Build o Run del menú.

Si se desea realizar manualmente deberá ejecutarse un comando como el siguiente.

$ gmcs "/out:GeoCoderSimple.exe" "/r:/usr/lib/mono/2.0/System.dll" "/r:/usr/lib/mono/2.0/System.Xml.dll" /t:exe "geonames_search_raw/Main.cs"

Donde geonames_search_raw/Main.cs es el código fuente de la aplicación y GeoCoderSimple.exe será el archivo binario resultante.

Cómo ejecuto el ejemplo ?

Ya sea invocándolo desde el IDE o desde línea de comando, el programa recibe un argumento.  En caso de obviarse este argumento se utilizará el valor por defecto.

$ ./GeoCoderSimple.exe

I found 2 places!
* LUGAR: Manizales
 Nombre: Manizales
 Departamento: Caldas
 Latitud: 5.07
 Longitud: -75.52056
 País: Colombia (CO)
 Población: 357814 habitantes
 País: Colombia (CO)
 Características: P/PPLA> city, village,... (seat of a first-order administrative division)
* LUGAR: Manizales
 Nombre: Manizales
 Departamento: Caldas
 Latitud: 5.08333
 Longitud: -75.5
 País: Colombia (CO)
 Población:  habitantes
 País: Colombia (CO)
 Características: A/ADM2> country, state, region,... (second-order administrative division)

Enlaces.

Problemas de conexión a las cuentas de Messenger desde Empathy 2.32.0

Introducción.

Desde hace un par de días las conexiones con el servicio de Microsoft Messenger Live han dejado de funcionar desde la aplicación de mensajería Empathy de Gnome mientras que los demás protocolos funcionan normalmente.

Conexión fallida a una cuenta de Messenger desde Empathy

Conexión fallida a una cuenta de Messenger desde Empathy

Todo parece indicar que se trata de un problema introducido en una actualización de la librería papyon la cual es la responsable de realizar las conexiones con el protocolo de Microsoft para esta aplicación (telepathy-butterfly) y otras similares basadas en Python.

Solución.

Mientas se publica el paquete con la versión actualizada de esta librería en los repositorios oficiales es posible manipularla para introducir manualmente el parche necesario para superar este impase.  Para hacerlo, simplemente siga los pasos descritos a continuación.

Desactive las cuentas de Messenger en el Empathy.

Abra una terminal y ejecute los siguientes comandos.

$ cd /usr/lib/pymodules/python2.6/papyon/service/description/SingleSignOn

$ sudo vi RequestMultipleSecurityTokens.py

(busque la siguiente línea)
CONTACTS = ("contacts.msn.com", "?fs=1&id=24000&kv=7&rn=93S9SWWw&tw=0&ver=2.1.6000.1")

(reemplácela con la siguiente línea)
CONTACTS = ("contacts.msn.com", "MBI")

Active nuevamente las cuentas de Messenger en el Empathy.

Deberán conectar exitosamente otra vez!

Enlaces.