Archivo de la categoría: Técnico

Sobre diseñadores, programadores y formación

El pasado sábado un buen amigo me pidió que le acompañase a un seminario sobre Flex. No me apetecía mucho, y menos aún madrugar un fin de semana, pero es lo que tiene la amistad. Al final resultó que era un pequeño avance de lo que sería un curso de formación continua de 30 horas sobre desarrollo de RIA con Flex3. El profesor era programador profesional y bastante majete, pero mi sorpresa llegó cuando preguntó a la sala por el perfil profesional que teníamos. Para un curso de desarrollo había aproximadamente 28 diseñadores sobre 30 personas. Mi cara de asombro fue abrumadora, alguien no había entendido de qué iba la movida. Mi amigo, perro viejo ya en estas lides, no se cortó un pelo y comenzó a preguntar sobre las salidas de una herramienta como Flex para un diseñador, y ahí llegó lo peor, el profesor hizo una defensa a ultranza de las posibilidades que tenía, obviamente siempre bajo su punto de vista de programador, para muestra un botón:

Es muy sencillo, no tienes que saber programación, casi todo se hace con una línea de código. Yo tardé muy poco en comenzar a trabajar, aunque es verdad que tenía experiencia programando en muchos otros lenguajes.

De verdad, yo estaba alucinando. Lo peor es que convenció a mucha gente, y no lo digo por él, que me parece fenomenal que se gane un dinero, lo digo por la utilidad de un curso así para un diseñador. Tampoco debemos olvidarnos del otro lado de la ecuación. Con lo fácil que es hoy en día informarte y documentarte sobre una herramienta, la gente acude sin saber absolutamente nada acerca de lo que es Flex y para qué sirve.

En mi humilde opinión hubiese mejorado considerablemente el curso si hubiese optado por cambiar la orientación hacia skining puro y duro en Flex, una de las tareas más complicadas (los programadores tampoco son diseñadores) y a la vez desconocidas y con más salidas (bajo mi punto de vista), pero claro, él tampoco tenía los conocimientos adecuados para dar un curso de diseño, así son las cosas. En varios momentos de la charla hizo referencia a Adobe Flash Catalyst, la nueva gran herramienta de Adobe que llevará a desarrollar RIA’s de próxima generación, la simbiosis perfecta entre diseño y programación, pero se limitó a decir cosas como: “según la demo que nos hicieron en junio“, “por lo poco que he visto“… ¡Podías haberla probado tu mismo! Y es que os aseguro que será una revolución, una integración absoluta entre apariencia y funcionalidad.

Y todo esto me lleva al meollo de la cuestión: la gran mentira de la formación (de cualquier tipo) en este país. A principios de esta década impartí varios cursos, se pagaban muy bien y era una muy buena oportunidad, pero mi ética me obligó a abandonar la formación. Os pondré un par de ejemplos:

  • Allá por 2000 impartí un curso de 20 horas sobre Windows NT (¿quien se acuerda de eso? 😛 ) para trabajadores de Xerox. Patético. A los asistentes lo único que les interesaba era saber configurar una impresora en una red local, el resto les daba absolutamente igual. Claro, imaginaos el estado anímico de la  gente cuando lo que les interesa de verdad no llega casi hasta el final… Da igual que necesites saber qué son privilegios de administrador o colas de impresión, ellos sólo querían que les explicase lo que necesitaban para su trabajo diario, lógico también.
  • Un año después impartí la parte práctica de un curso sobre comercio electrónico organizado por la Universidad Politécnica de Valencia.  Mi clase versaba sobre el desarrollo de una tienda electrónica (bases de datos, programación, pasarela de pagos…). ¿La audiencia? Estudiantes de últimos cursos de derecho, empresariales, psicología… ya se sabe, créditos de libre elección. ¿A quien se le ocurrió incluir algo tan técnico en el programa de este curso?

Este último ejemplo me llevó a mi primer desengaño con la formación. Las negociaciones las llevé a cabo con una de las cabezas más visibles de la Universidad Politécnica (no diré nombres 😛 ). Un día, comiendo juntos, este personaje me soltó tan libremente:

Tú, si quieres hacer un curso, no tienes más que decírmelo. Preparas un programa, me dices cuánto te quieres llevar, vemos cual sería el coste por alumno y del resto nos encargamos nosotros. Al ser cursos patrocinados por la Universidad siempre hay gente dispuesta a apuntarse y pagar lo que haga falta.

¡Qué inocente era yo de aquélla! Podía haberme forrado pero preferí dejarlo para quien no tuviese problemas morales, no entendía que en un curso medianamente técnico se mezclase gente de perfiles tan dispares llevando la utilidad del mismo al mero canje de créditos o a un diploma con el que rellenar un curriculum.

En aquellos días llegó mi divorcio definitivo con la formación. Acudí a una entrevista en una de esas empresas líderes en Valencia, de esas que se dedican a impartir cursos para certificaciones. Querían organizar un curso sobre desarrollo en C bajo Linux. Os recuerdo que hablamos de 2001, así que me pareció alucinante que a alguien se le ocurriese montar algo de ese calado, si aún hoy es raro ver un curso así imaginaos hace ocho años. Mi conversación terminó casi en discusión, a modo de resumen:

  • Yo:  ¿A qué perfil de alumnos va orientado el curso?
  • NI: A cualquiera que se apunte y pague.
  • YO: No, a ver, no puedes mezclar en un mismo curso a gente que no ha visto Linux en su vida, gente que no sabe programar, gente que ya programa en C bajo Windows y quiere cambiar, gente que tiene conocimientos básicos de programación…
  • NI: ¿Cómo que no? Con que paguen…
  • YO: Pero vamos a ver, ¿quieres que la gente aprenda algo y aprovechen el curso?
  • NI: (silencio…)

Ahí entendí que esto no estaba hecho para mi. No podía engañar así a la gente.
Y esto me lleva de vuelta al comienzo del artículo. De nuevo por aquella época (2001) este amigo mío, en sus ansias por mejorar como diseñador, se planteó tener unos conocimientos mínimos de programación que le permitiesen hacer cosas básicas por su cuenta, así que se me apunta en este último sitio del que os he hablado a un curso sobre Visual Basic 6. Sabéis cómo acabó la historia ¿no? Creo que no llegó ni a asistir una semana.

