Archivo de la categoría: Técnico

Primeros pasos con Flash Media Server (2), autentificando usuarios del chat

Hace unos días veíamos como crear un sencillo chat con FMS.

Hoy, a petición popular, vamos a añadir la primera mejora a nuestra aplicación. Por defecto habíamos diseñado un chat público, cualquiera podía conectarse. Sin embargo, puede que deseemos que sólo nuestros usuarios registrados puedan utilizarlo. Necesitamos entonces que nuestros clientes se puedan autentificar contra nuestra base de datos pero no podemos dejar esa responsabilidad en manos de la aplicación cliente, cualquiera podría hacer otra que se conecte a nuestro servidor sin requerir autentificación.

FMS no puede conectarse con ningún tipo de bases de datos, no es su función, aunque cada vez hay más gente solicitando esta funcionalidad y se espera que en la próxima versión (FMS4) haya alguna novedad al respecto. Sea como fuera, de momento no hay métodos nativos para acceder a bases de datos así que sólo se puede recurrir a lo que un día fue Flash Remoting y hoy son los servicios RPC bajo AMF, el método estándar de acceso a datos desde aplicaciones Flash (con el permiso de los webservices SOAP). Con el tiempo fueron apareciendo versiones en los distintos lenguajes para utilizar el protocolo AMF fuera del amparo de Macromedia (ahora Adobe), pero sin duda uno de los pioneros fue amfphp, el que nos permite comunicar aplicaciones Flash con el servidor a través de PHP.

AMF es un formato para serialización de datos e invocación de métodos remotos y es independiente al lenguaje con el que trabaja el servidor, simplemente debe cumplir las especificaciones. FMS, como no podía ser de otra forma, puede comunicarse con nuestro servidor gracias a este protocolo AMF y esto es precisamente lo que vamos a utilizar para autentificar a nuestros usuarios:

dibujo1.jpg

El proceso es mucho más sencillo de lo que parece.

Comenzaremos por descargar amfphp desde la web oficial.  Lo descomprimes y subes la carpeta “amfphp” a tu servidor en la ruta que desees. Ya tienes lo que es el cerebro del sistema listo, para comprobarlo prueba la URL:

http://tuservidor/amfphp/gateway.php

Si todo va bien, prueba ahora el navegador de servicios, herramienta imprescindible para probar tus métodos remotos:

http://tuservidor/amfphp/browser/

Si no has visto ningún error es que todo va bien. Veamos ahora como crear tu servicio de autentificación.

Dentro de la carpeta amfphp que has subido a tu servidor encontrarás la carpeta services. Crea dentro de ella un nuevo directorio con el nombre de tu aplicación, por ejemplo chat. Creamos ahora dentro de esa carpeta las clases y métodos que se necesiten para crear el servicio de autentificación. Por ejemplo, en mi caso creo Chat.php:

<?
class Chat{
    var $conn;    function Chat(){
      $this->conn=mysql_connect("host", "usuario", "clave");
      mysql_select_db('db', $this->conn);
    }
function login($nick, $pass){
        $rs=mysql_query("select * from usuarios where nick='$nick' AND pass='$pass'");
        if(mysql_num_rows($rs)>0)
          return 1;
        else
          return 0;
    }
}
?>

Sencillo ¿no?. Nuestra clase tiene un método login que nos dice si la pareja nick/clave existe en nuestra base de datos. Obviamente esto es sólo un ejemplo, en tu aplicación haz la clase con todos los métodos y la lógica que necesites.

Si ahora vas de nuevo al Service Browser verás que te aparece la nueva aplicación chat con la clase que has creado. Si accedes a ella puedes probar el método login pasándole los parámetros necesarios. Más sencillo imposible.

Ya tenemos la lógica de autentificación lista. ¿Cómo le decimos ahora a FMS que la utilice? Modificamos el archivo main.asc que habíamos hecho en el capítulo anterior para que antes de aceptar la conexión de un usuario la autentifique.

application.onConnect = function(client, nick, clave, sala){
    var valido=1;
    for( i in this.userList){
        if(String(this.userList[i].nick).toLowerCase()==String(nick).toLowerCase()){
            valido=0;
            break;
        }
    }
    if(valido==1){
        var myServer = NetServices.createGatewayConnection("http://tudominio/amfphp/gateway.php");
        var servicio = myServer.getService("chat.Chat", this);
        var callserver=servicio.login(email, clave);

        this.login_Result=function(result){
            //trace("RESULTADO LOGIN"+result);
            if(result>0){
                client.uid = this.uid;
                client.nick = nick;
                client.sala = sala;
                this.acceptConnection(client);
                this.userList[client.uid] = client;
                this.users_so.setProperty(client.uid, client);
                var msg = "*** Entra " + client.nick + " ***";
                this.chat_so.send("onMsgSend", msg);
                client.call("onUserid", null, client.uid, client.nick);
                this.uid++;
            }else{
                var err = new Object();
                err.message = "Email o contraseña incorrectos";
                application.rejectConnection(client, err);
            }
        }
    }else{
        var err = new Object();
        err.message = "Ya hay un usuario con ese nick";
        trace(err.message);
        application.rejectConnection(client, err);
    }
}

Es tan simple como añadir la comprobación del usuario y clave de nuestro cliente. Indicamos la url de nuestro gateway (amfphp) y llamamos al método login con los datos correspondientes. Lo más importante es la forma en que obtenemos los datos que devuelve el metodo. La llamada es asíncrona, no se paraliza la actividad esperando por un resultado sino que se continua la actividad esperando a recibir la respuesta. Para eso se supone que debe haber definida una función nombremetodoremoto_Result, en nuestro caso login_Result ya que login es el método remoto al que llamamos. Cuando se llame a esta función sabremos si el usuario existe en nuestra base de datos, con lo que aceptamos su conexión al chat, o no existe y la rechazamos.

Como habéis visto, es muy sencillo llamar a métodos remotos desde FMS de manera que aumenten las posibilidades de tu aplicación ya que puedes hacer uso de bases de datos y de sistemas avanzados y siempre de manera muy simple.

Yo te he dado las pistas de cómo hacerlo, el resto queda a tu imaginación.

Y seguimos con la falta de personal técnico cualificado

Tenía pendiente hacer un comentario desde la semana pasada.

¿Por qué se empeña Enrique Dans en contradecir a cientos de programadores? ¿En serio es creíble el argumento de que no hay personal adecuadamente cualificado en España? Suelo leer a Enrique y creo que es un tío normalmente serio y coherente en sus argumentaciones (estés o no de acuerdo con ellas), pero es que con ésta no puedo, no la veo reflejada por ningún lado en el mundo real.

Mi experiencia me dice lo contrario. Claro que ya no se reciben cientos de curriculums para una oferta de trabajo, pero es que aquello era lo anormal. ¿Acaso recibes docenas y docenas de currilums para un puesto importante de departamentos tradicionales (comercial, marketing, rrhh…)? ¿Por qué esperas entonces recibirlos para un puesto de programador? Un programador es, desde la base de su formación, personal altamente cualificado y han estado puteados durante muchos años. Resulta que ahora cuando comienza a verse un poco de dignidad en el sector, no hay gente, curioso ¿no?, es decir, ahora que queremos jornadas normales y salarios decentes, faltan programadores. Recuerda que, ante todo, buscas a la/s persona/s que diseñarán y construirán tu producto…

Lo he comentado ya en otras ocasiones pero creo que mis razones siguen siendo completamente válidas:

Sobran más palabras ¿no?.

Generador de códigos de barras en AS3

Hoy vamos a ver un ejemplo sobre cómo generar códigos de barras al vuelo utilizando ActionScript3. Un código de barras es la representación mediante líneas paralelas de distinto grosor de un código numérico o alfanumérico. Esta representación lineal los hace de fácil interpretación para un lector láser. No creo que se necesiten más explicaciones, todo el mundo sabe lo que es un código de barras.

Los códigos de barras lineales pueden ser de distintos tipos, en el ejemplo de hoy generaremos EAN y C128. EAN es el más utilizado a nivel mundial y el utilizado en España, así que será el que nos ocupe hoy. Mi trabajo está basado en la clase para generar códigos C128 de Friedrich Dimmel (lo siento, no localizo el link original). La clase que genera el código EAN no es ni más ni menos que la reescritura en AS3 de la que trae phpBarcode  y adaptada para generar la secuencia de líneas que se dibujan bajo la lógica de Friedrich Dimmel en vez de generar una imagen con GD como hace la clase original. Como casi siempre, este pequeño desarrollo lo hice hace un par de años para un sistema de control de stocks, no me critiquéis mucho el código :P.
swfobject.embedSWF(“/wp-content/uploads/2008/10/codigosdebarras1.swf”, “codigosdebarras”, “470”, “350”, “9.0.000”);

Flash del generador

Un código EAN son 13 dígitos, 12 de información (código de país, empresa y producto) y un dígito de control que ayuda a verificar que la decodificación posterior es correcta.
Para crear la imagen de nuestro código simplemente debemos llamar a la clase generadora  con los parámetros:

  • Código: la cadena que queremos convertir a código de barras.
  • Alto de la imagen generada.
  • Tipo de código (EAN o C128).
  • Mostrar el texto del código (opcional).
  • Color de fondo (opcional).

Por ejemplo:

BarcodeGenerator.generateBarcode("3456789012345", 50, "EAN");

Como hemos visto, el último dígito es de control, con lo que con introducir los 12 dígitos con valor será suficiente, la clase generará el treceavo automáticamente. No voy a explicar detalladamente como generar los códigos EAN, hay suficiente literatura acerca del algoritmo googleando un poco y para ver el resultado lo mejor es que examinéis el código fuente que os dejo más abajo. Creo que lo más curioso e importante es ver cómo generamos la imagen una vez tenemos generado el código de líneas.

public static function generateBarcode(code:String="0", height:Number=60, tipo:String="EAN", showText:Boolean=true, backgroundColor:uint=0xffffff):Canvas {
    var can:Canvas = new Canvas();
    can.horizontalScrollPolicy = "off";
    can.verticalScrollPolicy = "off";
    can.height = height;
    can.setStyle("backgroundColor", backgroundColor);
    var sBarcode:Shape;
    var rect:Rectangle;
    var bd:BitmapData;
    var img:Image;
    var bitmap:Bitmap;
    if(tipo=="EAN"){
      var barcode:BarcodeEAN = new BarcodeEAN();
      barcode.code = code;
      barcode.barHeight = height;
      if(showText) {
        barcode.barHeight -= 15;
      }
      var k:Object=barcode.barcode_encode_ean(code);
      if(!k.bars) return new Canvas();
      var bars:String=k.bars;
      var text:String=k.text;
      sBarcode = barcode.generateBarcode();
      rect = new Rectangle(0, 0, (sBarcode.width / 2) + 1, barcode.barHeight);
      bd = getBitmapData(sBarcode, rect);
      img = new Image();
      bitmap = new Bitmap(bd);
      img.source = bitmap;
      can.addChild(img);
      var chars:Array=text.split(" ");
      var n:String;
      var v:String;
      var total_y:Number=40;
      for(n in chars){
        v=chars[n];
        if (StringUtil.trim(v)){
          var inf:Array=v.split(":");
          var fontsize:Number=inf[1]/1.8;
          var fontheight:Number=30;
          var label:Label = new Label();
          label.text = inf[2];
          label.x = Number(inf[0])-2;
          label.y = fontheight;
          label.setStyle("fontSize", 9);
          label.setStyle("paddingBottom", 0);
          label.setStyle("paddingLeft", 0);
          label.setStyle("paddingRight", 0);
          label.setStyle("paddingTop", 0);
          can.addChild(label);
        }
      }
    }
    return can;
}

