La regla @page y estilos para impresión en CSS

Si bien es cierto que cada día es menos habitual imprimir cosas desde la web muchas veces puede interesarnos tener un formato específico para la impresora que nos permita ocultar menús. A la hora de meter un formato sólo para la impresora procederemos como con cualquier CSS normal, pero añadiendo en la etiqueta media=»print», como en el ejempo:

<!-- Cargando un CSS externo -->
<link href="paraimprimir.css" type="text/css" media="print" />
<!-- Insertando CSS en la cabecera -->
<style type="text/css" media="print">
/*aquí lo que corresponda*/
</style>
<!--También puede usarse esta sintaxis-->
<style type="text/css"> 
  @media print { 
      /* reglas css */ 
  } 
<style>

Con estos estilos, por ejemplo, podemos hacer que un elemento aparezca en todas las páginas definiéndolo como fixed u ocultar lo que no tenga sentido mostrar en la impresión con display:none.

El uso de media print genera una caja CSS distinta a la de pantalla (media screen) para alojar los contenidos, a pesar de que su boxmodel será el mismo. Esta caja se conoce como page y podremos acceder a ella para modificar sus características mediante el selector @page.

Dicho selector nos permite, por ejemplo, definir la orientación de la página entre normal y apaisada:

/*Orientación vertical*/
@page {size: portrait;}
/*Orientación apaisada*/
@page {size: landscape;}

También podemos utilizar clases ligadas a la regla page, por ejemplo imaginemos que queremos imprimir un informe con todas las páginas en vertical pero con las tablas y las gráficas apaisadas en una hoja aparte:

@page apaisada {size: landscape;}
table {page: apaisada; page-break-before: right;}
/*las gráficas las diferenciaremos con una clase CSS*/
.poll {page: apaisada; page-break-before: right;}

A la hora de definir el tamaño de la página debemos tener en cuenta que su total será la suma del contenido más los bordes (margin y padding), y podemos definirlo con el atributo size. Podemos utilizar valores numéricos (porcentajes, puntos, centímetros, milímetros), excepto píxeles porque pertencen al ámbito de la pantalla, o utilizar los diversos valores que definen tamaños standares de papel (A4, A3, letter). En caso de no especificar nada el tamaño será auto, que es el valor por defecto. Lo suyo es utilizar las medidas de los textos en pt y el alto y ancho de página en cm, mm o in (pulgadas), piensa que trabajamos sobre un medio físico y no sobre una pantalla que puede tener diversos tamaños y resoluciones.

@page {
  size: 8.5in 11in;  /* ancho y alto en pulgadas */  
}
@page {
  size: 21cm 190mm; /*medidas en tamaño*/
}
@page {
  size: A4; /*standar A4*/
}

Disponemos además de las pseudoclases :first, :left y :right que nos permitirán perfeccionar nuestra maquetación para la impresión a dos caras y pudiendo usar unas reglas específicas para la primera página.

/*Para la primera página usaremos un margen superior e izquierdo muy grande*/
@page :first {
  margin-top: 6cm;
  margin-left: 7cm;
}

/*definimos los márgenes de la página izquierda*/
@page :left {
  margin-right: 4cm;
  margin-left: 3cm;
}

/*y para la derecha invertimos los valores
para que la impresión a doble página
quede bien*/
@page :right {
  margin-right: 3cm;
  margin-left: 4cm;
}

Y también podemos definir cuántas líneas de un párrafo queremos dejar al final de una página como mínimo o al principio, con la intención de no dejar líneas huérfanas o viudas. Para ello disponemos de las propiedades orphans y widows (que significan huérfanas y viudas en inglés).

@page{
  orphans:5;
  widows:4;
}

¿Cómo funcionaría el ejemplo de arriba? Imaginemos que al final de una página caben 15 líneas. Si tenemos justo 15 o menos entrarán en esa página. Si tenemos 17 entonces, para no violar la regla, se repartirían 13 en la página 1 y 4 en página 2 (para cumplir el mínimo), y su tuviéramos 19 o más no habría problema: 15 en la primera y el resto en la siguiente.