Esto es una mi@3%6, estoy yo que no tengo ni idea, están los que vienen a clase a plantear problemas que tienen en su trabajo y ver como resolverlos (ya programadores), están los que les da todo igual… imposible aprender nada. Si explica a los que saben, nosotros no nos enteramos de nada, y si nos explica a nosotros los demás se aburren.

Esto, queridos lectores, es la formación (al menos la técnica): dinero, subvenciones y más dinero. Ah, y adelgazar las cifras del paro, no nos olvidemos 😉 . Ah, y lo más triste de todo, no ha cambiado nada en los últimos años, incluso diría que vamos a peor.

Vsftpd con autentificación a través de servidor Radius y MySQL

Le he cogido gusto a lo del Radius y centralizar todos los temas de autentificación bajo su paraguas 😛 .

Por un lado hemos visto como crear vpn’s y puntos de acceso wireless con Radius y por otro cómo integrar vsftpd con MySQL utilizando usuarios de ftp virtuales. ¿Por qué no combinarlos? ¿Para qué vamos a querer mantener una nueva base de datos para ftp si ya tenemos nuestro Radius preparado para realizar autentificaciones? Está casi todo hecho, sólo nos falta tocar las teclas adecuadas.

Vamos a suponer que tu servidor Radius ya está funcionando y que ya has montado tu sistema vsftpd con usuarios virtuales, en los links anteriores se explica todo. Configuraremos ahora nuestro servidor ftp para que se autentique contra Radius en vez de contra MySQL directamente.

Como vsftpd utiliza PAM para autenticarse, deberemos solamente cambiar el módulo MySQL por el correspondiente a Radius. En aquél momento utilizábamos pam_mysql. ahora haremos lo mismo con pam_radius, así de sencillo.

En mi caso, como sabéis, utilizo CentOS5, y curiosamente no está disponible un rpm con este módulo, así que toca prepararlo a mano, la suerte es que en los fuentes viene el archivo .spec adecuado para crearlo directamente. Si para vuestro sistema tenéis el instalable adecuado podéis saltaros este paso.

Podríamos compilar e instalar directamente el tar, pero prefiero crear un rpm que permita actualizarlo y desinstalarlo a posteriori. Descargamos el código fuente desde aquí, lo desempaquetamos en una ruta temporal y copiamos el propio tar a la ruta adecuada para compilar el rpm:

#cp pam_radius-1.3.17.tar.gz /usr/src/redhat/SOURCES/pam_radius_auth-1.3.17.tar.gz
#tar xvfz pam_radius-1.3.17.tar.gz
#cp pam_radius-1.3.17/pam_radius_auth.spec /usr/src/redhat/SPECS

Ahora debemos hacer un pequeño cambio en este archivo spec. Deberemos editarlo y modificar la línea 2

%define version 1.3.15

actualizando el número de versión correspondiente:

%define version 1.3.17

Y añadimos la línea

BuildRequires: pam pam-devel

Después de la línea 15, de manera que queda

%define name pam_radius_auth
%define version 1.3.17
%define release 0

Name: %{name}
Summary: PAM Module for RADIUS Authentication
Version: %{version}
Release: %{release}
Source: ftp://ftp.freeradius.org/pub/radius/pam_radius_auth-%{version}.tar
URL: http://www.freeradius.org/pam_radius_auth/
Group: System Environment/Libraries
BuildRoot: %{_tmppath}/%{name}-buildroot
License: BSD-like or GNU GPL
Requires: pam
BuildRequires: pam pam-devel

Ya tenemos todo preparado para compilar el módulo

#rpmbuild -ba /usr/src/redhat/SPECS/pam_radius_auth.spec

Si todo va bien tendremos en /usr/src/redhat/RPMS/x86_64 el rpm del paquete preparado para instalar. En mi caso, como es un sistema de 64bits está en la carpeta x86_64, en tu caso puede estar en i386. Sólo nos queda instalarlo.

rpm -ivh /usr/src/redhat/RPMS/pam_radius_auth-1.3.17-0.x86_64.rpm

Vamos ahora a configurar lo poco que hay que configurar 😛 . Indicamos la dirección del servidor Radius y la clave de acceso al mismo:

/etc/raddb/server

127.0.0.1       claveSecretaRadius             1

Cambiamos la configuración PAM de vsftpd para que en vez autenticarse con MySQL utilice Radius:

/etc/pam.d/vsftpd

auth required /lib/security/pam_radius_auth.so debug
account required /lib/security/pam_radius_auth.so debug

Eso es todo. Nuestro servidor FTP se autenticará contra el servidor Radius. Hemos añadido la opción de depuración (debug) para comprobar el funcionamiento, cuando estemos seguros de que todo funciona bien podemos eliminarla.

Si tienes algún problema, para verificar qué hace el módulo pam_radius debemos comprobar el log estándar de debug. Por defecto no viene activado en el demonio syslog, así que lo añadimos:

/etc/syslog.conf

*.debug       /var/log/debug.log

Intentamos acceder al servidor FTP y comprobamos qué está haciendo el módulo de autentificación en este log:

cat /var/log/debug.log

Sep 28 17:11:09 osiris vsftpd: pam_radius_auth: Got user name XXX
Sep 28 17:11:09 osiris vsftpd: pam_radius_auth: Sending RADIUS request code 1
Sep 28 17:11:09 osiris vsftpd: pam_radius_auth: DEBUG: getservbyname(radius, udp) returned 1214042784.
Sep 28 17:11:09 osiris vsftpd: pam_radius_auth: Got RADIUS response code 2
Sep 28 17:11:09 osiris vsftpd: pam_radius_auth: authentication succeeded

Si hubiese algún problema deberíamos verlo ahí, pero si ya tenías Radius y vsftpd funcionando, todo debería ir bien.

