Impresora virtual PDF en CUPS
Introducción
Dada la forma en la que trabaja CUPS añadirle una impresora virtual es bastante factible y más si se trata de una que produzca documentos en formato PDF.
El paquete cups-pdf es el indicado para ello, y ya que su configuración puede ser un poco delicada en el escenario que persigo voy a anotar aquí algunas cosas.
Configuración
Los pasos a seguir son:
- Instalar el paquete (lo que ya reinicia el propio servidor
CUPS
). - Crear una impresora virtual en el servidor:
- Asignarle el puerto de impresión (ó backend)
cupspdf:/
- Seleccionar como modelo de impresora
Generic/PostScript Color
.
- Asignarle el puerto de impresión (ó backend)
- Revisar la configuración en
/etc/cups/cups-pdf.conf
para adaptarla a nuestro entorno, especialmente las siguientes variables:- Grp: grupo de trabajo al que cambiar nada más comenzar la ejecución.
- Spool: ruta de trabajo, que ya está creada por el paquete.
- Out: ruta donde situar el documento
PDF
final. - AnonDirName: ruta donde guardar los
PDF
sin usuario reconocible ó asignados anobody
. - GhostScript: en Debian Etch el paquete cupsys depende de gs-esp, pero los documentos
PDF
no se construyen correctamente, en concreto desaparece la capa de texto, por lo que no se puede buscar texto dentro de ellos. Para solucionarlo basta con instalar el paquete gs-gpl, que puede coexistir perfectamente congs-esp
, y cambiar esta variable a/usr/bin/gs-gpl
. - GSTmp: este directorio debe existir y tener permisos de escritura
al menos para el grupo indicado en
Grp
. - GSCall: si empleamos papel en tamaño A4 es muy conveniente
añadir la opción
-sPAPERSIZE=a4
a esta variable, puesto que tantogs-esp
comogs-gpl
parecen usarLetter
como valor predeterminado. Al menos eso es lo que he podido sacar en claro tras echarle un vistazo al archivo de definicionesgs_statd.ps
donde se indica que la familia de tamaños por defecto corresponde aUS
y no a Europa.
Permisos de acceso
Añadidos
Juegos de caracteres
Este aspecto es necesario tenerlo en cuenta únicamente para los nombres de los
documentos PDF
generados, sobre todo si proceden de entornos Windows,
porque al final nos podemos encontrar con un buen número de documentos
untitled
ó con frases extrañas dada la sustitución masiva de caracteres
internacionales.
La variable DecodeHexStrings
(0) de la configuración permite que se empleen
caracteres UTF-8 en los documentos y se reconozcan como tales.
Si está desactivado (con valor cero ó comentada) una página web con un título
como RHD299H LG : LG España
podría resultar en un nombre de archivo como:
job_57281-RHD299H_LG___LG_Espa__a.pdf
en lugar de
job_57276-RHD299H_LG___LG_España.pdf
y es sólo un ejemplo fácil, otros títulos quedan irreconocibles.
Envío por correo electrónico
El paquete cups-pdf
incluye entre sus ejemplos un programa capaz de realizar
esta tarea, muy flexible y bien pensando. Su autor es Nickolay Kondrashov,
está escrito en Perl y bajo licencia GPL.
La instalación y puesta en marcha puede realizarse de esta forma:
- Descomprimir y copiar el archivo ejecutable
cups-pdf-dispatch.gz
a un directorio de ejecutables como/usr/local/bin
. - Instalar la configuración de
cups-pdf-dispatch.conf
a/etc/cups
y asegurarse de que tiene permisos de lectura para todo el mundo, puesto que el programa es invocado como el usuario final que recibe el documento. - Ajustar la configuración como describo en la siguiente sección.
- Incluir este programa en el funcionamiento de
cups-pdf
, añadiéndolo en la opciónPostProcessing
de su configuración.
Configuración de cups-pdf-dispatch
El archivo de configuración está escrito también en lenguaje Perl
por lo
que debe ser sintácticamente correcto.
En una primera aproximación, para ponerlo en marcha rápidamente, debemos retocar las siguientes variables:
$CHARSET
contiene el juego de caracteres que se emplea para el mensaje de correo.$FROM_MAILADDR
es la dirección de remite del mensaje.$MAX_ATTACHMENT_SIZE
está establecido en 5 Mb, pero puede ser necesario retocarlo porque si se sobrepasa el mensaje no se envía.$TRY_STRIP_JOB_IDS
activa el intento de supresión del prefijo que tanto Samba comocups-pdf
realizan.$REMOVE_SENT
determina si los archivos se borran tras enviarlos por correo.$GET_USER_MAILADDR_SUB
es una funciónPerl
que recibe como parámetro el nombre del usuario y debe retornar la dirección de correo electrónico a la que enviar el mensaje.
La última variable que menciono es la más compleja de todas puesto que se
trata de código Perl
y son necesarias nociones de programación en este
lenguaje.
Mi idea es que los archivos, una vez creados en la carpeta del usuario, sean
remitidos por correo electrónico al usuario siempre que éste así lo disponga.
Y la manera de hacerlo es crear un archivo en su carpeta llamado mailto
conteniendo en una línea su dirección de correo; en caso de que este archivo
no exista, ó el contenido esté comentado, no se enviará ningún mensaje. El
programa de Nickolay ya ha pensando en esto, y termina sin errores en caso de
no tener una dirección de envío.
Las modificaciones al archivo pueden verse aquí:
1 # cups-pdf-dispatch.conf 2 # Configuration file for cups-pdf-dispatch. 3 # This file is interpreted by perl. 4 5 6 # $GET_USER_MAILADDR_SUB 7 # Reference to a function which converts username to e-mail address. 8 # Arguments: username 9 # Returns: e-mail address 10 # 11 12 my $_user_realname = undef; 13 14 use locale; 15 16 $GET_USER_MAILADDR_SUB = 17 sub { 18 my $username = shift; 19 my $filename = "/var/spool/pdf/${username}/mailto"; 20 my $mailto = undef; 21 22 if (-r $filename) { 23 if (open(MAILTO,"< $filename")) { 24 while (<MAILTO>) { 25 chomp; 26 next if /^#/; 27 if (not $mailto) { 28 $mailto = $_; 29 last; 30 } 31 } 32 close(MAILTO); 33 } 34 else { 35 warn "could not open ${filename}: $!"; 36 } 37 } 38 else { 39 $mailto = sprintf("%s@%s", $username, hostname()); 40 } 41 42 if ($mailto =~ m{^([^<]+)\s<([^>]+)>}) { 43 $_user_realname = $1; 44 $mailto = $2; 45 } 46 47 return $mailto; 48 }; 49 50 # $GET_USER_REALNAME_SUB 51 # Reference to a function which converts username to user's realname (used 52 # when constructing To: header). 53 # Arguments: username 54 # Returns: user's real name 55 # 56 57 $GET_USER_REALNAME_SUB = 58 sub { 59 my $username = shift; 60 61 if (defined $_user_realname) { 62 return $_user_realname; 63 } 64 else { 65 # (i.e. user's real name from gecos) 66 return (split( /,/, (getpwnam($username))[6], 2 ))[0]; 67 } 68 }; 69
Para comprobar que lo escrito sea sintácticamente correcto siempre podemos
usar el intérprete Perl
para ello:
# perl -cw /etc/cups/cups-pdf-dispatch.conf
Name "main::GET_USER_MAILADDR_SUB" used only once: possible typo at cups-pdf-dispatch.conf line 106.
Name "main::REMOVE_SENT" used only once: possible typo at cups-pdf-dispatch.conf line 83.
...
cups-pdf-dispatch.conf syntax OK
#
los avisos que vemos nos indican, precisamente, que esas variables han tomado un valor, así que a menos que estemos usándolo con el programa no debemos preocuparnos.
Y así, el archivo mailto
puede contener algo como lo siguiente:
#
# Dirección de correo a la
# que enviar los documentos PDF
#
Víctor Moral <victor@taquiones.net>
Interioridades
Información que he ido agrupando durante la búsqueda de problemas.
Esquema de funcionamiento
cups-pdf
, como cualquier backend de CUPS
recibe los siguientes
parámetros cuando es invocado
job user title num-copies options [ filename ]
numerados desde el uno al seis, puesto que el primero es siempre el nombre del programa.
Lo siguiente es un detalle de su funcionamiento en el que intento resaltar los puntos clave para depurar errores, puesto que casi siempre son achacables a permisos de acceso.
Los valores por defecto que se incluyen en el paquete Debian los incluyo entre paréntesis.
- Prepara el entorno de trabajo:
- Cambia el group del proceso indicado por la variable
Grp
de la configuración (lp
). - Intenta identificar el usuario que envía el trabajo en el archivo
/etc/passwd
.- Si lo consigue y es la primera vez, crea un directorio con su
nombre en la localización donde almacenar los trabajos, determinado
por la variable
Out
(/var/spool/cups-pdf/${USER}
), y le asigna los permisos a ese mismo usuario. Esta es la principal razón de que sea necesario que funcione como root. - Si no lo consigue emplea el directorio para usuarios anónimos
definido en la variable
AnonDirName
(/var/spool/cups-pdf/ANONYMOUS
) y establece como usuario activo al indicado en la variableAnonUser
(nobody
).
- Si lo consigue y es la primera vez, crea un directorio con su
nombre en la localización donde almacenar los trabajos, determinado
por la variable
- Cambia el group del proceso indicado por la variable
- Crea el archivo
PDF
:- Añade un archivo al directorio de trabajo, definido en la variable
Spool
(/var/spool/cups-pdf/SPOOL
), y le asigna el identificador del usuario propietario del trabajo. - Copia en ese archivo la fuente de entrada, bien leyendo de la entrada estándar, bien leyendo de un archivo recibido como último parámetro (el sexto).
- Obtiene y prepara un nombre para el documento final.
- Prepara una orden para invocar al programa --gs-- con los parámetros
necesarios para crear el documento
PDF
directamente en la carpeta final del usuario, para lo que se escinde en un proceso hijo en el cual:- Cambia en el entorno la variable
TMPDIR
con el valor de la configuración deGSTmp
(/var/tmp
). - Cambia el GID y el UID del proceso a los que correspondan con el usuario propietario del trabajo.
- Ejecuta el programa --gs-- con el contenido de la variable
GSCall
y los parámetros reunidos anteriormente. - Cambia los permisos del archivo
PDF
empleando el valor deUserUMask
(0077
) como máscara. - Si la variable
PostProcessing
contiene algún valor se toma como la ruta de un programa al que llamar en este momento con los siguientes parámetros:- Ruta al archivo
PDF
. - Nombre del usuario tal y como lo ha determinado anteriormente.
- Nombre del usuario tal y como lo ha recibido él como parámetro (el segundo).
- Ruta al archivo
- Cambia en el entorno la variable
- Añade un archivo al directorio de trabajo, definido en la variable
- El proceso padre espera a que te termine la creación del proceso hijo y:
- Borra el archivo de la cola de trabajo (
/var/spool/cups-pdf/SPOOL
). - Libera memoria
- Borra el archivo de la cola de trabajo (
Nombre del documento PDF
El nombre final del documento PDF
es muy importante, y cups-pdf
tiene
varias formas de componerlo.
- Durante la creación del archivo de trabajo en la cola, el programa efectúa
una copia línea a línea del texto y, saltándose los bloques de
PostScript encapsulado que pueda contener, intenta localizar una meta
variable llamada
%%Title
y leer su contenido. - El tercer parámetro que recibe, si contiene algo, puede ser también el título.
¿ Cómo seleccionar uno ú otro en caso de que existan los dos ? Si la variable
TitlePref
(0) contiene el valor cero tiene preferencia el título encontrado
en el documento, mientras que si el valor es uno la preferencia la tienen los
parámetros.
Una vez elegido el texto del título el programa lo completa de esta forma:
- Elimina algunos caracteres especiales: salto de línea y retorno de carro.
- Si la variable
DecodeHexStrings
tiene el valor 1 se intenta decodificar los textos hexadecimales para permitir títulos internacionales. - Elimina, si los encuentra, cualquier carácter de apertura y cierre de paréntesis.
- También borra cualquier carácter separador de directorios (
/
). - Quita la extensión del archivo.
- Por último limpia otros caracteres especiales, dependiendo del valor de
DecodeHexStrings
:- Si tiene el valor cero, reemplaza caracteres especiales, no mostrables
según el código ASCII con guiones bajos (
_
). - Si tiene el valor uno, hace lo mismo pero llama a las funciones
isalnum()
yisascii()
en lugar de comparar numéricamente cada carácter.
- Si tiene el valor cero, reemplaza caracteres especiales, no mostrables
según el código ASCII con guiones bajos (
Al terminar el proceso anterior podemos tener el título limpio de caracteres
extraños ó totalmente vacío. En el primer caso se le añade un prefijo al
nombre del archivo con el texto job_
y el número de trabajo de impresión
(recibido como primer parámetro), sólo si la variable Label
tiene el valor
uno. En el segundo caso, cuando no hay título como tal, se forma uno con el
prefijo antes citado, el número de trabajo y el literal untitled_document
.
Referencias y enlaces
Existen multitud de referencias y artículos sobre este tema:
- Documentación oficial.
- Blog "Algo de Linux" describiendo la instalación desde las fuentes.
- Artículo sobre Ubuntu en inglés
- Creating a free PDFWriter using
Ghostscript
describe aspectos básicos sobre el uso de
ghostscript
en entornos Windows 98/2000/XP. - ps2pdf: PostScript-to-PDF
converter donde se
detallan las opciones para convertir archivos
postscript
aPDF
empleando esta herramienta, que es un envoltorio para --gs--.
y algunos más técnicos sobre las interioridades de CUPS
:
- Tutorial de Till Kamppeter sobre la comunicación con los servidores de impresión, los clientes y las impresoras; más útil para desarrollo que para administración, pero muy interesante.