En realidad es tan sencillo como crear una imagen a partir de lo que nos devuelve la clase generadora EAN (un objeto de tipo Shape). Finalmente si queremos imprimir el código en la propia imagen tendremos que poner cada letra en el sitio que le toca, no van en cualquier lado.

¿Qué tal si hacemos el proceso contrario con un lector casero?

Os dejo el código fuente del proyecto.

Primeros pasos con Flash Media Server, creando un chat

Hoy vamos a ver cómo crear una sencilla aplicación de chat con Flex. No debe tomarse este ejemplo como una aplicación completa sino simplemente como un método para comprender cómo funcionan los distintos elementos que intervienen. Tampoco debe compararse este proyecto con el cliente IRC que os presenté hace unos meses, aunque ámbos son aplicaciones de chat,  son sistemas distintos, como veréis más adelante.

Chat en Flex con FMS

Flash Media Server (FMS, antiguamente conocido Flash Communication Server) es el software de Adobe que permite crear aplicaciones multiusuario de una manera sencilla y bajo tecnología Flash. FMS permite no sólo intercambiar y sincronizar mensajes de texto entre los clientes sino tambien audio y vídeo. Si flv es el sistema de vídeo por excelencia en Internet, FMS es la tecnología para crear sistemas multimedia multiusuario. La principal pega de FMS es que es de pago, y no precisamente barato, sin embargo existe una licencia “developer” que te permite utilizar el 100% de las posibilidades del servidor, la única limitación es la cantidad de usuarios concurrentes que puedes tener, diez en este caso. Existe una alternativa Open Source a FMS, Red5, pero no lo he utilizado nunca así que no puedo opinar. La teoría con Red5 es exactamente la misma que con FMS, sólo cambia el medio (FMS se programa con ActionScript y Red5 con Java). Como ejemplo de aplicaciones que utilizan FMS está Yahoo Live, la aplicación de grabación directa desde tu webcam de Youtube o todos, absolutamente todos los videochats que puedes encontrar en Internet.

Como lo más probable es que no tengas una licencia de FMS, trabajaremos con la versión developer que hemos indicado ya que nos permitirá poner en práctica todos los conceptos necesarios.  Lo primero que debes hacer es, por tanto, descargar e instalar FMS, hay versiones para servidores Linux y Windows, escoge la que mejor te venga. La instalación no tiene ningún secreto así que lo dejaré en vuestras manos. En mi caso, como siempre, un CentOS5.

Los proyectos creados sobre FMS son sistemas cliente/servidor y necesitan dos aplicaciones para poder funcionar:

  1. Aplicación cliente, sería la parte Flash pura y dura, la que utilizarán los usuarios.
  2. Aplicación servidor, sería lo que corre bajo FMS y a lo que se conecta la aplicación cliente.

Digamos que FMS es la varita mágina del conjunto ya que hace completamente transparente al desarrollador la conexión entre usuarios y la inyección de mensajes entre ellos. Para entenderlo pensemos en un chat donde los usuarios se conectan al servidor y cada vez que uno de ellos escribe un mensaje éste se envía automáticamente a los demás usuarios. Imagina, además, que no sólo hablamos de texto sino que cuando un usuario enciende su webcam los demás usuarios pueden verla. Todo esto lo hace FMS por ti, sólo debes preocuparte de construir tu aplicación.

Programando una aplicación FMS

Empezaremos la parte práctica creando el código necesario en el servidor. Por defecto las aplicaciones FMS de instalan bajo /opt/adobe/fms/applications creando un nuevo directorio con el nombre de tu aplicación, en nuestro caso la llamaremos textchat.

Las aplicaciones se cargan y se mantienen en memoria una vez algún usuario accede por primera vez a la misma y, cuando pasado un tiempo prudencial no hay usuarios, se descarga para liberar recursos esperando a que otro usuario vuelva a acceder. Cada vez que se hacen cambios a la parte servidor de la aplicación hay que reiniciarla, pero no es necesario reiniciar el servidor FMS para reiniciar una aplicación, basta con descargar y volver a cargar esa aplicación desde la consola de administración como veremos más adelante. Desde esta misma consola podremos ver también la salida de los comandos trace que ocurran en el servidor y los objetos creados y su contenido, ayuda imprescindible mientras estás desarrollándola. Veremos también como activar la salida de estas opciones puesto que por defecto (por seguridad) vienen deshabilitadas.

Por defecto todas las aplicaciones deben tener un archivo main.asc con el código necesario.  Crea tu main.asc de esta manera:

load("netservices.asc");

application.onAppStart = function(){
  trace("inicia text chat");
  this.users_so = SharedObject.get("users_so", false);
  this.chat_so = SharedObject.get("chat_so", false);
  this.userList = {};
  this.uid = 1;
}

application.onConnect = function(client, nick, clave, sala){
  trace(nick+" "+clave+" "+sala);
  var valido=1;
  for( i in this.userList){
    if(String(this.userList[i].nick).toLowerCase()==String(nick).toLowerCase()){
      valido=0;
      break;
    }
  }
  if(valido==1){
    client.uid = this.uid;
    client.nick = nick;
    client.sala = sala;
    this.acceptConnection(client);
    this.userList[client.uid] = client;
    this.users_so.setProperty(client.uid, client);
    var msg = "<font color='#666666'><b>*** Entra " + client.nick + " ***</b></font><br>";
    this.chat_so.send("onMsgSend", msg);
    client.call("onUserid", null, client.uid, client.nick);
    this.uid++;
  }else{
    var err = new Object();
    err.message = "Ya hay un usuario con ese nick";
    trace(err.message);
    application.rejectConnection(client, err);
  }
}

application.onDisconnect = function(oldClient){
  if(oldClient.uid!=undefined){
    trace("Descontecta cliente. UID:  " +oldClient.uid);
    var msg = "<font color='#666666'><b>*** " + oldClient.nick + " sale ***</b></font><br>";
    this.chat_so.send("onMsgSend", msg);
    for( i in application.userList){
      if(Number(application.userList[i].uid)==Number(oldClient.uid)){
        application.userList[i]=undefined;
      }
      k++;
    }
    application.disconnect(oldClient);
    application.users_so.setProperty(oldClient.uid, null);
  }
}

El funcionamiento es muy sencillo. Nuestra aplicación maneja tres eventos:

  1. onAppStart: Se dispara cuando la aplicación se carga en memoria por primera vez.
  2. onConnect: Se lanza cuando un usuario se conecta a la aplicación.
  3. onDisconnect:Cuando un usuario se desconecta.

Veamos la teoría de un chat y comprenderemos rápidamente como funciona FMS en su lado servidor. En una aplicación chat típica, el servidor debe manejar una lista de los usuarios conectados actualmente (los nicks) y debe poder ser capaz de enviar mensajes a todos los usuarios. Por ejemplo, cuando un nuevo usuario se conecta y sin que éste escriba nada, el servidor tiene que poder enviar un mensaje tipo “Nick se ha conectado al chat” a todos los demás usuarios.  Para solucionar todo esto FMS tiene lo que se conocen como objetos compartidos (SharedObjects), objetos a los que todos los clientes pueden acceder.

Si analizas ahora el código de nuestro main.asc verás que el evento onAppStart crea las variables que se utilizarán en la aplicación:

  • userList: array que contendrá la lista de usuarios conectados al sistema.
  • uid:  identificador del último usuario conectado para poder localizar de manera inequívoca a un cliente.
  • users_so: objeto compartido de la lista de usuarios
  • chat_so: objeto compartido del chat propiamente dicho, a él se envíen los mensajes que escriben los usuarios.

Creo que sobran más explicaciones ya que es tan sencillo que se entiende sólo.

Veamos ahora la teoría de qué ocurre cuando se conecta un usuario, evento onConnect. Cada vez que un cliente accede a nuestro chat debemos comprobar, antes de nada, siya hay otro usuario con el mismo nick, en cuyo caso rechazamos su conexión. Si todo va bien y su nick es único debemos guardar al usuario en la lista de usuarios y avisar a todos los clientes ya conectados que hay un nuevo usuario en el chat.

Compara ahora la teoría con el código del evento. El parámetro cliente es automático y representa al cliente que se conecta. Es un objeto con distintos parámetros predefinidos. Los demás parámetros del evento se los pasaremos nosotros desde nuestro cliente flash. Para ver si un nick ya existe simplemente lo comparamos contra todos los demás del array userList que ya hemos visto que contiene a todos los usuarios conectados. El evento onConnect puede aceptar un cliente (acceptConnection) o rechazarlo (rejectConnection).

Si el usuario ha sido admitido, le damos el siguiente UID y tendremos entonces que avisar a los demás usuarios de que tenemos un usuario nuevo. Si recordamos las variables que teníamos, había un objeto compartido al que tienen acceso todos los clientes, users_so, solo tendremos que añadir nuestro nuevo cliente a este objeto:

this.users_so.setProperty(client.uid, client);

Cuando veamos la aplicación cliente veremos como detectamos los cambios en ese objeto, pero es sumamente sencillo.

Espera!. Nos falta enviar el mensaje de “Nick se ha conectado al chat”. Tan sencillo como:

this.chat_so.send("onMsgSend", msg);

Magia!. Como chat_so era un objeto compartido, todos los clientes y el servidor tienen acceso al mismo y lo que uno escriba lo recibirán automáticamente todos los demás. Esta es la verdadera magia de FMS, no tienes que preocuparte de cómo enviar los mensajes a los usuarios uno a uno, se sincronizan sólos.

Como último detalle, cuando aceptamos la conexión de un cliente llamamos remotamente al método onUserid de la aplicación cliente. Si! has leído bien!, FMS llama a una función de tu SWF!. Más adelante veremos para qué, simplemente recuerda que cuando te conectas al chat el servidor llama a un método de la aplicación pasándole su propio UID.

Finalmente veamos el último evento del servidor, bastante obvio si has seguido lo explicado anteriormente y lo has entendido, la desconexión de un usuario.

Sin pensar mucho más entenderemos que habrá que eliminar al cliente de userList y de users_so. Pues eso mismo, no hay mucha más ciencia.