Hasta aquí hemos llegado, hemos integrado un nuevo servicio de nuestra red en el mismo sistema de autentificación. Cualquier demonio que utilice PAM para autenticarse se debería configurar del mismo modo. Ssh es el ejemplo más claro, se haría prácticamente igual.

Punto de acceso inalámbrico con autentificación a través de servidor Radius y MySQL

Si hace unos meses os mostraba cómo crear conexiones VPN entre máquinas Linux y Windows con autentificación a través de servidor Radius y MySQL hoy veremos cómo aprovechar la misma estructura para autentificar usuarios que se conecten a nuestra red wifi de manera que tengamos todo el entorno de acceso a nuestra red integrado. En capítulos posteriores puede que veamos cómo añadir más servicios.

Ante todo aviso que no soy ningún experto en la materia y puede que diga cosas que no son correctas 😛 , por favor, no dudéis en corregirme.

La parte más importante del sistema la tenemos ya preparada, si necesitas montar todo el servidor Radius puedes leer el artículo anterior donde se explica bien el procedimiento. En esta ocasión nos centraremos en hacer que el servidor Radius que ya teníamos autentifique a los usuarios que se conecten a nuestro punto de acceso inalámbrico.

En un escenario normal es el punto de acceso el que se encarga de autentificar al cliente que intenta conectarse a través de las conocidas contraseñas (técnicamente shared-keys) con el inconveniente de que todos los usuarios deben utilizar la misma y si hay que cambiarla hay que comunicar a todos ellos cual es la nueva.

Hay dos conceptos dentro de todo este sistema que van siempre juntos pero son distintos:

  1. La encriptación de la comunicación entre el cliente y el punto de acceso, las conocidas WEP, WAP y WAP2.
  2. La shared-key de acceso al router.

Estos dos elementos son completamente independientes. No me voy a meter técnicamente en los distintos tipos de encriptación ya que no son el objetivo del artículo y hay toda la documentación que quieras al respecto. Para nuestro ejemplo vamos utilizar WPA2 con cifrado AES. Recuerda que estos parámetros sólo importan al punto de acceso y al cliente que se conecta, se utiliza para asegurar el canal inalámbrico, el servidor Radius es completamente ajeno a estos mecanismos.

En nuestro escenario el procedimiento descrito anteriormente quedaría como muestra este gráfico.

Dibujo1

Es decir, para que un cliente se conecte a nuestro punto de acceso necesitará las credenciales adecuadas. Una vez el AP las recibe, consulta al servidor Radius si son correctas y debe dejarle pasar, en cuyo caso el cliente ya puede acceder a nuestra red. Cómo os habréis dado cuenta, entre el AP y Radius no he nombrado en ningún momento WAP2 o AES, éstos se utilizan sólamente en lo que en el gráfico superior es el rayo amarillo, el medio inalámbrico. En resumen, la diferencia entre la configuración habitual con shared-key estática y ésta es que permitimos que cada usuario tenga su login independiente, pudiendo cancelar su acceso en cualquier momento, algo que puede ser muy útil en ciertos ambientes, sobre todo corporativos.

Configurando el servidor Radius

Nuestro servidor Radius ya estaba configurado, sólo deberemos hacer un par de modificaciones. Primero añadimos permiso para que se conecte el punto de acceso y pueda autentificar a los clientes, para ello agregamos lo siguiente al archivo /etc/raddb/clients.conf:

client 192.168.100.100 {
	secret = clavesecreta
	shortname = osusnet
}

Donde la ip es la del APsecret es la contraseña que utilizará el AP para conectarse a Radius y shortname un identificador interno. A continuación editamos el archivo /etc/raddb/eap.conf dejando las siguientes secciones de este modo:

default_eap_type = peap

tls {
        private_key_password = whatever
        private_key_file = ${raddbdir}/certs/cert-srv.pem
        certificate_file = ${raddbdir}/certs/cert-srv.pem
        CA_file = ${raddbdir}/certs/demoCA/cacert.pem
        dh_file = ${raddbdir}/certs/dh
        random_file = ${raddbdir}/certs/random
}

peap {
        default_eap_type = mschapv2
}

Eap es el método de autenticación que se utilizará, en el caso que tratamos será PEAP (conocido también como EAP-MSCHAPv2) y necesita además TLS, por eso debemos añadir los dos en la configuración de Radius. En mi caso, la instalación de FreeRadius crea los certificados necesarios así que no entraré más en detalle sobre este tema, supondremos que existen y funcionan bien. Ya solo queda reiniciar el demonio Radius para que los cambios comiencen a estar visibles.

Configurando el punto de acceso

En este punto hemos terminado la configuración en nuestro servidor Linux. Configuraremos ahora el router, en mi caso un SMC. Tendremos que utilizar un cable ethernet para acceder al router ya que no podremos entrar por wifi hasta que lo tengamos bien configurado. La configuración en tu router será muy parecida a ésta. En las opciones “Wireless” le indicamos que la seguridad será WPA:

router4

En la configuración WPA especificamos los parámetros que hemos acordado, WPA2 y AES. Además le indicamos que no utilizaremos pre-shared key para autenticación sino que se hará a través del protocolo 802.1x. Los demás parámetros no nos importan.

router1Finalmente configuramos las opciones de autenticación 802.1x habilitándola e indicando la dirección IP de nuestro servidor Linux con Radius.

router2Ya está el router configurado. Sólo nos queda hacer lo propio con nuestro cliente inalámbrico. Como hemos escogido WAP2 supondremos que la tarjeta wireless del portátil la soporta, si no ¿por qué la has escogido? 😛 .

Configurando los clientes inalámbricos

Al buscar las redes inalámbricas disponibles en el portátil veremos la del router que hemos configurado. Por defecto no podremos acceder a ella, debemos indicarle cómo debe autenticarse, para ello vamos a “Configuración avanzada” y en la lista de redes seleccionamos la que nos ocupa y vamos a propiedades.

En la venta que se abre seleccionamos, en la pestaña “Asociación“,  los métodos de autenticación y cifrado que hemos definido:

wifi3En la pestaña “Autenticación” seleccionamos “PEAP” como tipo de EAP y vamos a “Propiedades“:

wifi1En la ventana que se abre eliminamos la selección de “Utilizar certificado cliente” y en el método de autenticación “EAP-MSCHATP v2” vamos a “Configurar”, eliminando en la ventana que se abre la opción que viene señalada por defecto.

wifi0Aceptamos todos los cambios hacia atrás y guardamos la configuración. A los pocos segundos veremos este aviso:

wifi4Parece que funciona 😛 . Pinchamos en el aviso y nos salta la venta que buscamos:

wifi5Introducimos nuestro usuario y clave de Radius y padentro!

Eso es todo, hemos conseguido el objetivo del artículo, utilizar la misma infraestructura de autentificación para el entorno inalámbrico que la que teníamos para el acceso remoto por VPN.

Cómo realizar backups centralizados de sistemas Windows desde Linux

Hoy voy a contar cómo tengo montado en mi casa el sistema de copias de seguridad para los equipos de escritorio basados en Windows, concretamente para mi portátil.

Lo primero, obviamente, es compartir las carpetas o unidades de las que vas a querer hacer copia, en mi caso la unidad d: completa y mi escritorio, tengo todos los documentos y archivos centralizados en esas dos rutas. Lógicamente no debes hacerlas públicas, sino cualquiera que tenga acceso por red a tu equipo podrá entrar a todo, hay que compartirlas y punto, el que quiera entrar que se autentifique como hará nuestro sistema.

Las copias están centralizadas desde el servidor Linux del que ya os he hablado otras veces, basado en CentOS5. En este equipo tengo un disco duro USB con dos particiones, una ext3 para copias del propio servidor y otra NTFS para las de los equipos Windows. Para evitar pérdidas por cortes, el disco se monta y desmonta automáticamente cuando se necesita a través del sistema autofs, de este modo cada vez que se acceda a la ruta predefinida se montará el disco y pasado un tiempo de inactividad se desmonta. Veamos primero cómo preparar este sistema.

Lo primero que necesitaremos son los drivers para acceder a partificiones NTFS desde Linux, yo utilizo ntfs-3g. En mi caso es tan sencillo como hacer:

yum instal ntfs-3g

Mi equipo tiene varias rutas de automontaje, vamos a ver las que nos interesa que son las del disco usb. Primero configuramos los dos archivos necesarios:

/etc/auto.master

/mnt/backup /etc/auto.backup

/etc/auto.backup

backupusb       -fstype=auto    :/dev/sdb1
backupntfs      -fstype=ntfs-3g,rw,fmask=666,dmask=777,gid=users,nls=iso8859-1,locale=es_ES     :/dev/sdb2

En el primero indicamos que nuestras unidades se van a montar en /mnt/backup y que lea la configuración del segundo archivo mientras que en éste especificamos qué montar y cómo hacerlo.

Como veis he definido dos puntos de montaje correspondientes a las dos particiones. El primer parámetro indica la “carpeta” dónde los montará dentro de la definida en master, en este caso /mnt/backup/backupusb y /mnt/backup/backupntfs. La importante en nuestro caso es la segunda donde indicamos el tipo de archivos y el lenguaje en el que están los nombres, imprescindible para que se lean bien los acentos y eñes del castellano.

Ya sólo debemos iniciar el demonio autofs y probar las rutas.

service autofs status

Si ahora intentamos acceder a /mnt/backup/backupntfs deberíamos poder entrar a la unidad del disco correspondiente y ver su contenido. Ya tenemos la mitad del trabajo resuelto.

Vayamos ahora con el backup propiamente dicho. Creamos el punto de montaje de la unidad que vamos a copiar, en este caso /mnt/portatil. Ahí montaremos temporalmente la unidad remota para hacer las copias. Creamos además en la partición del disco USB el directorio donde meteremos el backup, en mi caso /portatil. Ya estamos preparados para copiar. Ah no, falta un detalle. ¿Cómo lo hacemos si no queremos copiar los tropecientos Gb cada vez que se haga la copia?. El socorrido rsync viene en nuestra ayuda. Rsync permite sincronizar datos entre dos rutas sin copiarlo todo, simplemente elimina lo que ya no existe  y copia lo nuevo y lo que ha cambiado sin tocar lo que no ha variado, con lo que en un ambiente normal será poco lo que hay que copiar.

Este sería mi script.

#!/bin/sh
PORTATIL=192.168.0.79
fecha=`date +%Y-%m-%d`

#MONTAMOS EL USB
if test -d "/mnt/backup/backupntfs"
	then
		echo "Dispositivo USB correcto"
	else
		echo "Error en dispositivo de backups NTFS $fecha" | mail -s "Error montando dispositivo USB backup NTFS" [email protected]
		exit;
	fi
echo "MONTAMOS EL PORTATIL"
mount -t cifs -o iocharset=utf8,username=usuario,password=clave  //$PORTATIL/D /mnt/portatil
if [ $? -ne 0 ]
	then
		echo "ERROR MONTANDO EL PORTATIL/D"
	else
		#copiamos
		echo "COPIAMOS"
		/usr/bin/rsync ----alv ----modify-window=1 ----delete ----recursive ----exclude "RECYCLER" ----exclude="$RECYCLE.BIN" ----exclude "System Volume Information"  /mnt/portatil/ /mnt/backup/backupntfs/portatil/
fi
echo "DESMONTAMOS"
umount //$PORTATIL/D

Lo primero que hago es comprobar que el disco USB está disponible, básico para poder hacer el backup :P. A continuación montamos la unidad remota en el directorio que creamos antes, /mnt/portatil. Esto lo hacemos con:

mount -t cifs -o iocharset=utf8,username=usuario,password=clave  //$PORTATIL/D /mnt/portatil

Los parámetros importantes son los del medio, le indicamos que el juego de caracteres sea UTF-8 y los datos con los que autentificarse en el portátil.  En este punto podríamos hacer un ls -l /mnt/portatil y deberíamos ver los archivos de la unidad D: del portátil.