Finalmente hablaremos de los saltos de página, que ya vimos en uno de los ejemplos de arriba. Y es que los elementos de bloque pueden llevar asociado un salto de página, que puede ir antes, después o dentro del elemento. Para ponerlo antes usaremos page-break-before y para hacerlo después iría page-break-after. Para hacerlo dentro usaremos page-break-inside, que tiene la particularidad de que se puede heredar del elemento contenedor mientras que los otros dos no. Para page-break-after y page-break-before puede haber cuatro valores:

  • always: fuerza siempre un salto de página antes o después del elemento, según corresponda
  • avoid: evita siempre un salto de página antes o después del elemento, según corresponda
  • left: fuerza los saltos de página que sean necesarios para que la siguiente página sea compuesta como una página izquierda
  • right: fuerza los saltos de página que sean necesarios para que la siguiente página sea compuesta como una página derecha

Y creo que con esto hemos recorrido los puntos principales el tema de los estilos CSS para impresión.

Reparar el Grub en Ubuntu

Cuando yo estudiaba el desaparecido FP de Desarrollo de Aplicaciones Informáticas (hoy convertido en DAW y DAM en busca de una mayor especialización) teníamos una asignatura genérica de Sistemas y Redes el primer año. Era un batiburrillo donde veías de todo un poco, y tuvimos la suerte de tener un profesor muy bueno, un auténtico crack (una pena que dejara la docencia para trabajar en otros proyectos) que nos eseñó mucho. Curiosamente en mi trabajo he tenido que hacer mucho cacharreo en los últimos meses, y estas pequeñas lecciones me han servido de mucho.

Una de las cosas que recuerdo haber tenido que hacer en un examen era reparar el GRUB de un Ubuntu (9.04 por aquel entonces), ejercicio fundamental porque era el primero y como no fueras capaz de hacerlo no podías seguir con el examen.

Para este proceso vamos a necesitar una distribución Linux que podamos arrancar en modo Live (desde un disco o pendrive). Una vez cargada lanzamos un terminal y a cacharrear. Empezamos por averiguar en qué partición está instalado el SO haciendo sudo fdisk-l. Para el ejemplo vamos a utilizar sda1. El siguiente paso es «montar» el disco duro de nuestro equipo en esta distribución live:

#primero montamos el disco duro del SO
sudo mount /dev/sda1 /mnt

#luego el resto de dispositivos
sudo mount –bind /dev /mnt/dev

Tras esto debemos crear una jaula chroot, un comando que nos permite acceder como root al sistema de archivos del SO instalado:

sudo chroot /mnt

Y ya en esta situación lo único que nos queda por hacer es instalar el GRUB en el Master Boot Record para que se ejecute al arranque.

grub-install –recheck /dev/sda

Existen otras soluciones, como Super Grub Disk, sencillas y rápidas, pero la descrita arriba me ha funcionado en la mayoría de los casos sin problema.

Incluir un equipo Ubuntu 14.04 en un dominio Active Directory de Windows

En este sencillo tutorial vamos a ver cómo incluir un equipo con Ubuntu (14.04 en el ejemplo) dentro de un dominio Active Directory (servidor con Windows 2k8) utilizando PowerBroker IS Open Edition, que es el software que ha «jubilado» a Likewise-Open. Los datos que usaremos para el ejemplo (y que debéis cambiar por los que correspondan en vuestro caso) son los siguientes:

Dominio: DONNIE.local
DC: SERVIDOR.DONNIE.local
IP: 192.168.100.2

Antes de comenzar mirad que la ip de vuestro equipo esté en el mismo rango que la ip del servidor. Una vez confirmado esto (si no, configuradlo) comenzaremos.

Descargamos el script correspondiente desde la web oficial y le damos permisos de ejecución con un chmod +x. Luego nos situamos en la carpeta donde lo guardamos y lo ejecutamos (cambiad el nombre del archivo por el de la versións que hayáis descargado vosotros):

sudo ./pbis-open-8.0.1.2029.linux.x86_64.deb.sh

Tras esto nos unimos al dominio (cambia administrator y el nombre del domino por los que procedan):

sudo domainjoin-cli join DONNIE.local administrator

O si lo quieres desactivando ssh por defecto:

sudo domainjoin-cli join --disable ssh DONNIE.local administrator

En el siguiente paso hay que hacer un pequeño cambio en un archivo de configuración. En el archivo /etc/pam.d/common-session debes cambiar la línea que pone session sufficient pam_lsass.so por esta: session [success=ok default=ignore] pam_lsass.so.

Y desde la consola configuramos más datos para el acceso, recordad cambiar el dominio y el grupo de usuario por el que corresponda en vuestro caso:

sudo /opt/pbis/bin/config UserDomainPrefix DONNIE
sudo /opt/pbis/bin/config AssumeDefaultDomain true
sudo /opt/pbis/bin/config LoginShellTemplate /bin/bash
sudo /opt/pbis/bin/config HomeDirTemplate %H/%U
sudo /opt/pbis/bin/config RequireMembershipOf "DONNIE\\Usuarios" 

Ahora nos queda configurar lightdm para activar el login manual:

sudo vi /usr/share/lightdm/lightdm.conf.d/50-unity-greeter.conf

#Añade las siguients líneas (sin la marca de comentario)
#allow-guest=false
#greeter-show-manual-login=true

##Nota, en Lubuntu 14.04 el archivo a cambiar será  60-lightdm-gtk-greeter.conf 

Finalmente vamos a darle permisos de sudo al usuario del dominio (ojo, si procede) editando el archivo correspondiente con un sudo vi /etc/sudoers y añadiendo los datos que sean necearios siguiendo los ejemplos contenidos en el propio documento.

Tras esto, si reiniciamos el equipo, deberíamos ya poder hacer login con el usuario del Active Directory de Windows.

Windows Explorer de repente no funciona como cliente FTP en Windows 7

Era complicado ponerle un título a esto, pero es algo que me encontré por la mañana. Un cliente estaba trabajando con una aplicación web para Internet Explorer que debía abrir una carpeta de un servidor FTP, funcionaba bien en todos los equipos de una red menos en uno (pulsabas un enlace de la aplicación y se abría en Explorador de Windows en la carpeta FTP), así que fuera lo que fuera era algún problema local de ese equipo. Probé de todo pero nada parecía funcionar, aunque poco a poco tachaba cosas de la lista hasta llegar a una conclusión: por algún motivo Windows Explorer no quería funcionar como cliente FTP.

Probé cambiando varias entradas en el registro, probé diversas configuraciones y al final, tras mucho darle a Google encontré esta solución: crear una entrada en el registro de Windows. La cosa consiste en copiar el texto de abajo y guardarlo en un archivo con la extensión .reg, y luego ejecutar dicho archivo para que la entrada se añada. Con esto la cosa volvió a funcionar perfectamente.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\ftp]
@="URL:File Transfer Protocol"
"AppUserModelID"="Microsoft.InternetExplorer.Default"
"EditFlags"=dword:00000002
"FriendlyTypeName"="@C:\\Windows\\system32\\ieframe.dll,-905"
"ShellFolder"="{63da6ec0-2e98-11cf-8d82-444553540000}"
"Source Filter"="{E436EBB6-524F-11CE-9F53-0020AF0BA770}"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\ftp\DefaultIcon]
@=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,75,00,72,00,\
6c,00,2e,00,64,00,6c,00,6c,00,2c,00,30,00,00,00
[HKEY_CLASSES_ROOT\ftp\shell]
@="open"
[HKEY_CLASSES_ROOT\ftp\shell\open]

¿Y por qué había dejado de funcionar el Windows Explorer como cliente FTP por defecto? Pues tras instalar Google Chrome y definirlo como navegador por defecto este había cambiado también la configuración del cliente FTP por defecto del equipo. Tras la ejecución del cambio en el registro todo volvió a funcionar con normalidad.

Clonar un disco duro .vdi de Virtual Box

Si bien cuando estudiaba hacíamos los clonados de disco en VirtualBox tal como se harían en una máquina real, tirando del Clonezilla, existe una forma más práctica de lograrlo.

En Windows la cosa es lanzar una consola de comandos (cmd) y situarnos en la carpeta en la que hubiéramos instalado VirtualBox. Una vez allí basta con usar el siguiente comando:

VBoxManage.exe clonehd ../rutadeldisclonar.vdi ../rutadelresultado.vdi 

Obviamente tenéis que cambiar ../rutadeldisclonar.vdi por la ruta real del disco .vdi que queréis clonar, y tres cuartos de lo mismo con ../rutadelresultado.vdi que es donde se creará el nuevo archivo.

En el caso de Linux la cosa es prácticamente igual: lanzáis el terminal, os ubicáis en la carpeta donde se instaló el VirtualBox y desde allí lanzáis

sudo VBoxManage clonehd ../rutadeldisclonar.vdi ../rutadelresultado.vdi 

Como antes, cambiando las rutas fictias por las que correspondan. Y listo, disco virtual clonado por si se os corrompe el que está en uso.

Esteganografía en Linux sin instalar software adicional

Ya en el pasado hablamos de software para ocultar archivos dentro de otros (la llamada esteganografía) en Linux, como SilentEye o Outguess. Pero realmente es posible ocultar un archivo dentro de una imagen sin necesidad de software extra, valiéndonos sólo de la consola.

Lo primero es comprimir el archivo que queremos ocultar, mismamente con la herramienta nativa de compresión de Ubuntu lo transformamos en un archivo .zip (y si queréis un extra de seguridad podéis añadirle una contraseña). Para el ejemplo lo llamaremos secreto.zip mismamente.

Ahora necesitamos un archivo de imagen, para el ejemplo tendremos una que se llamará base.png, y lanzamos un terminal.

La idea es simple: concatenamos el archivo zip a continuación de la imagen generando un nuevo archivo que tendrá la misma extensión que la imagen base (si la extensión de la imagen fuera distinta no se verá, por ejemplo si usamos un png de base pero de salida lo llamamos .jpg). Rápidamente lo entenderéis con un ejemplo de la operación:

cat base.png secreto.zip > resultado.png

A primera vista el archivo resultado.png es la misma imagen que base.png, si bien si miráis sus propiedades veréis que tamaño del nuevo archivo es más grande (porque lleva concatenado el archivo comprimido, lógicamente).

¿Cómo accedemos al archivo oculto? Pues simplemente renombrando el archivo resultado.png y cambiando la extensión de la imagen por la del archivo comprimido que usáramos (en este caso pasaría a ser resultado.zip). Tras este cambio si la abrís con el gestor de archivos comprimidos podréis acceder al archivo que habéis ocultado.

Tal vez ofrezca menos seguridad y optimización que el software específico para esteganografía, pero para un apuro puede ser una solución válida.

Llamar a procedimientos almacenados en PHP

Una entrada que llevaba mucho tiempo en el TODO list, sobre trabajar con procedimientos almacenados con PHP.

Para empezar vamos a plantear un escenario en el que tenemos tres procedimientos almacenados en MySQL (dos selects y un insert) contra una tabla llamada productos, que tiene tres campos (id como clave primaria, descripcion con una descripción del producto, precio con un valor numérico decimal).

El SP de la primera select, que nos devuelve la descripción y el precio del producto según su id, sería este:

CREATE PROCEDURE getProducto(IN id_val INT) 
  BEGIN 
    SELECT descripcion, precio from productos WHERE id = id_val; 
  END;

El SP de la segunda select, que nos devuelve la descripción y el precio todos los productos, por lo que no recibe parámetros:

CREATE PROCEDURE getAllProductos() 
  BEGIN 
    SELECT descripcion, precio from productos ; 
  END;

Y el sp del insert sería este, al que le pasamos los tres valores a meter:

CREATE PROCEDURE addProducto(IN id_val INT,descripcion_in varchar(50), precio_in float) 
  BEGIN 
    INSERT INTO productos VALUES (id_val, descripcion_in, precio_in);
  END;

Bueno, ya tenemos nuestro escenario, entonces ¿cómo hacemos para llamar a estos procedimientos desde nuestro código php? Pues simplemente lanzamos una consulta en la que llamamos a la instrucción CALL de MySQL para que ejecute el procedimiento almacenado que le digamos con los parámetros que tocan.