Ya tenemos la parte del servidor terminada. Sencillo ¿no?. Es posible que aún no entiendas algunas cosas. Tranquilo, cuando veamos el cliente lo entenderás todo y verás que realmente es muy sencillo crear aplicaciones de este tipo con FMS.

La consola de administración

Es una aplicación Flash desde la que podemos conectarnos como administradores al servidor FMS y realizar operaciones de mantenimiento, entre ellas cargar y descargar instancias de aplicación, ver el log del sistema (los trace), examinar objetos (en modo debug)…

Cuando instalaste FMS te pidió un usuario y una clave además de un puerto donde instalar esta consola de administración. No tienes más que conectarte a ella y comenzarás a ver estos datos.

Consola de administración de FMS

Hay, sin embargo, algunas particularidades de administración que conviene tener en cuenta. Para habilitar las opciones de debug de objetos habrá que modificar el fichero conf/_defaultRoot_/_defaultVHost_/Application.xml cambiando:

<AllowDebugDefault>false</AllowDebugDefault>

a true. Reinicias el servidor para que el cambio se active y verás algo como esto que te servirá para depurar tu aplicación.

Consola de administración de FMS

En la barra de opciones de aplicación (Live log, clients…) tienes dos pequeños botones que te permitirán recargar una instancia de la aplicación o descargarla por completo, las opciones que comentábamos antes que te ayudarán durante el desarrollo para no tener que reiniciar todo el servidor.

La aplicación cliente

Y llegamos al último punto. La aplicación, el chat, lo que ve el usuario.

Ya os he puesto una captura del resultado final. Veremos ahora los entresijos. Abajo de todo tenéis el código fuente de todo el proyecto de demostración.

Me meteré directamente al meollo de conectar con FMS y enviar y recibir datos, la interfaz de usuario y la pantalla inicial de login la dejo para vosotros. Como veréis hay una clase Chat.as que centraliza casi todo el proceso de comunicación con el servidor.

chat = new Chat();
chat.addEventListener(UserIDEvent.USER_ID, onUID);
chat.addEventListener(NCEvent.STATUS, onStatus);
chat.addEventListener(NCEvent.NONICK, onStatus);

salaSeleccionada=sala.selectedItem.toString();
chat.init("rtmp://"+host+"/textchat/"+salaSeleccionada, StringUtil.trim(nick.text), StringUtil.trim(clave.text), salaSeleccionada);

private function onUID( event:UserIDEvent ):void{
    miuid=event.userid;
    currentState = "chatState";
    msg.setFocus();
    CursorManager.removeBusyCursor();
    this.addEventListener(KeyboardEvent.KEY_UP,keyHandler);
    picker.addEventListener(DropdownEvent.CLOSE, pickerClose);   
}

private function onStatus( event:NCEvent ):void{
    CursorManager.removeBusyCursor();
    if (event.status == "closed"){
        status.text = "Desconexión completa.";
    }
    if (event.status == "nickexiste"){
        status.text = "Usuario o contraseña incorrectos.";
    }
    currentState = "";
}

Al crear la instancia de Chat haremos que se detecten un par de eventos que ejecutarán:

  •  onUID: se dispara cuando el servidor, como hemos visto antes, llama al método cliente onUserid. Nos sirve para cambiar el estado de la interfaz de usuario desde la ventana de login a la de chat ya que sólo si hemos conectado recibiremos el UID.
  • onStatus: nos ayudará adetectar conexiones rechazadas y tu propia desconexión.

Para conectar véis que llamamos al método init de Chat con cuatro parámetros, la url de la instancia de nuestra aplicación (definida como host+aplicacion+sala de chat), el nick, la clave (para un futuro) y la propia sala de chat. Si examinas este método verás qué sencillo es iniciar una conexión. El método más importante es

private function netStatus( event:NetStatusEvent ):void{
    var info:Object = event.info;
    switch(info.code)
    {
    	case "NetConnection.Connect.Success":
    	    users_so = SharedObject.getRemote("users_so", nc.uri, false);
    	    users_so.addEventListener(SyncEvent.SYNC, usersSync);
    	    users_so.addEventListener(NetStatusEvent.NET_STATUS, onSOnetStatus);
    	    users_so.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSOSecurityError);
    	    users_so.client = this;
    	    users_so.connect(nc);
    	    chat_so = SharedObject.getRemote("chat_so", nc.uri, false);
    	    chat_so.addEventListener(NetStatusEvent.NET_STATUS, onSOnetStatus);
    	    chat_so.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSOSecurityError);
    	    chat_so.client = this;
    	    chat_so.connect(nc);
    	    break;
    	case "NetConnection.Connect.Rejected":
    	    dispatchEvent( new NCEvent(NCEvent.NONICK, "nickexiste"));
    	    break;
    	case "NetConnection.Connect.Closed":
    	    dispatchEvent( new NCEvent(NCEvent.STATUS, "closed"));
    	    break;
    }
}

Es el que detecta el estado de la conexión, si el servidor nos acepta o nos rechaza tal como veíamos antes e incluso si nos desconecta. Fíjate que si somos aceptados estaremos conectándonos a los SharedObjects que creamos antes en el servidor 😉 . Ahora es Chat quien hará absolutamente todo el trabajo, la aplicacion mxml es la interface de usuario que transmite a Chat los comandos necesarios. Veamos por ejemplo como indicamos al chat que envíe a los demás usuarios un mensaje que hayamos escrito nosotros.

private function sendMsg():void{
    if ( msg.text.length > 0 ){
        chat.sendMsg(msg.text);
        msg.text = "";
    }
}

Vale sí, pero has hecho trampas, aquí estás dejando el meollo en manos de Chat 😛 . Bien, veamos qué ocurre en sendMsg.

public function sendMsg( color:String, msg:String ):void{    
    var outmsg:String = "";    
    outmsg = "<font color='#" + color + "'><b>" + this.username+":</b> " + msg + "</font><br>";
    chat_so.send("onMsgSend", outmsg);
}

¡ Sólo eso ! Como ya estamos conectados al SharedObject chat_so, le decimos a éste que llame al método onMsgSend de sus clientes y le pase el texto que hemos escrito.

De acuerdo, entonces ¿cómo leemos los mensajes que escriben los demás usuarios?. Te lo acabo de decir 😛 . Cuando un usuario escribe algo, llama (da igual cómo) a un método local de cada usuario que se encarga de escribir el texto en su ventana de chat.

public function onMsgSend( msg:String ):void{
    chatHistory += msg;        
    dispatchEvent(new Event("histChange"));
}

Obviamente este método tiene que ser público. Al despachar ese evento simplemente hace que se actualice el texto del textarea.

Hasta ahora creo que lo voy entendiendo, pero no has explicado como detectamos las entradas y salidas de usuarios. Cierto, veámoslo pues. Acabamos de ver que cuando entramos al chat y somos admitidos conectamos con los SharedObjects que creamos en el servidor. Si te has fijado (y si no hazlo ahora), al de usuarios le poníamos un listener distinto:

users_so.addEventListener(SyncEvent.SYNC, usersSync);

Esto significa que cada vez que haya un cambio en el objeto users_so todos los clientes serán notificados de ello. Por ejemplo, cada vez que entra o sale un usuario vimos que añadíamos o quitábamos un elemento de este objeto que representaba a ése usuario. Cada una de estas operaciones hará que nuestra aplicación cliente reciba una llamada a usersSync.

private function usersSync(event:SyncEvent):void{
    var results:Object = event.target.data;
    var usersArray:Array = new Array();
    var existe:Boolean=false;
    //debo actualiza solo los datos
    for( var i:String in results ){
        if(results[i].sala==this.sala){
            existe=false;
            for(var j:Number=0; j<dpUsers.length; j++){
                if(dpUsers[j].uid==results[i].uid){
                    existe=true;
                    break;
                }
            }

            if(existe){
                //por si se ha cambiado el nick
                dpUsers[j].nick=results[i].nick;
            }else{
                var clientObj:Object=new Object();   
                clientObj.nick = results[i].nick;
                clientObj.uid = results[i].uid;
                dpUsers.addItem(clientObj);
            }
        }
    }
    //ahora tengo que eliminar las bajas
    //elimino empezando por la ultima para no cambiar los indices
    for(j=dpUsers.length-1; j>=0; j--){
        existe=false;
        for(i in results ){
            if(dpUsers[j].uid==results[i].uid && results[i].sala==this.sala){
                existe=true;
            }
        }

        if(!existe){
            dpUsers.removeItemAt(j);
        }
    }
    //finalmente ordenamos los usuarios
    var sort:Sort = new Sort();
    sort.fields = [new SortField("nick",true)];
    dpUsers.sort = sort;
    dpUsers.refresh();
    dispatchEvent(new Event("dpChange"));           
}

Esta método repasará todos los usuarios del SharedObject comparándolos con los de la lista de usuarios del canal donde nos encontramos chateando y añadiendo los nuevos usuarios y eliminando los que hayan salido.  Quizás no sea el mejor método para manejar los usuarios y habría sido mucho más sencillo tener una pareja de métodos addUsuario y delUsuario que manejen todo automáticamente con llamadas remotas igual que con los mensajes, pero quería que viésemos el modo de recibir avisos de cambios en un SharedObject.

Conclusiones

Hemos visto cómo de una manera sencillísima podemos crear nuestro propio chat sin excesivas complicaciones. Piensa además que esto es, seguramente, lo más sencillo, el primer paso con FMS, las posibilidades reales son infinitas.

Quizás en otros artículos ampliemos las posibilidades de este pequeño ejemplo añadiendo vídeo y audio o, incluso, hagamos un pequeño sistema de grabación de vídeo con tu webcam similar al de Youtube.

Como siempre, aquí os dejo el código completo del proyecto. Recuerda que es un ejemplo de lo que se puede hacer, no debes usarlo tal cual sino aplicar lo que hemos explicado. Del mismo modo habrá cosas que no sean perfectas y se puedan hacer mejor, no lo dudo, mi intención era explicar los conceptos básicos de FMS y que se vea su potencial.

Cancelan el SIMO, bueno ¿y que?

Creo que todos los que vivimos de esto (o en esto) lo veíamos venir.

La última vez que fui sería allá por 2002 o 2003 y, la verdad, no me quedaron muchas más ganas de vover los años siguientes en que el SIMO sufrió una decadencia progresiva sin que nadie dentro de la organización quisiera verlo, no hay más que ver los comentarios acerca de las últimas ediciones.

Hace 10 años era increíble el movimiento que suponía. Estaba en Teleco-Vigo y, acercándose las fechas del SIMO, sólo se hablaba de eso. La feria era como introducirte en Matrix o en Blade Runner, cientos y cientos de cosas nuevas, hardware y software por doquier que a saber cuando llegaría a este país. Había ordenadores con acceso gratuito a Internet (esto, hoy, puede sonar a coña, pero era un atractivo enorme de aquella). Todos los fabricantes esperaban a este evento para presentar productos nuevos. El SIMO no era la única feria tecnológica, había otras más pequeñas por todo el territorio nacional. Hoy todo esto se ha perdido. La globalización, los países emergentes e Internet han acelerado al máximo las presentaciones que, salvo contadas excepciones, ya no son por todo lo alto. Hoy se presenta un producto y mañana tienes un análisis completo en Internet, pasado lo puedes comprar en tiendas de Hong Kong y la semana que viene en el Media Markt.