A continuación ejecutamos la sincronización. La primera vez que se lance tardará bastante ya que ahí sí que debe copiar todos los archivos de la unidad origen en el portátil a la del backup. Cuando termine de sincronizar el script desmontará la unidad del portátil y el autofs hará lo propio automáticamente con la del USB. En el rsync añadimos los parámetros adecuados para que excluya algunas carpetas predefinidas de Windows, como la papelera, y muy importante, el parámetro –modify-window=1 para salvar la diferencia que tiene Windows con la fecha de modificación de los archivos, Windows utiliza sólo valores pares, lo que generaría la copia de muchos más archivos de los que realmente han sido modificados.

Vayamos un poco más lejos. Quiero poder recuperar algún archivo borrado, no sea que me equivoque y me cargue algo. Con rsync todo es muy sencillo, le indicamos que los archivos que vaya a eliminar los mueva a otra ruta. Para ello añadimos los parámetros :

----backup ----backup-dir=/mnt/backup/backupntfs/incremental/portatil/$fecha

Con esto conseguimos que en la carpeta /incremental/portatil de la partición NTFS del disco USB se mantenga un histórico por fecha de los archivos eliminados. El comando entero quedaría del siguiente modo:

/usr/bin/rsync ----alv ----modify-window=1 ----delete ----recursive ----backup ----backup-dir=/mnt/backup/backupntfs/incremental/portatil/$fecha ----exclude "RECYCLER" ----exclude="$RECYCLE.BIN" ----exclude "System Volume Information"  /mnt/portatil/ /mnt/backup/backupntfs/portatil/

De este modo tenemos en el disco todo, por un lado la instantánea de la unidad al momento de hacerla y en otro un histórico diario por día, pudiendo recuperar todo lo que queramos.

Sólo nos queda añadir el script al crontab para que se ejecute cuando creamos oportuna, una vez por semana por ejemplo. Yo crearía también un script que limpie las copias de los archivos eliminados cada cierto tiempo, cada mes o cada dos meses, para que no crezca en exceso el espacio ocupada por la “basura“.

Siguiendo este mismo ejemplo podemos ahora hacer copias de las copias para tener varias copias distribuidas no sea que se nos incendie el piso y lo perdamos absolutamente todo 😛 .

Adobe AIR X – Obtener el número de versión de una aplicación durante la ejecución

Parece simple pero, pese a que hay métodos para casi todo en Adobe AIR, no hay manera de saber en tiempo de ejecución el número de versión de una aplicación. ¿Para qué puedes necesitarlo? Pues por ejemplo, como es mi caso, para mostrar la típica pantallita “Acerca de…” donde indiques automáticamente el número de versión. Me diréis, bueno, si, pero puedes tener una variable que actualices con cada cambio de versión. Claro, pero entonces tendría que acordarme de actualizarlo en tres puntos: esta nueva variable, el descriptor de la aplicación y el archivo XML de autoactualización. Si puedo eliminar uno de ellos ¿por qué no hacerlo?

[Bindable]
private var airApplicationVersion:String = "";

private function getVersion():void{
 var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
 var air:Namespace = appXML.namespaceDeclarations()[0];
 this.airApplicationVersion = appXML.air::version;
}

Así de fácil tendremos una variable “bindeable” para mostar el número de versión, por ejemplo:

<mx:Text text="Versión {airApplicationVersion}"/>

Espero que os sea útil 🙂 .

Paradigma Reflection en PHP

Literalmente, el paradigma Reflection es un proceso mediante el cual el software puede observarse a sí mismo, aprender sobre cómo está desarrollado y modificarse automáticamente. ¿Ha quedado claro?. Ya 😛 .

Supongamos que tienes una clase con unos atributos y unos métodos. Supongamos ahora que desde otra clase necesitas averiguar por cualquier razón qué atributos y métodos tiene esa clase primera. Más aún, supongamos que necesitas saber los parámetros de llamada de cada método. ¡No se puede!. Pues te equivocas 😛 , sí que se puede, el paradigma Reflection es el que nos da la solución y viene implementado de serie en PHP con un conjunto de clases que lo dan todo hecho. Una especie de ingeniería inversa.

Estaréis pensando ¿qué utilidad tiene eso? ¿por qué he de necesitarlo?. Bueno, depende de lo que quieras hacer 😛 . Probablemente si sabes que existe y que se puede hacer, algún día recurras a ello. En mi caso necesitaba crear un sistema dinámico para listar un número indeterminado de clases, sus métodos y poder ejecutarlos con todos sus parámetros, es decir, listar todos los parámetros de un método en un formulario, que el usuario pueda rellenarlos y ejecutar ese método de la clase para obtener el resultado. Se podría hacer cargando en una base de datos todas las clases, sus métodos y los atributos de estos, pero habría que hacer un mantenimiento horrible y tenerlo en cuenta, además, cada vez que se modifique algo ya que el sistema está en desarrollo. Reflection nos aporta una solución más elegante.

Nuestro sistema consta de cuatro pasos:

  • Listar las clases disponibles
  • Una vez se selecciona una clase, listar sus métodos públicos.
  • Al escoger un método, listar sus parámetros en un formulario.
  • Ejecutar el método y devolver el resultado al usuario.

1) Lista de clases

Este paso es el sencillo y obvio, en mi caso simplemente listo los archivos que hay en la carpeta donde guardo todas las clases, no hay más truco. Sólo quedaría montar un combo en un formulario para que el usuario escoja la clase a probar.

$clases=array();
$d=dir("./clases");
while (false !== ($entry = $d->read())) {
 //los archivos de clases son del tipo class.nombreClase.php
 $temp=explode(".", $entry);
 $ext=$temp[count($temp)-1];
 if($ext=='php'){
   $clases[]=$temp[count($temp)-2];
 }
}
natcasesort($clases);
$clases=array_merge(array("Escoge Clase"), $clases);

2) Obtención de métodos de una clase

Hacemos ahora uso de la clase ReflectionClass para obtener los métodos públicos de una clase. Hay muchos más métodos dentro de esta clase para consultar muchísimas cosas.