/*Empezamos con el procedimiento de inserción*/
/*Lo primero es crear un objeto mysqli*/
$mysqli = new mysqli("servidor", "usuario", "pass", "db");

/*Y llamamos al procedimiento para hacer la inserción*/
/*Si falla imprimimos el error*/
if (!$mysqli->query("CALL addProducto(1001, 'Nueces de Macadamia', 12.23)")) {
    echo "Falló la llamada: (" . $mysqli->errno . ") " . $mysqli->error;
}

La propia función query del objeto mysqli nos devolverá TRUE si no viene con errores y FALSE si pasa algo inesperado.

¿Y qué pasa con la recuperación de datos? Ahí nos vamos a un terreno más pantanoso. Para una consulta con un solo resultado no es problema:

/*Empezamos con el procedimiento de recuperación de una fila*/
/*Lo primero es crear un objeto mysqli*/
$mysqli = new mysqli("servidor", "usuario", "pass", "db");

/*Y llamamos al procedimiento para recoger los datos*/
/*Si falla imprimimos el error*/
if (!($res = $mysqli->query("CALL getProducto(1001)"))) {
    echo "Falló la llamada: (" . $mysqli->errno . ") " . $mysqli->error;
}

/*E imprimimos el resultado para ver que el ejemplo ha funcionado*/
var_dump($res->fetch_assoc());

Pero claro, en este caso podemos hacerlo así porque esperamos un solo resultado, pero si esperamos varias filas la cosa es distinta ya que mysqli->query() devuelve sólo el primer conjunto de resultados. Tenemos dos alternativas para esta situación: mysqli_real_query() o mysqli_multi_query(). En el ejemplo usaremos la segunda, y veremos también la forma de recorrer este resultado:

/*Empezamos con el procedimiento de recuperación de una fila*/
/*Lo primero es crear un objeto mysqli*/
$mysqli = new mysqli("servidor", "usuario", "pass", "db");

/*Y llamamos al procedimiento para recoger los datos*/
/*Si falla imprimimos el error*/
if (!$mysqli->multi_query("CALL getAllProductos()")) {
    echo "Falló la llamada: (" . $mysqli->errno . ") " . $mysqli->error;
}

/*Ahora con este bucle recogemos los resultados y los recorremos*/
do {
    /*En el if recogemos una fila de la tabla*/
    if ($res = $mysqli->store_result()) { 
        /*Imprimimos el resultado de la fila y debajo un salto de línea*/
        var_dump($res->fetch_all());
        printf("\n");
        /*La llamada a free() no es obligatoria, pero si recomendable para aligerar memoria y para evitar problemas si después hacemos una llamada a otro procedimiento*/
        $res->free();
    } else {
        if ($mysqli->errno) {
            echo "Store failed: (" . $mysqli->errno . ") " . $mysqli->error;
        }
    }
} while ($mysqli->more_results() && $mysqli->next_result());
/*El bucle se ejecuta mientras haya más resultados y se pueda saltar al siguiente*/

Añadiré a esto que también es posible utilizar las llamadas a procedimientos almacenados usando sentencias preparadas, procediendo del modo habitual a la hora de pasar los parámetros.

¿Cómo conocer nuestra IP externa?

¿Conocéis ese momento incómodo en que tenéis que hacer una conexión para ayudar a alguien por remoto y el TeamViewer os mira mal y os dice «nanay, chaval, esto es uso comercial descarado«? Tras el pánico inicial toca recurrir a VNC, que es verdaderamente libre (otra opción sería pagar TeamViewer o buscar alguna alternativa más, pero asumamos que no pensáis soltar un duro). ¿Problemilla? Que para realizar una conexión por VNC necesitamos (además de abrir algunos puertos) conocer nuestra IP externa. Un ifconfig en Linux o un ipconfig en Windows sólo nos dará nuestra ip dentro de la red local, y en este caso necesitamos saber qué ip usamos hacia el exterior.

Una solución rápida y fácil es recurrir a alguna de estas webs, que directamente nos darán el resultado:

Evitar la desconexión automática de las unidades de red en Windows 7