El SIMO se había convertido en eso, un Corte Inglés o un Media Markt sin mayores pretensiones. Una feria donde lo más llamativo eran las azafatas (triste pero cierto) y donde la gente se dedicaba a llenarse de bolsas y bolígrafos, porque cosas interesantes, en realidad, ninguna.

Pero espera, si había una parte profesional, de hecho, salvo el último día, todo era profesional. Lancemos una tímida sonrisa. ¿Acaso ahora todo el mundo es profesional? ¡Claro!, las entradas e invitaciones circulaban por todas las esquinas.

No creo, pues, que a nadie le coja por sorpresa la decisión. Creo que una feria profesional del sector es necesaria en este país, pero no creo que la fórmula adecuada actualmente sea la del SIMO. Quizás ahora mismo estaban dando los primeros pasos correctos, pero han llegado tarde. Las grandes marcas han perdido el interés en la feria. Este año había sólo un 55% de los presentes el pasado y no creo que a gente como Telefónica o Microsoft les afecte la crisis o no puedan hacer frente a lo que cuesta tener presencia en el SIMO, una cifra ridícula al lado de sus presupuestos. Simplemente han visto que el sector ya no necesita ferias, tienen Internet.

Aprovechando la bajada de persiana (temporal) del SIMO, muchos han comparado esta feria con el Mobile World Congress (MWC), antiguo 3gsm, que se celebra en Barcelona desde hace unos años. He estado en el SIMO y en el MWC y, en serio, no hay color, no tienen nada que ver. La profesionalización del MWC es patente desde que llegas al recinto, y no lo digo por el coste de la entrada, lo digo por la gente que viene, los stands, los expositores… nada que ver con el SIMO. Posiblemente el MWC acabe igual que el SIMO, pero aún queda mucho tiempo para que llegue ese día, el movimiento profesional, los ciclos de conferencias y los workshops hacen del MWC algo distinto, algo hacia lo que debió comenzar a enforcarse el SIMO desde hace al menos cinco años. El público en general no tiene cabida en el MWC, apenas hay azafatas o regalos en los stands, sin el inglés estás incomunidado y lo peor de todo, la mayoría de cosas que se ven no son de consumo así que, por tanto, no interesan al público en general. En el MWC tampoco hay grandes lanzamientos ni presentaciones, pero genera una expectación inusitada.

Además, ¿por que no separar la parte mas lúdica de la feria y crear un SIMO dedicado al sector de juegos en exclusiva? Creo que tiene potencial y mercado suficiente y atraería a un público muy concreto y especializado. Aunque, por otro lado, si hoy en día eliminamos la parte de juegos y la de móviles (ya está el MWC) del SIMO, ¿quedaría algo realmente interesante?. Más aún, ¿debe una feria de este tipo ser interesante para el gran público o con que lo sea para el profesional es más que suficiente?. Quizás sea esta la pregunta fundamental ya que el SIMO se enfocó en sus últimos años al gran público, olvidándose del profesional al cual se suponía que ya interesaría de por sí.

Corren nuevos vientos para las ferias tecnológicas.

Cómo emitir webcams en vivo con un servidor Linux y en formato FLV

Hace un tiempo tuve un cliente que quería visualizar las cámaras que había instalado en su oficina desde su casa. En aquel momento eran cámaras IP cableadas. Este tipo de cámaras tienen un servidor web integrado desde donde puedes configurar su dirección IP además de ver el vídeo que transmiten. Por lo general estas cámaras transmiten en formato mjpeg, un flujo de imágenes JPG normales, aunque también podría ser que lo hiciesen directamente en mpeg. Firefox es capaz de reproducir este formato directamente, no así Internet Explorer que necesita de un applet Java para hacerlo.

El reto consistía, por tanto, en transmitir las cámaras en un formato que se pudiese reproducir fácilmente y que se pudiese integrar en una página web, sin programas de terceros para visualizarlas. Así llegamos a la solución que hoy os presento y que no es exclusiva de cámaras IP sino que puedes utilizar cualquier webcam USB que tengas por casa. El formato que hemos escogido para el flujo de vídeo será flv,  con lo que con una sencilla aplicación Flash podremos visualizar cada una de nuestras cámaras evitando, de paso, los applets Java.

Para el proyecto de hoy necesitaremos:

  • Servidor Linux, imprescindible, en mi caso Centos5
  • Apache instalado en el servidor.
  • ffmpeg, para hacer la conversión de formatos
  • ffserver, parte del paquete ffmpeg, para transmitir elvídeo

Si no tienes el software necesario, el primer paso es instalarlo. En mi caso:

yum install ffmpeg apache

Comenzaremos configurando ffserver desde /etc/ffserver.conf:

Port 8090
BindAddress 0.0.0.0
MaxClients 1000
MaxBandwidth 10000            

<Feed feed1.ffm>
  File /tmp/feed1.ffm
  FileMaxSize 5M
</Feed>
<Feed feed2.ffm>
  File /tmp/feed2.ffm
  FileMaxSize 5M
</Feed>            

<Stream camara1.flv>
  Feed feed1.ffm
  Format flv
  VideoCodec flv
  VideoBitRate 128
  VideoBufferSize 500
  VideoFrameRate 5
  VideoSize 320x240
  NoAudio
  Preroll 5
</Stream>            

<Stream camara2.flv>
  Feed feed2.ffm
  Format flv
  VideoCodec flv
  VideoBitRate 128
  VideoBufferSize 500
  VideoFrameRate 5
  VideoSize 320x240
  NoAudio
  Preroll 5
</Stream>            

<Stream stat.html>
  Format status
</Stream>

ffserver funciona como un servidor, transmitiendo en el puerto que le indiquemos lo que recibe en los archivos que se indican en los feed’s. A continuación se indican los flujos de vídeos que vamos a utilizar (feed’s), en mi caso dos cámaras. Finalmente definimos los streams que será a lo que realmente nos conectaremos nosotros, un stream por cada feed en formato flv. Podríamos crear tantos streams de cada feed como creamos oportuno en distintos formatos, consulta la ayuda de ffserver y ffmpeg.  Finalmente creamos un stream especial, el de status, donde veremos información sobre el estado del servidor y clientes contectados.

Lanzamos el servidor ffserver tal cual.

ffserver

Con esta configuración tendremos acceso a dos flujos de vídeo en formato FLV en las direcciones:

http://localhost:8090/camara1.flv
http://localhost:8090/camara2.flv

Vale, sí, aún no hemos transmitido el vídeo, eso viene ahora. Debemos enlazar el flujo de vídeo de nuestras cámaras con ffserver para que éste haga la conversión de formato y lo emita por las urls indicadas. Para hacerlo recurrimos al inseparable compañero de ffserver, ffmpeg.

Si tus cámaras están conectadas al servidor bien sea por USB o por capturadoras de vídeo (debes tenerlas configuradas previamente):

ffmpeg -f video4linux -s 320x240 -r 5 -i /dev/video0 http://localhost:8090/feed1.ffm
ffmpeg -f video4linux -s 320x240 -r 5 -i /dev/video1 http://localhost:8090/feed2.ffm

Si tus cámaras fuesen IP habría que capturar primero el vídeo desde la cámara y pasárselo a ffmpeg, por ejemplo:

curl http://ip-de-tu-camara:8080 | ffmpeg -er 4 -y -v quiet -an -f mjpeg -r 4 -i - http://localhost:8090/feed1.ffm

Sea como sea el formato de tus cámaras, y si todo ha ido bien, tendrás a ffmpeg enviando el vídeo a ffserver para que lo convierta a FLV y lo publique en las URL’s anteriormente indicadas.

Debes tener cuidado con un detalle importante si conectas más de una cámara USB, debes enchufarlas a concentradores distintos de tu equipo para diversificar adecuadamente los flujos de vídeo y el ancho de banda que ocupan, si conectases dos cámaras al mismo concentrador no tendrían ancho de banda suficiente y se bloquearían entre ellas.

Veamos ahora como visualizar los flujos de vídeo. Necesitaremos el editor de Flash de toda la vida. Creamos un nuevo documento y arrastramos al escenario un objeto FLVPlayback. Como nombre de instancia le ponemos “video” y en sus propiedades ponemos isLive a true. En el primer frame añadimos el siguiente código:

video.contentPath=path_video;

De esta manera podremos indicar desde html la url del flujo de vídeo sin tener que tener un swf por cada cámara. Compila y publica el proyecto puesto que ya hemos terminado.

No me voy a preocupar ahora del código html generado, eso es cosa tuya. Dentro del archivo html que te genera el editor de flash tendrás algo como:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="550" height="400" id="webcam1" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="webcam.swf" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<embed src="webcam.swf" mce_src="webcam.swf" quality="high" bgcolor="#ffffff" width="550" height="400" name="webcam1" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>

Vamos a convertirlo en esto:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="550" height="400" id="webcam1" align="middle">
<param name="allowScriptAccess" value="sameDomain" />
<param name="movie" value="webcam.swf?path_video=http://192.168.90.1:8090/camara1.flv" />
<param name="quality" value="high" />
<param name="bgcolor" value="#ffffff" />
<embed src="webcam.swf?path_video=http://192.168.90.1:8090/camara1.flv" mce_src="webcam.swf?path_video=http://192.168.3.2:8090/camara1.flv" quality="high" bgcolor="#ffffff" width="550" height="400" name="webcam1" align="middle" allowScriptAccess="sameDomain" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
</object>

Es decir, añadimos al parámetro de la película la variable path_video con la url del flujo tal y como lo hemos generado antes. Debe ser una URL a la que se pueda acceder externamente pues el swf se conectará a ella para recoger el flujo flv generado por ffserver, si tienes un router o un firewall de por medio, deberás abrir ese puerto. Copiaremos este código tantas veces como cámaras tengamos modificando la URL del flujo de vídeo.

Voila! esto es todo, ya tienes publicadas tus cámaras en un formato sencillo de visualizar como es el FLV.  Abre la página html que has creado y comenzarás a visualizar tus cámaras.

Pero… vayamos un paso más allá. Con lo que hemos visto hasta ahora tendremos que abrir el puerto 8090 de buestra máquina para permitir el acceso a los flujos de vídeo, pero nosotros no queremos hacer esto, ya tenemos el puerto 80 con Apache a la escucha. ¿Qué hacemos? Sencillo, mod_proxy viene a nuestro rescate. Asegúrate de tenerlo habilitado en la configuración de tu Apache. Después simplemente habrá que añadir las siguientes líneas a la configuración del virtual host en el que quieres publicar los streams:

ProxyPass /camara1.flv http://127.0.0.1:8090/camara1.flv
ProxyPassReverse /camara1.flv http://127.0.0.1:8090/camara1.flv        

ProxyPass /camara2.flv http://127.0.0.1:8090/camara2.flv
ProxyPassReverse /camara2.flv http://127.0.0.1:8090/camara2.flv

Con esto estaremos enviando automáticamente cada stream desde su url original en el puerto 8090 a una url de tu virtual host del tipo http://tudominio.com/camaraX.flv. Esa ruta no existe físicamente, es virtual y Apache sabe que debe redirigirla al stream del ffserver.

Finalmente queda un detalle importante. Hasta ahora habíamos incializado ffserver por un lado y los flujos de las cámaras por otro con ffmpeg. Esto es un rollo y no nos gusta, queremos que ffserver lo inicie todo automáticamente. Pues vale, añadamos una directiva Launch a cada feed de la configuración de ffserver con los parámetros que utilizábamos para llamarlo desde la línea de comandos. En el ejemplo de nuestra primera cámara quedaría:

<Feed feed1.ffm>
  File /tmp/feed1.ffm
  FileMaxSize 5M
  Launch  ffmpeg -f video4linux -s 320x240 -r 5 -i /dev/video0
</Feed>

A partir de ahora, cada vez que lancemos ffserver tecleando simplemente ese comando, se inciarán, además del propio servidor, los streams que hayamos definido.

Hasta aquí hemos llegado por hoy. De una manera extremadamente sencilla hemos publicado nuestras webcams. Es responsabilidad tuya y sólamente tuya el asegurar el acceso a los flujos y a la página de visualización que hemos creado. Otro día quizás veamos cómo montar un sistema de videovigilancia casero con detector de presencia 😉 .

Os dejo el código de la aplicación Flash de visualización de los streams.

Usando certificados SSL de cliente como sistema de autenticación web

A menudo creamos aplicaciones web con un backend de gestión que, por ser también web, exponemos públicamente a cualquiera que consiga averiguar la URL. Habitualmente estos sistemas son de acceso restringido, sólo un pequeño grupo de usuarios lo utiliza.

En escenarios donde tenemos un número de usuarios acotado y se necesita autentificación, se puede utilizar un mecanismo de certificados que aporten mayor seguridad al sistema, de esta manera solo aquellos usuarios que tengan el certificado en cuestión tendrán acceso a la máquina.

Hoy veremos como permitir el acceso a nuestra aplicación a aquellos usuarios que dispongan de un certificado que previamente les habremos enviado mientras que si no lo tienen no podrán acceder de ningún modo. Este método se puede combinar, además, con el tradicional usuario/clave para dar mayor seguridad. Podremos incluso verificar que el nombre de usuario que se intenta utilizar se corresponde con el certificado de usuario que le hemos enviado y no intenta autentificarse con otro.

Conceptos básicos sobre certificados SSL

El método que vamos a ver se basa en certificados SSL. Se utilizan para asegurar la información entre un cliente y el servidor y prevenir escuchas ya que la información viaja encriptada. Ésta es su función y la hace aunque no esté firmado por una autoridad certificadora (CA) oficial o, incluso, aunque esté caducado. Sigue asegurando las comunicaciones.

Los navegadores web reconocen, por defecto, una serie de autoridades certificadoras como Verisign o Thawte, aunque hay muchas más. Puedes verlas todas en las opciones de tu navegador. Pero, ¿qué es realmente lo que hace una Autoridad Certificadora? Firmar. Firma tu certificado SSL asegurando que os pertenece a ti y a tu dominio. Cuando un cliente accede a tu dominio y descarga el certificado SSL, busca dentro de sus certificados de CA‘s si hay alguno que lo haya firmado. Si lo encuentra, acepta tu certificado y no ocurre nada especial, pero si no encuentra la CA lanza un aviso indicando que no se reconoce la autoridad que lo firma. Esto no quiere decir que el certificado no sea válido, lo único que ocurre es que no sabe quien lo firma. Esto significa, por tanto, que tú mismo puedes ser tu propia autoridad certificadora y firmar tus certificados, funcionarán perfectamente y cumplirán su cometido de asegurar las comunicaciones cliente/servidor.

Comercialmente o en sistemas de acceso público en general, no se recomiendan certificados autofirmados ya que el aviso de autoridad de certificación no reconocida generará desconfianza entre tus usuarios, pero en entorno intranet o de paneles de adminsitración es un método ideal.

El servidor puede requerir, además, otro certificado al cliente, de manera que ámbos extremos autentifiquen la comunicación. Esto es precisamente lo que vamos a hacer hoy en este artículo.

Según lo que hemos explicado, los certificados autofirmados son igual de seguros que los firmados por una autoridad certificadora. Como en el ejemplo que estamos viendo estamos asegurando el acceso a nuestra aplicación para un grupo reducido de usuarios, no hay ningún problema en utilizar un certificado firmado por nosotros mismos ya que nuestros usuarios sabrán que no hay ningún problema. Pero esto no es todo, por esta misma razón podemos decir a los usuarios que se instalen el certificado público de nuestra CA, tal y como hacen las CA oficiales, y automáticamente el navegador comenzará a confiar en nuestros certificados ya que, ahora sí, tiene un certificado de una CA que firma los certificados SSL.

Como resumen, nuestro trabjo consistirá en:

  • Crear nuestra autoridad certificadora y su certificado.
  • Crear el certificado SSL para nuestro servidor web firmado por nuestra CA.
  • Crear los certificados de cliente para nuestros usuarios.
  • Habilitar la lectura de los datos SSL desde PHP.

Servidor web SSL

Utilizaremos el paquete Openssl para generar los certificados. Si aún no lo tienes instalado en tu servidor, es el momento. Explicaré de manera rápida como crear crear certificados SSL para asegurar las comunicaciones ya que es el primer paso necesario para añadir certificados de cliente, sin embargo no es el objeto principal de este artículo y hay mucha documentación, googlea un poco 😉 .

Primero creamos el certificado y la clave privada de nuestra autoridad de certificación:

openssl req -x509 -newkey rsa:2048 -days 3650 -keyout CAXplotaKey.pem -out CAXplotacert.pem

Lo más importante de este comando es el parámetro days, ya que no queremos que dentro de un año nos caduque el certificado de nuestra propia entidad. Yo le pongo 10 años. Este comando genera dos archivos, la clave privada con la que firmaremos nuestros futuros certificados y el certificado con la clave pública que instalaremos, si queremos no recibir avisos, en el navegador. Este comando te pedirá algunos datos (nombre de empresa, país…) y, sobre todo, una contraseña. Deberás recordarla cada vez que vayas a firmar un certificado SSL, así que no la olvides. Ya tenemos nuestra CA creada.

Creamos ahora el certificado SSL para nuestro dominio:

openssl genrsa -des3 -out claveprivada.pem 2048
openssl req -new -key claveprivada.pem -out certificado.pem

El primer comando crea la clave privada de nuestro certificado. Te pedirá otra contraseña, esta vez para la clave privada. Recuérdala también.

El segundo comando genera la petición de certificado sobre la clave privada anterior. Te pedirá la contraseña de la clave privada anterior.

En este punto tenemos cuatro archivos, la clave y el certificado de tu CA y la clave y la solicitud de tu certificado SSL. Sólo queda firmar el certificado con nuestra CA y ya podremos utilizarlo.

Para poder firmarlo debemos generar primero un fichero de texto con algunos parámetros de configuración:

cat configservidor.cnf

basicConstraints = critical,CA:FALSE
extendedKeyUsage = serverAuth

Y firmamos el certificado.

openssl x509 -CA CAXplotacert.pem -CAkey CAXplotaKey.pem -req -extfile configservidor.cnf -in certificado.pem -days 3650 -CAcreateserial -sha1 -out certificado-servidor.pem

Ya tenemos un certificado SSL preparado para utilizar en nuestro servidor web. En nuestro caso es certificado-servidor.pem. Vamos a configurar Apache para que lo utilice.

Habrá que editar httpd.conf y añadir

LoadModule ssl_module modules/mod_ssl.so
Listen 443

Es probable que, si no has compilado tu propio Apache y has instalado un paquete precompilado, tengas ya algún ssl.conf. Activándolo tendrás este paso preparado. En mi caso, un CentOS5, sólo hay que incluir /etc/httpd/conf.d/ssl.conf.

Finalmente hay que indicar en el virtual host que quieres asegurar que use nuestro nuevo certificado. Para hacerlo añadimos un nuevo virtual que escuche en el puerto 443 y añadimos las siguientes líneas:

SSLEngine on
SSLCertificateFile /ruta/a/certificado-servidor.pem
SSLCertificateKeyFile /ruta/a/claveprivada.pem

Si ahora reinicias Apache y accedes a tu dominio con https verás que en tu navegador aparece el candado indicando que la información es segura. Verás también que te aparece un aviso de que no se confía en la autoridad certificadora. Veremos más adelante como solucionarlo.

Añadiendo certificados de cliente

Ahora que ya tenemos nuestro servidor web seguro con nuestros certificado autofirmado llega el momento de crear certificados para nuestros clientes de manera que si alguien intenta acceder a nuestra aplicación sin uno de ellos se le prohíba el paso.

Crearemos primero un archivo de configuración con los parámetros que necesitaremos.

cat configcliente.cnf

basicConstraints = critical,CA:FALSE
extendedKeyUsage = clientAuth

Con esto daremos instrucciones de que es un certificado cliente a la hora de firmar el certificado.

Creamos ahora, igual que hacíamos antes, la clave privada y la solicitud de certificado.

openssl genrsa -des3 -out clave-cliente.pem 2048
openssl req -new -key clave-cliente.pem  -out certificado-cliente-req.pem

Como antes, al generar la clave te pedirá una contraseña que deberás introducir después, al hacer la solicitud de certificado. Los datos que te pide esta solicitud, como ocurría antes, los podrás leer posteriormente para comprobar datos o lo que estimes oportuno, así que es importante que prestes atención.

Firmamos ahora el certificado con nuestra CA:

openssl x509 -CA CAXplotacert.pem -CAkey CAXplotaKey.pem -req -in certificado-cliente-req.pem -days 3650 -extfile configcliente.cnf  -CAcreateserial -sha1 -out certificado-cliente.pem

Como véis, el proceso es el mismo para generar el certificado de servidor, pero cambiamos el contenido del archivo de configuración que le indica que es un certificado cliente, no servidor.

Vale, bien, pero ¿no quedamos que es el cliente el que debe instalar el certificado? Sí, ahí vamos ahora. El certificado que acabamos de generar lo debes instalar en tu navegador web, no en el servidor, así que habrá que convertirlo a algún formato que puedan entender. Para esto hacemos lo siguiente:

openssl pkcs12 -export -in certificado-cliente.pem -inkey clave-cliente.pem -certfile CAXplotacert.pem -out cliente.p12