$methods=array();
if(isset($_GET['clase']) && file_exists("class.".$_GET['clase'].".php")){
	require_once("clases/class.".$_GET['clase'].".php");

	$class = new ReflectionClass($_GET['clase']);
	$metodos=$class->getMethods();

	foreach($metodos as $m)
		$methods[]=$m->name;
	$methods=array_merge(array("Escoge Método"), $methods);
}

Como véis, obtenemos otro array con los métodos públicos de la clase seleccionada, listo para montar otro combo con los métodos disponibles.

3) Obtención de los parámetros de un método

Sabiendo ahora el método y la clase que queremos ejecutar sólo tenemos que averiguar sus parámetros de invocación. Parece imposible, pero de nuevo el API Reflection viene a nuestra ayuda.

$parametros=array();
if(isset($_GET['method'])){
 $class = new ReflectionClass($_GET['clase']);
 try{
   $asmth=$class->getMethod($method);
   foreach ($asmth->getParameters() as $i => $param)
     $parametros[]=$param->getName();
 }catch(ReflectionException $e){}
}

Tenemos de nuevo otro array con los parámetros del método. Sólo debemos montar un formulario con tantos campos de texto como parámetros del método para que el usuario pueda probarlo.

4) Ejecutar el método

Voy a complicarlo más. Todas mis clases tienen implementado un singleton, con lo que no se pueden instanciar tal cual 😛 .Podríamos haber hecho algo del tipo:

$class=$_GET['clase']::getInstancia();

Pero PHP no permite utilizar una variable en el nombre de clase al llamar a un método estático, devolvería un error:

Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM

Lo que traducido del hebrero 😐 significaría “Unexpected double-colon“, es decir, en los dos puntos.

Si no tuviésemos el singleton y no fuese necesario hacer la llamada estática que devuelva la instancia, podríamos hacer algo del tipo:

$class=new $_GET['clase'];
$res=$class->metodo($parametros);

Pero seguiríamos teniendo un problema con los parámetros ya que no sabemos cuántos hay que pasar, no podemos montar algo dinámico en una llamada directa a un método.

Lo primero que debemos hacer es llamar al método getInstancia de mi clase para que nos devuelva una instancia a la misma. Para eso recurrimos a la función call_user_func indicándole la clase y el método a ejecutar.

$class=call_user_func(array($_GET['clase'], 'getInstancia'));

Ahora ejecutamos el método correspondiente sobre la clase ya instanciada pasándole los parámetros necesarios en un array, con lo que no importa la cantidad de ellos que haya, irán todos en una lista. Para ello usamos esta vez call_user_func_array.

$params=array();
foreach($_GET as $name=>$param){
 if($name!="clase" && $name!="method" && $name!="submit")
   $params[]=$param;
}
$res=call_user_func_array(array($class, $method), $params);

Eso es todo. Hemos conseguido averiguar los métodos de una clase, sus parámetros y ejecutarlos salvando todos los problemas que nos hemos ido encontrando.

Espero que os sirva de ayuda 🙂 .

Generando online imágenes de información de carga de Ajax

Hoy vamos con algo que personalmente me parece muy útil y que he utilizado en multitud de ocasiones.

¡Quién no ha necesitado en algún momento una imagen de esas típicas de información de actividad que aparecen en una aplicación  web cuando lanzas una acción AJAX!

5-0

La solución: ajaxload.info.

ajaxloadinfo

Desde ajaxload.info podemos generar nuestra imagen de “loading” de muchas formas diferentes personalizando los colores, directamente y online.

Hay gran cantidad de formas distintas que se pueden utilizar, desde las más típicas hasta otras mucho más originales, todas totalmente personalizables según tus necesidades.

combo

Una vez has definido cómo quieres tu imagen puedes visualzar el resultado hasta que te satisfaga completamente y descargarla. Este sería el resultado.8-1

Otra aplicación similar es Preloaders.net, aunque yo prefiero la anterior.

Webservices: Tratando con cabeceras SOAP en PHP (2)

Tras el artículo anterior dónde explicaba cómo leer las cabeceras en una respuesta SOAP, he descubierto cómo hacer funcionar el método estándar, seguro que a más de uno le viene bien saberlo.

El problema era que con __soapCall no había manera de que me funcionase la llamada, independientemente de recibir las cabeceras de la respuesta. Ahora sé porqué. El webservice al que estaba llamando está hecho en .NET y parece ser que hay que llamarlo de distinta forma que si se hace invocando al método directamente 😐 .

Si lo llamamos directamente hacemos:

$result = $client->TuMetodo($parametros);

Si lo llamamos con __soapCall haremos:

$result = $client->__soapCall("TuMetodo", array("parameters"=>$parametros), NULL, $reqheaders, $resheaders);

¿Veis la diferencia?

De la segunda forma hay que pasar los parámetros de entrada del método cómo un sólo parámetro “parameters“, es decir, el mismo array que teníamos con la primera manera pero asignándolo a “parameters“.

Eso es todo. Ahora ya funciona y podemos recoger automáticamente los headers de la respuesta SOAP.

Sigo sin saber porqué de esta manera se tiene acceso a las cabeceras y con la invocación directa (la forma recomendada) no. Por el momento voy a seguir utilizando el desarrollo que hice en el primer artículo, la invocación directa me parece más elegante y ya que había conseguido recuperar las cabeceras, ¿por qué cambiarlo ahora? 😛 .

Webservices: Tratando con cabeceras SOAP en PHP

Llevo ya un tiempo bastante liado con webservices a los que debo llamar con PHP y hoy me ha tocado lidiar con cabeceras SOAP. La verdad es que es un mundo bastante oscuro y me he encontrado con muchas trabas. Os contaré cuales y cómo las he solucionado, pero veamos primero algo de teoría.

Los servicios web se han convertido en el principal modo de intercambio de  información entre aplicaciones independientemente de plataformas, sistemas operativos y lenguajes de programación. SOAP es uno de los protocolos sobre los que se realiza el intercambio de los datos y está basado en XML, de manera que la parte cliente interroga al servidor con un código XML en el formato adecuado y recibe la respuesta en otro XML. Para entender de qué estamos hablando veamos la estructura de una petición SOAP y su respuesta.