Esta mañana me encontraba una incidencia de un cliente a primera hora, se quejaba de que un programa que utilizaba debía cargar una radiografía desde una unidad de red compartida, pero que los equipos, Windows 7 Home, tras un rato fuera de uso dejaban de tener accesibles estas unidades. Basta un simple doble click para activarlas otra vez, pero eso implica salir del programa y repetir todas la operación, una cuestión engorrosa.

¿Existe una solución para esto? Sí, desde luego. Esta desconexión automática ocurre a los 30 minutos y este tiempo puede modificarse, ampliándose, reduciéndose y hasta desactivándose. Lo que tenemos que hacer es abrir la línea de comandos con permisos de administrador (Inicio-> Todos los programas-> Accesorios-> Click derecho sobre Símbolo del sistema, y ahí elegir Ejecutar como administrador.)

¿Lo podemos cambiar para que en vez de cada media hora ocurra cada hora y media? Basta poner el número de minutos en 90:

net config server /autodisconnect:90

¿Y para desactivarlo?

net config server /autodisconnect:-1

Ojo, para desactivar la desconexión es -1, como pongo arriba, hay gente que lo pone a 0 y entonces no sólo no la desactivan sino que se desconectan a los pocos segundos

Puede haber más motivos para la desconexión, como que el servidor los desconecte por su propia configuración o que el equipo se ponga en suspensión por ahorro de energía (desconectando incluso la tarjeta de red) así que si tras aplicar este comando siguen desconectándose revisad las opciones de energía y la configuración del servidor. Esta solución es válida para los equipos con XP, Vista y Windows 7, no tengo información de si va igual en Windows 8, y no valdría para los anteriores a XP (si alguien los conserva).

«Poor Man’s VPN» sobre tunel SSH

Partamos de uno de estos supuestos:

  1. Estás conectado a una red de cuya seguridad no te fías, pero tienes un servidor seguro en algún sitio al que podrías conectarte.
  2. Estás en una red con conectividad limitada, pero puedes conectarte a un servidor con salida a internet

¿Tienes una opción para conectarte de forma segura? Sí. Puedes hacer una llamada «poor man’s VPN» sobre un tunel SSH ¿Cómo hacemos esto? Bueno, lo primero es que el servidor contra el que vamos a conectarnos tenga habilitados tanto la posibilidad de hacerse root de forma remota como la posibilidad de tunneling y la redirección de puertos. Todo esto lo podéis poner a funcionar en el archivo /etc/ssh/sshd_config, poniendo PermitRootLogin yes y PermitTunnel yes y luego reiniciando el servicio sshd (suponiendo que trabajéis con un servidor Linux, claro). Por seguridad podéis (deberíais) configurar unas claves SSH

Una vez esté todo eso habilitado toca empezar a configurar el cliente (vamos de nuevo con Linux) desde el que queremos conectarnos. Necesitamos saber la IP del servidor al que nos conectamos, en el ejemplo vamos a poner una ficticia que sería 83.112.23.4X. La máscara de red también tiene que ser la pertinente para vuestra configuración de conexión:

#abrimos un tunel ssh al servidor (vosotros poned la IP de vuestro server)
sudo ssh -f -w any 83.112.23.4X true

#asignamos una ip al tunel (ip local típica, en vuestro caso la que corresponda asegún la configuración)

sudo ifconfig tun0 192.168.0.2 192.168.0.1 netmask 255.255.255.0

Tras esto es necesario tener configurado el tunel en el servidor

# la misma asignación que en el server, pero invirtiendo las ip's de los dos puntos del tunel
sudo ifconfig tun0 192.168.0.1 192.168.0.2 netmask 255.255.255.0

# habilitamos el reenvío de paquetes
echo 1 > /proc/sys/net/ipv4/ip_forward

# habilitamos el enmascaramiento
sudo iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE

Finalmente toca acabar de configurar el enrutamiento en el cliente, en este ejemplo la puerta de enlace será 10.1.1.1, para vuestra configuración usad la que toque. La cosa es borrar la ruta previa a la puerta de enlace para usar la del tunel:

sudo ip route add 10.0.0.0/8 via 10.1.1.1
sudo ip route del default via 10.1.1.1 dev eth0
sudo ip route add default via 192.168.0.2

Ya sólo te queda comprobar si la conexión está funcionando bien.