Nos pedirá la contraseña de la clave privada del certificado y nos solicitará otra para el que va a generar. Es importante poner contraseña al certificado final ya que es el que vas a enviar a tus usuarios y pretendes que sólo estos puedan utilizarlo, así que poner una contraseña nunca está demás.

Ya tienes tu certificado cliente.p12. Puedes probarlo tu mismo. Ah no, espera, que no hemos configurado el servidor web para que solicite sí o sí un certificado al cliente. Añade a la configuración SSL de tu virtual host de Apache:

SSLCACertificateFile /path/a/CAXplotacert.pem
SSLVerifyClient require

Y reinicia tu servidor web. Si ahora intentas acceder a tu dominio verás como salta una ventana solicitando un certificado que no tienes. Desde las opciones de tu navegador, busca la sección de certificados SSL y añade un nuevo certificado. Selecciona tu cliente.p12. Te pedirá la contraseña que pusiste al convertirlo a pk12. Ya está. Accede ahora y verás como te aparece el certificado en la ventana de certificiados disponibles y, si lo seleccionas, te deja acceder a tu aplicación.

Revocar certificados

A veces puede ser necesario prohibir el paso con determinados certificados, bien porque el usuario ya no colabora o trabaja contigo, bien porque hay posibilidades de que el certificado haya sido robado, etc. Una opción es, a la hora de crear el certificado, y si sabes de antemano que el usuario lo necesitará pocos días (cuenta de prueba, usuario esporádico o cualquier razón similar), generarlo con una validez limitada (10 días, un mes, etc.), de modo que pasado este tiempo el certificado caduca y el usuario no puede entrar. El problema más importante lo tendrás cuando necesites prohibir el acceso a usuarios con certificados a largo plazo. Para hacerlo debemos crear una lista de revocación de certificados.

En mi caso (CentOS5 y RHEL5) tuve algunos problemas de configuración. Openssl debería de mantener una lista de los certificados que emite y sus números de serie sin embargo tal como hemos generado nuestros certificados no lo hace. Para solucionarlo, creamos a mano la lista.

touch /etc/pki/CA/index.txt

Ahora debemos editar el archivo de configuración de Openssl para reflejar algunos parámetros. Por defecto indica una ruta relativa hacia los directorios donde dejará los certificados, pero no me funcionó bien hasta que no le puse rutas absolutas. Modificamos pues la ruta al directorio y le indicamos donde están la clave privada y el certificado de nuestra CA.

Editar /etc/pki/tls/openssl.cnf

dir             = /etc/pki/CA              # Where everything is kept
certificate     = /path/a/CAXplotacert.pem    # The CA certificate
private_key     = /path/a/CAXplotaKey.pem # The private key

Ahora sí, podemos revocar un certificado de manera tan sencilla como:

openssl ca -revoke certificado-cliente.pem

A partir de la lista con el estado de los certificados debemos crear una lista de revocación que usaremos para indicar al servidor web los certificados que no debe permitir.

openssl ca -gencrl -out recovados.crl

Es posible que al lanzar este comando te apareza otro error relacionado con crlnumber. Esto viene relacionado con lo que os indicaba antes y es simplemente que no existe el archivo de números de serie. Para solucionarlo, simplemente lo creamos:

echo "01" > /etc/pki/CA/crlnumber

Y volvemos a generar la lista de revocación. Cada vez que revoques un certificado deberás repetir esta operación para tener la nueva lista.

Ya sólo falta indicarle a Apache qué certificados no debe permitir. Añadimos a la configuración de nuestro virtual host, en el mismo sitio donde configuramos anteriormente el certificado SSL, la siguiente línea:

SSLCARevocationFile path/a/revocados.crl

Eso es todo, ya tenemos nuestro servido SSL funcionando, nuestra aplicación protegida con certificados de cliente y la opción para revocar certificados.

Comprobando los certificados con PHP

Queremos ir un poco más allá. Queremos que, desde nuestra aplicación, podamos leer el certificado SSL del cliente y comprobar quién es. Seguro que a ti se te ocurren mejores cosas que hacer con estos datos 😉 . Lo primero que tendremos que hacer será configurar Apache para que pase los datos del certificado. En la configuración de tu virtual host añades:

<Files ~ ".(cgi|shtml|phtml|php?)$">
 SSLOptions +StdEnvVars +ExportCertData
</Files>

Ya está. Si ahora haces un print_r($_SERVER) en la aplicación donde tienes instalado tu certificado SSL de cliente verás entre todos los datos algo como:

[SSL_CLIENT_S_DN] => /C=ES/ST=Valencia/L=Valencia/O=Osusnet/CN=blog.osusnet.com/emailAddress=osusENosusnet.com
[SSL_CLIENT_S_DN_C] => ES
[SSL_CLIENT_S_DN_ST] => Valencia
[SSL_CLIENT_S_DN_L] => Valencia
[SSL_CLIENT_S_DN_O] => Osusnet
[SSL_CLIENT_S_DN_CN] => blog.osusnet.com
[SSL_CLIENT_S_DN_Email] => osusENosusnet.com

Tienes toda la información que necesitas, son los datos que se solicitaban al crear la solicitud de certificado. Generando estos parámetros de manera adecuada podrás saber quién entra a tu aplicación a partir del certificado cliente que usa.

Eso es todo por hoy. De una manera muy sencilla hemos añadido un poco más de seguridad a una aplicación web de acceso privado y con un número de usuarios acotado. También hemos aprendido y poco más sobre certificados SSL y hemos visto cómo firmar nuestros propios certificados, igual de fiables que los comerciales. Espero que os sirva de algo 😛 .

Error con el smtp autentificado con los clientes de Nokia a través de Qmail

Como os decía hace unos días, recientemente he cambiado el Nokia N70 que llevaba desde hace algo más de dos años por un Nokia E51. La característica más importante de este nuevo terminal es la disponibilidad de conexión WIFI, con lo cual, si tienes un poco de suerte y encuentras un punto de acceso abierto, puedes tener acceso a tu correo desde cualquier punto. No he tenido muchos problemas para encontrar puntos de acceso y habitualmente he podido leer el correo. El problema llega a la hora de enviarlo ya que siempre obtengo un error en el envío. Cansado de no saber qué ocurría y dado que voy a estar unos días fuera de casa, decidí investigar un poco.

Comencé por ver cual era la comunicación del cliente de correo con mi servidor. Para ello lancé un sniffer en mi máquina, en este caso Wireshark, nombre actual del Ethereal de toda la vida. Para no volverme loco con todo el tráfico que iba a ver añadí un filtro, de manera que sólo se mostraría el tráfico que venía desde la IP de mi conexión ADSL (11.22.33.44), así sólo vería lo que estaba generando mi propio móvil.

tshark -i2 -f "src host 111.222.333.444"

Y este fué el resultado:

3.197223 11.22.33.44 -> 55.66.77.88 TCP 49537 > smtp [SYN] Seq=0 Win=64240 Len=0 MSS=1460 TSV=1218449730 TSER=0 WS=0
3.197263 55.66.77.88 -> 11.22.33.44 TCP smtp > 49537 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=389435772 TSER=1218449730 WS=7
3.347274 11.22.33.44 -> 55.66.77.88 TCP 49537 > smtp [ACK] Seq=1 Ack=1 Win=64240 Len=0 TSV=1218663480 TSER=389435772
3.356257 55.66.77.88 -> 11.22.33.44 SMTP Response: 220 servidor ESMTP
3.509426 11.22.33.44 -> 55.66.77.88 SMTP Command: EHLO
3.509451 55.66.77.88 -> 11.22.33.44 TCP smtp > 49537 [ACK] Seq=29 Ack=22 Win=5888 Len=0 TSV=389436084 TSER=1218828105
3.509490 55.66.77.88 -> 11.22.33.44 SMTP Response: 250-servidor
3.663467 11.22.33.44 -> 55.66.77.88 SMTP Command: AUTH CRAM-MD5
3.813384 11.22.33.44 -> 55.66.77.88 SMTP Command: amlxxxxxxWFzIDkxNTM4MxxxxxxyYjVjOTdmYxxxxxxxDFhZDg2MjEw
3.853554 55.66.77.88 -> 11.22.33.44 TCP smtp > 49537 [ACK] Seq=197 Ack=95 Win=5888 Len=0 TSV=389436428 TSER=1219132230
8.817212 55.66.77.88 -> 11.22.33.44 SMTP Response: 535 authorization failed (#5.7.0)

Es decir, me estaba fallando la autentificación a través de CRAM-MD5 a pesar de que en el programa de correo del móvil tenía correctamente introducidos el usuario y la clave y el servidor, obviamente, se presentaba diciendo que soportaba este método.

[osus@servidor ~]# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
220 servidor ESMTP
ehlo
250-servidor
250-AUTH LOGIN CRAM-MD5 PLAIN
250-PIPELINING
250 8BITMIME

Investigando un poco averigué que para habilitar la autentificación CRAM-MD5 en Qmail, el MTA de correo que utilizo, además de instalar el parche qmail-smtp-auth es necesario sustituir el original checkpasswd  por cmd5checkpw. Con este argumento caí en la cuenta de que nunca jamás he tenido esta autentificación funcionando en mis servidores 😀 , el servidor indicaba que sí pero la realidad era que no 😛 . Ocho años utilizando Qmail y ahora me doy cuenta de que no funcionaba.

No quería complicarme mucho así que, si hasta ahora no la tenía, tampoco me importaba seguir así. Pensé entonces que, si en vez de responder a los clientes que soportaba CRAM-MD5, no se lo decía, el cliente del E51 escogería el AUTH LOGIN de toda la vida y funcionaría. ¿Cómo hacer esto? Sencillo, Qmail es de código abierto y tenemos el código fuente a nuestra disposición.

Parcheando Qmail

En los fuentes de mi Qmail (con todos sus parches) busqué la cadena en cuestión:

grep  CRAM-MD5 *

Y ahí me apareció el objetivo de nuestro parche, qmail-smtpd.c.

Dicho y hecho, abrimos el archivo con vi y buscamos la cadena en cuestión (:/CRAM-MD5) llegando a:

out("rn250-AUTH LOGIN CRAM-MD5 PLAIN");

Esto va a ser sencillo. Modificamos esa/s línea/s de manera que quede/n:

out("rn250-AUTH LOGIN PLAIN");

De este modo, cuando el cliente pregunte le diremos que soportamos LOGIN y PLAIN, pero no CRAM-MD5. He explicado esto en plural porque es más que probable que tengas dos líneas iguales y no sólo una. Puedes dejar sólo una modificada como he indicado o modificar las dos.

Compilamos ahora con un simple make. Como sólo hemos  modificado este archivo únicamente se compilará este. Hacemos una copia de seguridad de nuestro /var/qmail/bin/qmail-smtpd (por si algo no va bien) y copiamos el nuevo a esta ruta dejando los permisos, owner y group igual que tenía el que había.

Si ahora pruebas a enviar un email desde el Nokia E51 comprobarás que funciona correctamente, el cliente del teléfono escoge otro de los sistemas. Utilizando de nuevo el sniffer verás que ahora escoge AUTH LOGIN como método de autentificación.

Ya puedo enviar correo desde mis servidores con el teléfono. Gracias a un sencillo ejercicio de escucha del tráfico de red hemos averiguado cual era el problema.

Desbloquea tu teléfono móvil sin desbloquear el teléfono

Suena raro, lo se, pero veréis que todo tiene sentido.

Esto es un post algo fuera de lo que suelo escribir, pero creo que a muchos de los lectores les interesará tener acceso a Internet vía móvil y el invento es, cuanto menos, curioso.

Necesitaba un teléfono libre para utilizar la tarjeta SIM de Simyo para conectarme a Internet y lo único que tenía era mi viejo T630, sin 3G. Sin embargo la suerte quiso que acabe de cambiarme mi viejo Nokia N70 por un flamante Nokia E51 con wifi, con lo que tenía el compañero perfecto en el N70. Sólo quedaba un paso, desbloquearlo, pero me pedían 50 euros. Buscando por Internet y sin saber bien cómo, llegué a esto en una tienda en Hong Kong donde aseguraban vender una especie de adaptadores que puestos entre la SIM y el teléfono de la mayoría de compañías y terminales los desbloqueaban. Buscando alguna referencia sobre el cacharro en cuestión todo era alabanzas y buenas referencias.

Era la primera vez que oía hablar de ello, pero por el precio que proponían, 7,76 dólares (poco más de cinco euros) gastos de envío incluídos, tampoco tenía mucho que perder. Así que dicho y hecho, me compré una. Sorprendentemente cuatro días después tenía una carta en mi buzón con el adaptador en cuestión.

El aparato no es más que una lámina de plástico del tamaño de una SIM que hace de base del circuito y unas cuantas pistas para conectar ambas caras del adaptados ya que por un lado pondrás la SIM original y por el otro el teléfono. En una esquina lleva un pequeño microchip que será lo que, como veremos después, nos de algún problema para introducirla.

Esto sería mi equipo de pruebas. El Nokia N70, la SIM de Simyo y el adaptador de cinco euros. Obviamente no me creía yo mucho que esto funcionase, pero habia que probar.

img_4016.JPG

Para los incrédulos, esto es lo que dice el teléfono al poner la tarjeta directamente, está bloqueado por Vodafone.

img_4035.JPG

Cuando tienes en tu mano el adaptador y la sim ves que vas a tener problemas. Aunque yo pude colocarla más o menos bien en mi N70 reconozco que es a costa de forzar un poco el cierre. En un terminal donde la tarjeta entra en su compartimendo deslizándola no va a ser posible ponerla, así que lo mejor es operarla. Lo que tendremos que hacer, tal como véis en las siguientes fotos, es hacer un pequeño recorte en el extremo de la sim del tamaño del microchip del adaptador, de manera que al colocar una sobre otra el chip sobresale por este hueco y ámbas tarjetas quedan perfectamente unidas.

img_4021.JPG

img_4020.JPG

Procedemos ahora a colocar el adaptador y la sim en su compartimento. Primero dejamos caer el adaptador. En la foto véis como en la parte superior sobresale el microchip del que hablábamos. Si pusiésemos la sim tal cual sobre él, el compartimento no cerraría bien y habría que forzarlo un poco.

img_4023.JPG

Sin embargo, al hacerle el recorte que hemos visto, todo encaja a la perfección.

img_4024.JPG

Aquí vemos en detalle el cómo queda todo montado. Véis que encaja perfectamente.

detalle.jpg

Nos queda la prueba de fuego. ¿Funcionará?. Mejor lo véis en la foto directamente 😐 .

 img_4036.JPG

En efecto, funciona a la perfección, el teléfono no se queja de nada. Sólo tendremos que poner el adaptador debajo de cualquier SIM y funcionará en la mayoría de teléfonos. ¿Será esto cierto?. Lo probamos. Nokias, Sony Ericsson, HTC y hasta Blackberry. Todo lo que probamos funcionó. Estamos ahora pendientes de probarlas en un iPhone. Ya veremos, pero en general parece que el invento funciona.

Si tu teléfono es de esos donde la SIM entra deslizándola en su compartimento, recomiendo pegar el adaptador a la sim con pegamento, te quitarás problemas para meterla y sacarla.

Me parece simplemente genial el poder utilizar cualquiera de tus sim’s en cualquier teléfono (familia, amigos…). Ya no dependes del terminal sino simplemente de la tarjeta, la que te da el servicio y, a fin de cuentas, la que te cobra. Sencillo, barato y sin perder la garantía de tu teléfono 🙂 .

Conexiones VPN entre máquinas Linux y Windows con autentificación a través de servidor Radius y MySQL

Un día, harto de tener que abrir puertos y más puertos para cada cosa que quería hacer con el servidor Linux que tengo en casa, se me ocurrió hacer algo para poder conectarme directamente a ésa máquina y tener todos los servicios a mi disposición sin tener que crearlos uno a uno. La solución estaba clara, establecer una VPN contra ese servidor y automáticamente tendría acceso a todos los servicios disponibles. El problema principal a la hora de establecer esta red virtual era que tenía que ser sencilla, rápida de crear y, sobre todo, no necesitar software adicional ya que así podría utilizarla desde cualquier ordenador en cualquier localización. Es más, si me lo montaba bien tendría un sistema perfecto para dar soporte remoto a mis hermanas a través de VNC sin tener que abrirles esos puertos 😉 .

Por defecto todos los equipos Windows traen de serie un cliente VPN que puede contectarse a redes privadas virtuales, pero no de cualquier tipo, sólo PPTP. Podríamos haber escogido otro sistema basado en IPsec o incluso OpenVPN, pero necesitaríamos software adicional además de cerficados en ámbos extremos de la VPN, lo que no cumpliría los requisitos que nos habíamos impuesto. PPTP es un protocolo desarrollado por Microsoft (por eso viene de serie con Windows) y, gracias a eso, ha tardado mucho en haber un cliente (y más aún un servidor) que funcione bajo Linux. No es el más seguro de los protocolos de VPN 😛 pero dejémoslo en que cumple sus funciones.

La idea es, por tanto, montar un servidor PPTP bajo Linux. Una vez tengamos el servidor veremos como conectar tanto desde Linux como desde Windows. Aunque la idea de este artículo parte de un entorno doméstico es completamente aplicable a pequeñas empresas que necesiten dar acceso remoto a sus empleados sin complicarles la vida ni realizar grandes desembolsos en routers dedicados o en un servidor Windows (entendiendo esto como una máquina con algún Windows Server 😉 .

Servidor PPTP bajo Linux

Vamos a enrevesar un poco más nuestro servidor. Para da más versatilidad haremos que la autentificación de los usuarios se realice a través de un servidor Radius que posteriormente podemos utilizar para autenticar cualquier otro servicio que se nos ocurra (ftp, email, hotspot inalámbrico…).

Creo que aún es muy sencillo, vamos a complicarlo más aún. El servidor Radius autenticará, así mismo, contra una base de datos MySQL, con lo que tendremos un sistema muy facil de administrar sin tener que estar tocando archivos de texto para crear usuarios nuevos. El escenario es, por tanto PPTP+Radius+MySQL.

Como software vamos a utilizar:

  • Servidor Linux, Centos 5.2 en mi caso.
  • PopTop, servidor PPTP bajo Linux.
  • PPTP Client, cliente PPTP bajo Linux.
  • FreeRadius como servidor Radius.
  • Radiusclient como cliente Radius, para que PopTop pueda consultar el servidor Radius.

Para instalar el software, en mi caso, nada más sencillo. Primero instalamos el repositorio yum de PopTop:

rpm -Uvh http://poptop.sourceforge.net/yum/stable/fc7/pptp-release-current.noarch.rpm

Y ya podemos instalar todo el software:

yum --enablerepo=poptop-stable install freeradius freeradius-mysql radiusclient pptp pptpd

Creo que con eso sería suficiente y tendríamos todo lo necesario. Asumimos, por supuesto, que ya tienes MySQL instalado.

Antiguamente era más complicado instalar PopTop ya que había que parchear el kernel, pero hoy en día, si tu núcleo es superior a 2.6.15 (si no lo es, ¿a qué esperas para actualizarlo?), no es necesario este paso. De todos modos, si tuvieses que hacerlo, es totalmente seguro, yo mismo lo hice durante mucho tiempo. En la web de PopTop tienes las instrucciones para hacerlo.

Suponiendo que hemos instalado correctamente todo el software sin ningún problema, ya sólo nos queda configurar todos y cada uno de los pasos que conforman nuestro servidor VPN.

Configurando PopTop

Lo primero que debes hacer es decidir que direccionamiento utilizarás en tu VPN. En mi caso tengo uno independiente de todo lo demás (192.168.3.0), así puedo gestionarlo a mi antojo, permitiendo o denegando lo que se me ocurra de una manera sencilla. A continuación indico los archivos de configuración a toquetear y como tengo los míos.

/etc/pptpd.conf

[osus@servidor ~]# cat /etc/pptpd.conf
option /etc/ppp/options.pptpd
localip 192.168.3.1-5
remoteip 192.168.3.6-10

El parámetro localip tendrá las IP’s que utilizará tu servidor como locales cada vez que reciba una conexión mientras que en remoteip indicarás las que va a dar dinámicamente a los clientes. Tendrás que poner un rango lo suficientemente amplio como para cubrir las posibles conexiones simultáneas que puedas tener.

/etc/ppp/options.pptpd

[osus@servidor ~]# cat /etc/ppp/options.pptpd
name pptpd
refuse-pap
refuse-chap
refuse-mschap
require-mschap-v2
require-mppe-128
proxyarp
lock
nobsdcomp
novj
novjccomp
nologfd
plugin radius.so

Sería recomendable que consultases las páginas man para saber qué hace cada parámetro.

Como véis, al final de todo indicamos al servidor PPTP que utilice el pluggin para Radius, así que configuremos todo lo relativo a Radius.

Configurando FreeRadius

/etc/raddb/clients.conf

[osus@servidor ~]# cat /etc/raddb/clients.conf
client 127.0.0.1 {
        secret          = TUCLAVESECRETA
        shortname       = localhost
}

Debemos indicar, para cada servidor Radius que tenemos, una clave de conexión y el nombre del mismo. La clave deberán utilizarla los clientes radius para consultar al servidor. Lo normal en un entorno como el nuestro es que el servidor Radius sea el mismo donde reside el servidor VPN.

El siguiente archivo es el más importante ya que se indica a FreeRadius que autentifique las peticiones contra un servidor MySQL. Como es muy largo pondré solamente las partes relevantes:

/etc/raddb/radiusd.conf

mschap {
 authtype = MS-CHAP
 use_mppe = yes
 require_strong = yes
}

authorize {
        preprocess
        mschap
        suffix
        eap
        sql
}

authenticate {
        Auth-Type MS-CHAP {
                mschap
        }
        eap
}

preacct {
        preprocess
        suffix
        files
}

accounting {
        detail
        acct_unique
        sql
}

session {
       sql
}

En /etc/raddb/sql.conf  debes configurar correctamente el acceso a tu base de datos MySQL, servidor, usuario, clave y base de datos que veremos un poco más adelante.

Configurando el cliente Radius

/etc/radiusclient/servers

[osus@servidor ~]# cat /etc/radiusclient/servers
localhost       TUCLAVESECRETA

Donde la clave es la misma que pusiste en /etc/raddb/clients.conf y localhost la dirección de tu servidor si es distinto al que tiene el pptpd.

En /etc/radiusclient debes tener el archivo dictionary.microsoft. Creo recordar que tuve algunos problemas con él, por si acaso dejo el que tengo ahora mismo que no es el que venía por defecto.

En /etc/radiusclient/radiusclient.conf asegurate que tienes los siguientes parámetros apuntando a la IP de tu servidor Radius si no es el mismo donde reside el servidor VPN:

authserver      localhost
acctserver      localhost

Si son distintos a localhost, no te olvides de configurarlos aquí.

Creando la base de datos MySQL

Llegamos al último paso.

Crea una nueva base de datos (create database radius) y un usuario con permisos sobre ella. Recuerda configurar ahora /etc/raddb/sql.conf  con estos datos.

Ahora crea la estructura de la base de datos. Con el paquete FreeRadius viene la estructura que necesitas.

mysql radius < /usr/share/doc/freeradius-1.1.3/examples/mysql.sql

El paso que viene a continuación siempre lo ignoran cuando alguien explica cómo configurar FreeRadius contra MySQL y creedme que no es nada intuitivo.

¿Cómo se rellenan las tablas de autentificación?

Buena pregunta Manel 😛 .

  • Tabla radcheck: mantiene las cuentas de usuario con los siguientes campos:
    • UserName: nombre de usuario.
    • Attribute: Password (literalmente, no la clave del usuario sino la palabra Password).
    • op: == (dos signos de igual).
    • Value: clave del usuario.
  • Table radreply: contiene parámetros de inicialización de los clientes que se conectan. Aquí yo configuro las IP’s que quiero dar a determinados clientes por cuestiones de comodidad.  Además indico que sólo voy a permitir una conexión simultánea con el mismo usuario.
    • UserName: nombre de usuario que estás configurando (según lo introducido en la tabla radcheck).
    • Attribute: la palabra Framed-IP-Address ó Simultaneous-Use, según indiques la IP a asignar a ese usuario o el número máximo de sesiones con el mismo nombre.
    • op: = (un sólo signo igual)
    • Value: 192.168.3.99 (la IP que quieras) ó el número máximo de conexiones simultáneas con el mismo usuario.
  • Table usergroup: agrupa los usuarios en grupos.
    • UserName: nombre de usuario
    • GroupName: nombre de grupo.

La tabla radact contiene el log de actividad del servidor, sesiones iniciadas, duración, etc.

Últimos pasos

Recuerda que debes abrir en tu router y/o firewall el puerto 1723 para que permita conexiones entrantes ya que es el usado por el protocolo PPTP.

Aunque como veremos más adelante podemos auditar las conexiones al servidor Radius (y por ende al servidor VPN) puede ser interesante tener un mecanismo de aviso de que un cliente se ha conectado. Una forma de hacerlo es consultando las interfaces de red disponibles en el servidor (ifconfig), habrá tantos pppX como usuarios activos. Pero hay otro método que te permite recibir, por ejemplo por email, un aviso cada vez que un usuario conecta o desconecta.

Cada vez que se levanta una interfaz ppp se ejecuta el script /etc/ppp/ip-up.local con todos los parámetros relativos a esa conexión, ip remota, local, interfaz… Igualmente cuando se desconecta se lanza /etc/ppp/ip-down.local. Solo debemos adaptar este script a nuestras necesidades. Estos scripts reciben todos los parámetros necesarios para identificar al usuario. Haríamos algo así, por ejemplo para ip-up.local.

#!/bin/sh
if [ "$5" == "192.168.3.10" ]
then
        cliente="pepito"
fi
echo  "Conexion VPN
Interfaz: $1
VPN Local: $4
VPN Remota: $5
IP Remota: $6
1: $1
2: $2
3: $3
4: $4
5: $5
6: $6
"  | mail -s "Conexion VPN - $cliente" [email protected]

De este modo recibirías un email cada vez que un usuario  levanta un tunel VPN con tu servidor y sabrías qué ip tiene el usuario y, si le has otorgado una IP fija en la configuración de Radius, sabrás qué usuario es, en este caso “pepito“.

Con ip-down.local harías un script semejante sólo que en vez de Conexión VPN en el asunto del email pondríamos Desconexión VPN. Los parámetros son exactamente los mismos.

Estos scripts podemos aprovecharlos también para crear/modificar/eliminar determinadas rutas en función de los túneles creados.

El propio paquete pptpd te habrá instalado el script de inicio necesario, en mi caso /etc/init.d/pptpd. Sólo debo añadirlo a la secuencia de arranque del runlevel de mi servidor y automáticamente estará siempre disponible el servicio.

En teoría, todas las vpn’s que se hagan contra el servidor tienen enrutamiento entre sí, es decir, podrías llegar desde un cliente a otro pasando por el servidor sin toquetear nada más. Digo en teoría porque esa es la función del parámetro proxyarp que configuramos hace un rato. Puede que este enrutamiento no sea suficiente y que necesites que los clientes VPN puedan acceder a otras subredes de tu infraestructura. Puedes hacerlo como quieras, incluso configurar un bridge, pero para estas cosas nada mejor que iptables.

Supongamos un escenario en el que tenemos una lan local de la que forma parte nuestro servidor (direccionamiento 192.168.0.0) y la nueva red que creamos para las VPN’s (direccionamiento 192.168.3.0). Para permitir el enrutamiento completo entre las dos redes haríamos algo como:

#!/bin/sh
echo 1 >/proc/sys/net/ipv4/ip_forward
LAN="192.168.0.0/16"
VPN2="192.168.3.0/24"
iptables -A FORWARD -s $LAN -d $VPN2 -j ACCEPT
iptables -A FORWARD -s $VPN2 -d $LAN -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -d $VPN2 -j MASQUERADE

Sencillo. Si sólo quisiésemos que ciertos usuarios accediesen a la red local modificaríamos la variable VPN2 por la IP que radius le da al usuario.

FreeRadius desde un  entorno web, dialup_admin

Vale, es cierto, es un auténtico coñazo gestionar FreeRadius y todos sus parámetros, así que, que mejor que un sencillo entorno web para la gestión de usuarios y visualización de actividad y log del sistema. Esta utilidad es dialup_admin. No me detendré en su instalación ya que creo que se sale fuera de este artículo y, además, es una sencilla aplicación web sin mucha dificultad.

 radiusweb.gif

Con esta herramienta será mucho más sencillo crear usuarios y sus propiedades y hacer un seguimiento de los que están conectados, periodos de conexión que han tenido, etc.

Estableciendo la VPN desde Windows

Sencillísimo. Desde Conexiones de red, se crea una conexión nueva, escoges Conectarse a la red de mi lugar de trabajo y prácticamente sólo queda introducir el host o IP de tu sevidor VPN y  conectarse. Recomiendo desmarcar la opción Usar puerta de enlace predeterminada en la red remota en las propiedades de esta nueva conexión, Funciones de red, Protocolo TCP/IP, Opciones avanzadas, de otro modo todo el trafico de Internet normal lo harás a través de la VPN.

Si todo va bien conectarás a tu servidor y tendrás acceso al mismo como si estuvieses en tu propia red local.

Estableciendo la VPN desde Linux

Desde Linux es un pelín más complicado ya que, como casi siempre, hay que hacer la configuración a mano. Existe una utilidad gráfica que permite crear las conexiones de un modo similar a Windows, pero prefiero explicar cómo hacerlo desde la consola por si tu máquina no tiene entorno gráfico.

[osus@servidor ~]# cat /etc/ppp/options.pptp
lock
noauth
refuse-eap
refuse-chap
refuse-mschap
nobsdcomp
nodeflate

Ahora indicamos el usuario y la contraseña que se utilizará para conectar. IdentificadorRed será el nombre que le demos a la conexión, puede ser lo que quieras.

[osus@servidor ~]# cat /etc/ppp/chap-secrets
usuario IdentificadorRed clave *

Ahora creamos la configuración para la conexión que vas a crear con el IdentificadorRed que comentamos antes. En TUIP debes poner el host o ip de tu servidor VPN.

[osus@servidor ~]# cat /etc/ppp/peers/IdentificadorRed
remotename IdentificadorRed
linkname IdentificadorRed
ipparam IdentificadorRed
pty "pptp TUIP --nolaunchpppd "
name usuario
require-mppe
require-mschap-v2
refuse-eap
refuse-pap
refuse-chap
refuse-mschap
#demand
holdoff 5
persist
maxfail 0
ipcp-accept-remote
ipcp-accept-local
noauth
192.168.3.1:192.168.3.254

Aquí hay dos opciones interesantes:

  • persist: vuelve a crear el tunel automáticamente si se cortase por alguna razón de manera que permanece siempre activo.
  • demand: crea automáticamente el tunel cuando se accede a la IP del servidor o a alguna otra que esté enrutada a través de este tunel,  mientras no se necesite permanece inactivo. Obviamente no debe estar en modo persist, de otro modo estará siempre activa.

Finalmente creamos un sencillo script de arranque para poder lanzarla automáticamente o simplemente para no tener que recordar los parámetros.

[osus@servidor ~]# cat /etc/init.d/IdentificadorRed
#!/bin/sh
  case "$1" in
      start)
            echo -n "Iniciando VPN IdentificadorRed "
            echo
            touch /var/lock/subsys/pptpd
            /usr/sbin/pppd call IdentificadorRed logfd 1 updetach &
            ;;
      stop)
            echo -n "Parando VPN IdentificadorRed: "
            echo
            kill -TERM `head -n 1  /var/run/ppp-IdentificadorRed.pid`
            ;;
      *)
            echo "Usage: $0 {start|stop}"
            exit 1
  esac
exit 0

Y eso es todo amigos. Me ha costado bastante más de lo previsto escribir este artículo ya que a medida que lo iba redactando me iba saltando nuevos recuerdos sobre detalles que se deberían nombrar.

Tened en cuenta que mi experiencia con PPTP se remonta cinco años atrás con lo que puede ser que algún detalle haya cambiado los últimos años. Después de aquel primer servidor VPN el año pasado migramos el sistema operativo a Centos5 y al configurar de nuevo el servidor VPN dejamos prácticamente todos los parámetros como estaban. No creo que tengáis ningún problema a la hora de solucionar algún pequeño detalle que pueda surgir.