Llamada (request):

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="com.xplota.ws">
    <SOAP-ENV:Header>
        <ns1:entity>
            <Code>1</Code>
            <Desc></Desc>
        </ns1:entity>
        <ns1:language>
            <Code>1</Code>
            <Desc></Desc>
        </ns1:language>
        <ns1:userId>
            <Code>1</Code>
            <Desc></Desc>
        </ns1:userId>
    </SOAP-ENV:Header>
    <SOAP-ENV:Body>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Respuesta (response):

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Header>
        <Status xmlns="com.xplota.ws">
            <Code>0</Code>
            <Desc>Ok</Desc>
        </Status>
    </soap:Header>
    <soap:Body>

    </soap:Body>
</soap:Envelope>

Como se puede ver en los listados anteriores, tanto el request como el response constan de dos nodos XML, header y body. El que se utiliza habitualmente es el body (que yo he dejado en blanco pues no nos interesa en este momento) y es el que contendría tanto los parámetros que se envían al webservice en el request como los que devuelve en el response.

Enviando headers soap

En el caso que nos ocupa debía enviar determinados parámetros en el header y leer de allí los potenciales códigos de error si los hubiese habido. El envío, pese a ser una estructura en vez de un parámetro simple, fue sencillo, se define una clase con los parámetros adecuados y se le envía directamente. El motor de SOAP de PHP se encarga de la traducción. Veamos un caso práctico.

//definimos la clase para las cabeceras
class wsHeader
{
    public $Code = 0;
    public $Desc = '';

    public function __construct($code, $desc){
        $this->Code=$code;
        $this->Desc=$desc;
    }
}

//instanciamos el cliente soap
$par=array();
$client = new SoapClient("http://midominio.com/ws?wsdl", $par);

//añadimos las cabeceras a las peticiones
$headers=array();
$headers[] = new SoapHeader("com.xplota.ws", 'entity', new wsHeader(1, ''));
$headers[] = new SoapHeader("com.xplota.ws", 'language', new wsHeader(1, ''));
$headers[] = new SoapHeader("com.xplota.ws", 'userId', new wsHeader(1, ''));
$client->__setSoapHeaders($headers);

//lanzamos la llamada al metodo del ws
$result = $client->TuMetodo($parametros);

Como veis es bastante sencillo de entender. Al añadir una cabecera hay que indicarle el namespace al que pertenece para que el motor SOAP sepa como tratarla, se le da un nombre y el objeto que la contiene.

Con esto hemos solucionado la parte del envío de nuestras cabeceras SOAP y tendremos un request como indicábamos en el primer XML.

Recibiendo headers SOAP

Ahora resulta que el método de nuestro webservice nos responde con otras cabeceras que debemos saber interpretar según el XML de response del segundo listado. Pues tenemos un problema y muy gordo. No hay forma de obtener estas cabeceras, el motor SOAP de PHP sólo devuelve el body, nunca los headers.

Según el manual de PHP el método __soapCall del cliente SOAP permite definir un array en el que se devolverán estas cabeceras, pero no fui capaz de hacer funcionar la invocación de un método del webservice con esta sintaxis mientras que invocándolos directamente en el cliente (cómo la documentación indica que se puede hacer) sí que me funcionaba perfectamente. Es decir, la teoría dice que con el primer método puedo recibir las cabeceras pero no me funcionó mientras que el segundo me funcionaba pero no me devuelve las cabeceras ni hay ningún método para recuperarlas.

Tras pelearme mucho con las funciones SOAP e investigar todavía más no llegué a ninguna conclusión, es como si no le hubiese pasado a nadie, no encontré absolutamente nada útil. Sólo me quedaba una solución, hacer mi propia clase SOAP a partir de la original y procesar el XML del response a mano para obtener los datos que necesitaba. Dicho y hecho. Veamos la solución.

Primero creo mi propia clase de SOAP y compruebo si voy a poder hacer lo que quiero.

class XSoapClient extends SoapClient{
    public function __construct($wsdl, $options){
        parent::__construct($wsdl, $options);
    }

    public function __doRequest($request, $location, $action, $version){
        $response=parent::__doRequest($request, $location, $action, $version);
        return $response;
    }
}
$client = new XSoapClient("http://midominio.com/ws?wsdl", $par);

Parece que voy a tener suerte, si pruebo este nuevo cliente SOAP funciona perfectamente, pero además si compruebo el contenido de $response veo que contiene íntegramente el XML de la respuesta del webservice. Cómo veis lo único que cambia al instanciarlo es que le paso el nombre de la nueva clase. Buen comienzo, si juego bien mis cartas podré sacar las cabeceras en el método __doRequest 🙂 .

Tratemos pues ese XML para obtener lo que buscamos. Gracias a las funciones DOM y XPATH de PHP será muy sencillo. Este es el resultado final de mi cliente SOAP con recuperación de cabeceras:

class XSoapClient extends SoapClient
{
    private $responseHeaders = array();

    public function __construct($wsdl, $options){
        parent::__construct($wsdl, $options);
    }

    public function __doRequest($request, $location, $action, $version){
        $response=parent::__doRequest($request, $location, $action, $version);

        $dom = new DOMDocument;
        $dom->loadXML($response, LIBXML_NOWARNING);
        $path = new DOMXPath($dom);
        $path->registerNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
        $xml = $path->query('//soap:Header/*');
        $this->responseHeaders=$this->headers2array($xml);

        return $response;
    }

    public function getResponseHeaders(){
        return $this->responseHeaders;
    }

    private function headers2array($response){
        $headers=array();
        foreach ($response as $node) {
            if($node->hasChildNodes()){
                $headers[$node->nodeName]=$this->headers2array($node->childNodes);
            }else{
                $headers[$node->nodeName]=$node->nodeValue;
            }
        }
        return $headers;
    }
}
$client = new XSoapClient("http://midominio.com/ws?wsdl", $par);
$result = $client->TuMetodo($parametros);
$soapheaders=$client->getResponseHeaders();

Problema solucionado y de manera bastante elegante. Si alguien sabe cómo conseguir las cabeceras sin montar todo este lio que me lo cuente por favor.

