"Después del juego es antes del juego"
Sepp Herberger

viernes, 3 de octubre de 2014

Interwrite Device Manager y los $HOME inaccesibles.

El problema.

Mi amiga y compañera Cristina nos comentó que había visto en los logs de algunos ordenadores de aulas unos extraños mensajes acerca un acceso no autorizado a "/home/<usuario>". Posteriormente se había dado cuenta de que sólo sucedía en los equipos que tenían una pizarra digital Interwrite conectada, pizarra que nos ha dado muchos problemas de calibración desde siempre, ya que parecía que olvidaba los datos de calibración entre reinicio y reinicio. Había que aclarar dicho misterio.

Parrafada sobre pizarras digitales.

Me encantan las pizarras digitales, me hubiera vuelto loco por tener una cuando daba clase pero ya me he rehabilitado y no practico ese vicio. Aún así me las veo con ellas ya que en el trabajo tenemos 3 modelos distintos de pizarra: Interwrite, SmartBoard 480 y SmartBoard 680.

Las SmartBoard tienen un problema: reciben la alimentación por USB, por lo que el cable debe ser corto o usar un cable USB activo, que no siempre funciona bien. No tiene porqué estar el ordenador del profesor a medio metro de la pizarra, digo yo.

La SmartBoard 680 es táctil, con una precisión muy buena. La SmartBoard 480, por desgracia, no es táctil. Es un tablero que tiene ocultas dos cámaras en las esquinas superiores y triangulan la posición del obstáculo (el lápiz o el dedo, incluso el codo si alguien se acerca sin querer a la pizarra). La triangulación la hace el driver propietario de la pizarra, que recibe en bruto un flujo apabullante de datos por el USB con lo que presumiblemente son las imágenes de ambas cámaras, las analiza y determina la posición del puntero. Eso hace que su precisión sea regulera y que el retraso al trazar en pantalla respecto al lápiz sea considerable.

La Interwrite tiene alimentación independiente y apenas tiene retraso en el trazado ya que es "táctil": necesita un lápiz magnético especial con una pila soldada que cuando se avería no es fácil de cambiar. Tampoco era para tanto, ya podían haber puesto pilas recargables normales. No creo que vayan a hacerse millonarios vendiendo lápices nuevos (si los venden, porque a mi me ofrecen el pack completo de dos lápices mas base de carga, cuando yo quiero solamente un lápiz). A su favor puedo decir que puntas e interruptores laterales de repuesto te los envían gratis sin problema los amables chicos de Artigraf, el distribuidor en España.

La causa.

Antes de nada, aclarar que he estado trabajando con Interwrite Workspace Linux 6.0.417.70042. Creo que ha salido alguna versión más después, pero todo lo que diga es válido también para ella. Idem para versiones anteriores.