Patrón singleton con herencia en PHP

Pensaba que sería más sencillo. Me he tirado varios días hasta conseguir que todo funcione correctamente.

La idea es implementar el siguiente diagrama diseñado con ArgoUML:

diagramadeclase11

Es decir, una clase base de la que heredan otras.

El escenario consiste en distintos webservices a los que tengo que llamar dentro de la aplicación. Lo que he hecho ha sido mapear cada webservice con una clase que contenga los mismos métodos que el webservice, de manera que lo que haya que hacer contra los webservice se haga contra clases del mismo nombre y con los mismos métodos devolviendo directamente la información devuelta.

Desde el principio planteé que las clases de los webservices extendiesen de una clase base que tuviese la funcionalidad común de todos, es decir, las llamadas a los webservices y el control de errores de los mismos, así evitaría duplicar esta funcionalidad en cada clase.

Posteriormente decidí que cada clase debía implementar el patrón singleton para asegurar que sólo hubiese una instancia de la misma clase en cada momento y evitar así que el código se viciase instanciando una nueva cada vez que se necesita.

La solución global era, por tanto, aplicar el singleton a la clase padre y que todas las que extendiesen de ésa tuviesen automáticamente el singleton y no tener que implementarlo en cada una de ellas. Idealmente es bonito, la práctica fue bastante peor.

La primera prueba fue declarar el método getInstancia encargado de asegurar el singleton de este modo:

private static $instancia = null;

public static function getInstancia($asurl)
{
    $returnValue = null;
    if (!isset(self::$instancia)) {
            $class = _CLASS_;
            self::$instancia = new $class($asurl);
    }
    $returnValue=self::$instancia;
    return $returnValue;
}

Se suponía que haciéndolo así se crearía, en caso de no existir, una nueva instancia del objeto que fue llamado, en nuestro caso, por ejemplo, Clase1. Sin embargo al comenzar las pruebas nos dimos cuenta que _CLASS_ no devuelve el nombre de la clase a la que se llama sino el de la clase sobre la que se ejecuta, en este caso Base, con lo cual no nos servía para nuestro propósito.

A continuación encontramos una función de PHP que nos devuelve lo que nosotros necesitamos, es decir, el nombre de la clase llamada: get_called_class. Lástima que esté disponible a partir de PHP 5.3 (en beta). Aún así encontramos una implementación alternativa de esta función válida para versiones anteriores de PHP.

if (!function_exists('get_called_class')){
    function get_called_class(){
        $bt = debug_backtrace();
        $lines = file($bt[1]['file']);
        preg_match('/([a-zA-Z0-9_]+)::'.$bt[1]['function'].'/', $lines[$bt[1]['line']-1], $matches);
        return $matches[1];
    }
}

Con esto ya podíamos crear la instancia de la clase que necesitábamos desde el singleton, pero nos aguardaba aún una sorpresa. No podíamos instanciar más de una clase (distinta) de las que heredan de un mismo padre, a pesar de ser distintos objetos asume que al heredar del mismo tiene que utilizar la misma instancia del singleton y no te deja, ya que  instancia (la variable del código anterior) ya está generada y es de otro tipo.

Pongamos un ejemplo para verlo mejor. Creamos una instancia de Clase1, el singleton nos devolverá una nueva instancia ya que no existe. Ahora creamos otra de Clase2. El objeto devuelto es de tipo Clase1. Aunque nosotros queremos crear objetos distintos (Clase1 y Clase2), la instancia de Base es compartida por ambos. Como ya he instanciado Clase1, instancia es de tipo Clase1, con lo que al instanciar Clase2 te devuelve la instancia ya creada, Clase1. Podríamos comprobar que instancia (la variable) esté creada y sea del mismo tipo que la nueva que se quiere instanciar, pero al crear la nueva (Clase2) destruiríamos la antigua (Clase1) con lo que perderíamos este objeto, y eso no es lo que se busca.

¿La solución? Parecerá algo cutre, pero es esta:

Base

class osusnet_com_Base
{
    private $asurl = '';
    private static $instancia = array();
    private $iVersion = null;
    private $sAsConsumer = '';
    private $debug = 0;
    protected function __construct($asurl)
    {
        if($asurl==""){
            return false;
        }else{
            $this->asurl=$asurl;
            $this->iVersion=1;
            $this->sAsConsumer="kk";
            $this->debug=0;
        }
    }

    public static function getInstancia($asurl)
    {
        $returnValue = null;
        $class = get_called_class();
        if (!isset(self::$instancia[$class])) {
            self::$instancia[$class] = new $class($asurl);
            $returnValue=self::$instancia[$class];
        }
        return $returnValue;
    }
}

Clase1

class osusnet_com_Clase1 extends osusnet_com_Base
{
    public function GetUserContext($cookie)
    {
    }
    public function CheckStateUser()
    {
    }
    public function LogIn()
    {
    }
}

Es decir, instancia (la variable) pasa a ser un array donde guardaremos un elemento para cada instancia de cada una de las clases que lo heredan. Si buscamos un tipo ya creado nos devolverá esa instancia, en caso contrario la crea y la deja disponible para posteriores instancias. La base es única pero se crea un objeto de cada clase disponible.

Como cualquier patrón singleton, para usarlo debemos hacer:

$instancia = osusnet_com_Clase1::getInstancia($url);

Recuerda que el constructor está protegido con lo cual cualquier intento de querer instanciar una clase de estas directamente dará error, prueba a hacer:

$instancia=new osusnet_com_Clase1();

No se cual será el comportamiento en otros lenguajes, mis años de Java quedan ya algo atrás, ni siquiera se si la teoría de POO es el comportamiento aquí descrito o es una mala implementación de PHP, no soy un experto en la teoria de la programación orientada a objetos. A mi la lógica me dice que si instancias una clase de un tipo, te debería instanciar todo,  sin compartir la clase base, aunque igual también tiene sentido que si todas extienden de Base y ésta tiene el singleton donde instancia y getInstancia son estáticos, sea común a todas. Cuando más lo pienso más me lío.

Al menos encontré una solución.