Dicen los viejos del lugar que Interwrite antes tenía un driver para el núcleo y/o un módulo para las X, de tal manera que la detección de las pulsaciones se manejaban en esos niveles inferiores. En la actualidad lo que trae es un programa en Java (!x#@) que se ejecuta en el inicio de sesion (desde /etc/xdg/autostart), llamando al script:

/opt/eInstruction/DeviceManager/LinuxLauncher.sh.

El script tiene esta pinta:

#!/bin/bash

#This script is used for
#creating links.

#Get the location of this script file.
target=$(exec stat -c %N $0)

#Parse the contents of target so that it is in a suitable
#form to be used as an argument for the command "cd"

#Determine whether or not this script is being launched from a symbolic link
grep -e "->" <<!FUNCKY!
$target
!FUNCKY!

if [ $? -eq 0 ]
then
#Symbolic link
parse_target=${target#*-> */}
else
#Absolute path i.e. "/opt/eInstruction/DeviceManager/"
parse_target=${target#*/}
fi

#Add "/" back to the beginning of the path
parse_target="/"$parse_target

#Remove the script name from the path
path=${parse_target%/*}

if [ ${#path} -eq 0 ]
then
#The script is being launched from it's working directory so, the
#path is equal to ".".  Example, "./Launch_InterWrite"
path="."
fi

#Set the working directory
cd $path

#Launch the application..
sudo ./jre/bin/java -Djava.library.path=. -classpath ./dm.jar:./*:./axis2-1.5/* einstruction.dm.ui.Main

Como vemos, el script acaba triunfalmente con la línea:

sudo ./jre/bin/java -Djava.library.path=. -classpath ./dm.jar:./*:./axis2-1.5/* einstruction.dm.ui.Main

En esta línea se carga el Device Manager, dm.jar para los amigos, el encargado de comunicar la pizarra con el entorno de escritorio (es decir: interpreta las coordenadas x-y que le llegan de la pizarra y mueve el puntero del ratón a su equivalente en pantalla). Como este programa necesita acceder a los puertos USB a bajo nivel para comunicarse con la pizarra tiene que correr como root, por lo que durante la instalación del software hay que modificar /etc/sudoers (obsérvese que se ejecuta con sudo) para que funcione con permisos de root automáticamente:

ALL ALL=(ALL) NOPASSWD : /opt/eInstruction/DeviceManager/jre/bin/java -Djava.library.path\=. -classpath ./dm.jar\:./*\:./axis2-1.5/* einstruction.dm.ui.Main
Defaults env_keep += "DISPLAY XAUTHORITY XAUTHLOCALHOSTNAME"

Como vemos, damos permisos para que se ejecute como root sin pedir contraseña a:

/opt/eInstruction/DeviceManager/jre/bin/java -Djava.library.path\=. -classpath ./dm.jar\:./*\:./axis2-1.5/* einstruction.dm.ui.Main

Siendo:

  • /opt/eInstruction/DeviceManager/jre/bin/java: la máquina virtual Java, ya que Interwrite trae su propia máquina en la instalación de la aplicación. Una decisión acertada vista la deriva de Java en los últimos años, con versiones incompatibles con las anteriores.
  • classpath ./dm.jar: el Device Manager en sí mismo.
  • El resto son parámetros para definir classpath y la clase de arranque.

Anécdota: en tiempos pasados el sudoers propuesto por Interwrite era:

ALL ALL=(ALL) NOPASSWD : /opt/eInstruction/DeviceManager/jre/bin/java
Defaults env_keep += "DISPLAY XAUTHORITY XAUTHLOCALHOSTNAME"

Lo cual era una temeridad, ya que daba permisos de root a cualquier cosa lanzada desde dicho Java. Cualquiera podría hacer un programita destructivo, compilarlo a bytecodes y lanzarlo con dicho intérprete para que se ejecutase como root con total impunidad.

Sigamos avanzando. El dm.jar escribe los datos de la calibración de la pizarra en el $HOME del usuario que inicia sesión, concretamente en dos ubicaciones:

  • /home/<usario>/eInstruction/Device Manager/.eInstructionDeviceManagerPreferences.xml
  • /home/<usuario>/.eInstructionDeviceManagerDevices.xml

Y he aquí el origen del problema descrito por Cristina:

  1. El $HOME de nuestros usuarios no es "/home/<usuario>", es "/home/profesor/<usuario>" o "/home/alumnos/<usuario>".
  2. El $HOME de nuestros usuarios está centralizado en un servidor NFS. El usuario root local de la máquina (recordemos que dm.jar se ejecuta con sudo) no tiene permisos de acceso, por seguridad, a las carpetas de sistemas de ficheros montados por NFS.

Por ambas razones salían esos misteriorosos mensajes de error en los logs: intentaba acceder a lo que no existía y además, no tenía permiso para hacerlo.

¿Cómo determina el dm.jar la ubicación del home del usuario?. Husmeando un poco: si abrimos dm.jar con jd-gui para verlo descompilado nos encontramos con que el método einstruction.dm.deviceap.LinuxEnvironment.getSudoUserHome() hace:

str = File.separator + "home" + File.separator + getSudoUser() + File.separator;

Estupendo, presupone que la ruta del home del usuario es "/home/<usuario>" siempre sin mayor consideración.

Cuando el Device Manager intenta guardar datos de la calibración de la pizarra en "/home/<usuario>", no rula porque:

  • No está ahí el home.
  • Si estuviera, no tendría permisos para escribir en él, al estar montado sobre NFS y ejecutarse dm.jar con el usuario root local.

Para mas INRI, si hiciésemos un apaño para que pudiese escribir allí tendríamos un problema añadido: un mismo usuario puede trabajar con varias pizarras en distintas aulas. Al calibrar una de ellas y teniendo el home centralizado en el servidor NFS, sobrescribiría los datos de calibración de la anterior. Tendría que calibrar la pizarra cada vez que entra en un aula.

Una solución del estilo "arrimo el piano al taburete" consiste en no calibrar ninguna pizarra y mover el cañón de vídeo de cada aula para que se ajuste a la calibración por defecto de la pizarra. Con ese sistema se ha estado funcionando mucho tiempo.

La solución.

Bueno, pues la solución que se me ocurrió pasa por 2 medidas:

  • Que los datos de calibración se guarden en el PC, no en el home del usuario. Y dentro de cada PC, en carpetas separadas para cada usuario, ya que además de dichos datos se guardan también configuraciones personalizadas del software de Interwrite.
  • Que dm.jar tome los datos para leer la configuración de la ubicación local dentro del PC, y no de "/home/<usuario>" escrito piñón en el dm.jar.

Para ello, creo en cada PC una carpeta: "/var/home/pizarras/$USER" donde irá la configuración de la pizarra para dicho usuario, en una ubicación local accesible por dm.jar aun cuándo lo ejecute el usuario root.

Esta carpeta se crea dentro de LinuxLauncher.sh, al iniciar sesión, que queda así:

.........
.........
#Set the working directory
cd $path

#Crea carpeta para configuración local de usuario
mkdir -p /var/home/pizarras/$USER

#Launch the application..
sudo ./jre/bin/java -Djava.library.path=. -classpath ./dm.jar:./*:./axis2-1.5/* einstruction.dm.ui.Main

Adicionalmente, hay que modificar dm.jar para que coja la configuración de /var/home/pizarras/$USER. Para ello hay que cambiar el método anteriormente comentado (einstruction.dm.deviceap.LinuxEnvironment.getSudoUserHome()) y dejarlo:

str = File.separator + "var/home/pizarras" + File.separator + getSudoUser() + File.separator;

Una captura de pantalla de como queda el método:

jd-gui

Para modificar el .jar utilizo:

  • jd-gui: para ver el contenido.
  • Class Editor 2.23: para modificar los ficheros .class (en este caso sólo modifico un string, cambio "home" por "var/home/pizarras", por lo que no hay que descompilar y recompilar de nuevo).
  • Y por último reempaqueto los class una vez modificados con el comando:

jar cf dm.jar *

Obteniendo un dm.jar tuneado para tomar y guardar la configuración en /var/home/pizarras/$USER.

Aquí van enlaces a ambos ficheros ya modificados:

Simplemente hay que descargarlos y colocarlos en /opt/eInstruction/DeviceManager/, sobreescribiendo los que trae por defecto Interwrite. Cerramos sesión y volvemos a entrar y ya está. En "/var/home/pizarras/..." irán apareciendo las carpetas con las configuraciones particulares de cada usuario.

(Des)agradecimientos.

Despues de informar de todo esto a eInstruction (los fabricantes de Interwrite) y proponer incluso soluciones mas elegantes, ya que ellos tienen el código fuente de dm.jar y pueden implementarlas sin problemas, me apena decir que no me han dicho ni mu. Un 0 por ellos.

Ahi va otra para los silenciosos chicos de Interwrite: si tienes el software instalado en una máquina amd64 y en el Device Manager le das a "Restart" para reiniciarlo, falla y se cierra. Falta poner el fichero libudev.0.so en /lib32.

Vámonos.

3 comentarios:

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Realmente no es nada fácil cambiar la pila, tengo dos lápices de una interwrite que no funcionan, creo que por la pila lo pongo en el cargador se enciende el led de carga y se apaga, y no encuentro la manera de extraer el pasador metálico sin estropear el lápiz

    ResponderEliminar
    Respuestas
    1. Efectivamente, están diseñados para ser de usar y tirar cuando la pila (que cuesta menos de 1 euros) llega al final de su vida util.

      Aquí desmonto uno http://2tazasdelinux.blogspot.com.es/2014/10/force-brute-attack-un-lapiz-de-la.html rompiendo todo simplemente por saber como está montado.

      Es un timo dirigido a que compres un lapiz+cargador nuevo (no se venden sueltos) por 90 euros simplemente por romperse una pila.

      Alguna vez me ha contado algún compañero de algún manitas que ha logrado sacar la pila, pero no sé como lo han conseguido sin romper el cableado hacia el circuito.

      Eliminar