Ich bin vor einigen Jahren schon auf die Möglichkeit gestoßen, mit der Installation von CUPS-PDF einen virtuellen PDF-Drucker anzulegen.

Interessanterweise ist CUPS, das UNIX-Drucksystem, flexibler, als ich erst dachte: Auch ohne Installation weiterer Software (von GhostScript mal abgesehen) lassen sich Druckaufträge in PDF wandeln oder sonstige Skripte anstoßen.

Das Meiste der hier aufgelisteten Informationen konnte ich aus diesem Artikel entnehmen.

Für was braucht man sowas?

Einerseits, um lokal an dem Computer, an dem die PDF-Erstellungs-Funktion eingerichtet ist, über den Druckdialog PDFs erstellen zu können, die darauf in einem Ausgabeordner landen oder per E-Mail zugesendet werden oder auch, um einen Druckserver anzulegen, auf den man von anderen Computern aus "druckt"; welcher die erstellten PDFs dann an gewünschter Stelle ablegt oder auch per E-Mail versendet.

In meinem Fall gibt es auch automatisierte Prozesse anderer Server, die in bestimmten Fällen Ausdrucke erstellen, welche der CUPS-Server in ein PDF umleitet, je nach Sender und Inhalt benennt und archiviert oder an andere Personen weiterleitet.

Die Anwendungsgebiete sind mannigfaltig.

Einrichtung

Voraussetzung

UNIX-Server/Computer mit CUPS, installiertes GhostScript (um aus den eingehenden PostScripts PDFs erstellen zu können).

Die Puzzle-Teile

/etc/cups/cupsd.conf             CUPS-Konfiguration
/etc/samba/smb.conf              Samba-Konfiguration (zu ändern, um via Samba Druckaufträge von
                                 Windows-Rechnern annehmen zu können)
/var/spool/samba                 Samba-Spool-Verzeichnis, in dem die Druckaufträge eingehen
/var/spool/pdf                *  Unser-PDF-Ausgabeverzeichnis
/usr/lib/cups/backend/pdf     *# Bash-Skript. Dies ist unser virtueller Drucker
/usr/lib/cups/pdf/ps2pdf.cups *# Bash-Skript, welches letztendlich aus der eingehenden
                                 PostScript-Datei mit Hilfe von GhostScript ein PDF erstellt

* = Datei/Verzeichnis muss erstellt werden
# = Auf dem Mac: /usr/libexec/cups/...

/var/spool/pdf kann auch an besser erreichbarer Stelle, wie z.B. ~/pdfdrucker, liegen (wobei der Pfad in der Konfiguration dann voll ausgeschrieben werden sollte, z.B. /home/apfelz/pdfdrucker).
Es können später beliebig viele virtuelle PDF-Drucker mit unterschiedlichen Ausgabepfaden erstellt werden.

Sollen später mehrere virtuelle Drucker angelegt werden, die verschiedenen Verarbeitungsregeln folgen (z.B. einer erstellt nur ein PDF und legt es ab, ein anderer versendet es per E-Mail, ein weiterer archiviert das PDF an Hand des Inhalts,...), muss je unterschiedlicher Verarbeitung eine weitere Backend-Datei á la /usr/lib/cups/backend/pdf angelegt werden.
Am besten sollte man diese Backend-Dateien gleich von Anfang an korrekt benennen, um ihre Funktion erkennen zu können (z.B. /usr/lib/cups/backend/pdffile, /usr/lib/cups/backend/pdfmail, /usr/lib/cups/backend/pdfarchiv,...).
Bei mehreren Backends achtet bitte auf mein Kommentar im Backend-Skript so in Zeile 10! Je Backend muss hier eine Anpassung erfolgen!

Grundeinrichtung CUPS-System

Sollte CUPS noch jungfräulich sein, empfiehlt es sich, einen Blick in die Konfiguration /etc/cups/cupsd.conf zu werfen. Standardmäßig lässt CUPS es nicht zu, dass es durch Druckaufträge anderer Computer belästigt wird. Zuvor am besten ein Backup erstellen.
sudo cp /etc/cups/cupsd.conf /etc/cups/cupsd.conf.bak

Hier die Ausschnitte aus cups.conf, die ich geändert oder hinzugefügt habe. Alles in der Annahme "Alle Computer im internen Netzwerk sind friedlich".

Auszug aus /etc/cups/cups.conf
Listen 0.0.0.0:631
Browsing On
BrowseLocalProtocols dnssd
DefaultAuthType Basic

<Location />
  Order allow,deny
  Allow From All
</Location>

<Location /admin>
  Order allow,deny
  Allow From All
</Location>

<Location /admin/conf>
  AuthType Default
  Require user @SYSTEM
  Order allow,deny
</Location>

Grundeinrichtung Samba

Auch hier am besten wieder erst ein Backup erstellen:

sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak

Samba stellt die CUPS-Drucker den Windows-PCs im lokalen Netzwerk zur Verfügung, wenn folgende Zeilen in der

[global]
-Sektion vorhanden sind:

Auszug aus /etc/samba/smb.conf
[global]
   security = user
   guest account = nobody
   load printers = yes
   invalid users = root
   printcap name = cups
   printing = cups

Außerdem sollte es noch eine Sektion

[printers]
geben:

Auszug aus /etc/samba/smb.conf
[printers]
   comment = All Printers
   browseable = no
   path = /var/spool/samba
   printable = yes
   public = yes
   guest ok = yes
   create mask = 0777
   create mode = 0777
   use client driver = yes

Sollte das Spool-Verzeichnis /var/spool/samba noch nicht existieren, dann muss es natürlich noch angelegt werden. Ob es existiert und ob die Berechtigungen korrekt sind, ist schnell geprüft:

$ ls -l /var/spool | grep samba
drwxrwxrwt   2 root   root    4096 2016-05-11 19:55 samba/

Falls es nicht existiert:

mkdir -p /var/spool/samba
chown -R root:root /var/spool/samba
chmod 1777 /var/spool/samba

Neues CUPS-Backend

CUPS verwendet sogenannte backends, um auf Drucker zuzugreifen. Beispiele für solche backends sind ipp, lpd und socket. Jedes Backend hat eine bestimmte URI, so dass die Backends bei der Druckereinrichtung unterschieden werden können.
Ein Beispiel für die URI eines IPP-Druckers: ipp://172.17.60.122/print/hp1200

Unser neuer PDF-Drucker wird auf Grund des Dateinamens des Skriptes /usr/lib/cups/backend/pdf (es handelt sich hier trotz fehlender .sh-Dateiendung um ein Shell-Skript) die URI pdf:// erhalten.

Das Ausgabeverzeichnis des PDFs ist in diesem Skript nicht enthalten. Dieses wird später bei der Einrichtung des eigentlichen virtuellen Druckers angegeben und jedes Mal beim Drucken an das Skript weitergegeben.
Folgendes Backend-Skript kann also für mehrere virtuelle Drucker verwendet werden, die die PDFs jeweils an unterschiedlichen Stellen ablegen.

/usr/lib/cups/backend/pdf
#!/bin/bash

PDFBIN=/usr/lib/cups/pdf/ps2pdf.cups
PRINTTIME=`date +%Y-%m-%d_%H.%M.%S`

# Kein Argument übergeben
if [ $# -eq 0 ]; then
  if [ ! -x "$PDFBIN" ]; then
    exit 0
  fi
  # WICHTIG: Hier muss nochmals der verwendete Backend-Name
  # (in diesem Fall "pdf") stehen. Werden weitere Backends angelegt, so
  # ist die folgende Zeile entsprechend anzupassen!
  # z.B.  echo "direct pdfmail \"Unknown\" \"PDF Creator\""
  echo "direct pdf \"Unknown\" \"PDF Creator\""
  exit 0
fi

# case of wrong number of arguments
if [ $# -ne 5 -a $# -ne 6 ]; then
  echo "Usage: pdf job-id user title copies options [file]"
  exit 1
fi

# PDF-Verzeichnis aus der URI auslesen und Zugriffsrechte prüfen
# WICHTIG: Auch hier wäre der Backend-Name #pdf anzupassen
PDFDIR=${DEVICE_URI#pdf:}
if [ ! -d "$PDFDIR" -o ! -w "$PDFDIR" ]; then
  echo "ERROR: directory $PDFDIR not writable"
  exit 1
fi

# generate output filename
OUTPUTFILENAME=
if [ "$3" = "" ]; then
  OUTPUTFILENAME="$PDFDIR/unbenannt.pdf"
else
  if [ "$2" != "" ]; then
    OUTPUTFILENAME="$PDFDIR/$2-$PRINTTIME.pdf"
  else
    OUTPUTFILENAME="$PDFDIR/$PRINTTIME.pdf"
  fi
fi

# run ghostscript
if [ $# -eq 6 ]; then
  $PDFBIN $6 $OUTPUTFILENAME >& /dev/null
else
  $PDFBIN - $OUTPUTFILENAME >& /dev/null
fi

# Datei gehört nach Erstellung zu Benutzer und Gruppe ++lp:lp++
# Ändere ich hier auf den lokalen Benutzer (z.B. apfelz) und gebe allen
# Benutzern das Recht zum Öffnen, löschen & Co.
chown apfelz $OUTPUTFILENAME
chmod 777 $OUTPUTFILENAME

exit 0

Der Backend-Name (in oberem Fall pdf muss im Skript an zwei Stellen unter derselben Bezeichnung vorkommen. Dies wäre zu beachten, sollte man sein Backend umbenennen und/oder mehrere Backends erstellen.
Nehmen wir an, wir erstellen noch ein zweites Backend /usr/lib/cups/backend/pdfmail:

  • Zeile 15:
    echo "direct pdfmail \"Unknown\" \"PDF Creator\""
  • Zeile 27:
    PDFDIR=${DEVICE_URI#pdfmail:}

Existieren in /usr/lib/cups/backend/ mehrere Backend-Skripte, die an diesen Stellen denselben Namen haben, werden diese nicht funktionieren.

Dieses Skript muss exklusiv für den Root-User ausführbar sein. Ist es für sämtliche Benutzer/Gruppen ausführbar, wird es später mit einem unprivilegierten Benutzer wie etwa lp ausgeführt und nicht funktionieren:

sudo chown root:root /usr/lib/cups/backend/pdf
sudo chmod 755 /usr/lib/cups/backend/pdf

Das backend müsste nun aufgelistet werden, wenn man den Befehl

sudo lpinfo -v | grep direct

ausführt.

Skript zur PDF-Erstellung

Die in diesem Backend verwendete
PDFBIN=/usr/lib/cups/pdf/ps2pdf.cups
sollte dieselben Zugriffsrechte wie eben erwähnt besitzen und ist der eigentliche PDF-Ersteller. Die Backend-Datei fängt nur mögliche Fehler ab und kümmert sich um die Dateinamensvergebung.

/usr/lib/cups/pdf/ps2pdf.cups
#!/bin/bash

OPTIONS=""
while true
do
        case "$1" in
        -*) OPTIONS="$OPTIONS $1" ;;
        *)  break ;;

        esac
        shift
done

if [ $# -lt 1 -o $# -gt 2 ]; then
        echo "Usage: `basename $0` [options...] input.ps [output.pdf]" 1>&2
        exit 1
fi

infile=$1;

if [ $# -eq 1 ]
then
        outfile=$1
else
        outfile=$2
fi

# Doing an initial 'save' helps keep fonts from being flushed between pages.
exec gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
        -sOutputFile=$outfile $OPTIONS -c save pop -f $infile

Info: Die PDF-Erstellung hatte bei mir anfangs überhaupt nicht funktioniert. Irgendwo tief im Log /var/log/cups/error_log war etwas von wegen "bad fd number" zu finden. Dies hatte die Ursache, dass ich im Backend-Skript den Shebang #!/bin/sh statt #!/bin/bash verwendet hatte.
Auch ein Leerzeichen vor dem Shebang kann Probleme verursachen!

PPD

CUPS verwendet PPDs, um den eingehenden Druckjob in ein PostScript zu wandeln. Für unseren Zweck können wir die Standard-Adobe-Distiller-PPD verwenden, die Adobe hier zur Verfügung stellt. Die PPD sollte als gzip (nicht als ZIP wie von Adobe zur Verfügung gestellt) in /usr/share/cups/model/ abgelegt werden (inklusive .ppd.gz-Dateierweiterung). Beim Anlegen des Druckers greifen wir später auf sie zurück.

Der Einfachheit halber hier gleich als gzip zum Download:
http://www.apfel-z.net/dl/diverses/pdfcolor.ppd.gz

Besitzer sollte auch hier root sein:

sudo chown root:root /usr/share/cups/model/pdfcolor.ppd.gz
sudo chmod 644 /usr/share/cups/model/pdfcolor.ppd.gz

Neustart

CUPS und Samba neustarten, damit alle Änderungen in den Konfigurationen wirksam werden (Befehle variieren je nach Unix-System):

sudo service smbd restart
sudo service cups restart

Drucker anlegen und Drucken lokal testen

Den Ausgabeordner, in welchem die PDFs letztendlich landen sollen, haben wir noch nicht angelegt. Ich werde ihn im Verzeichnis /var/spool anlegen, allerdings ist auch jeder andere Ort möglich.

sudo mkdir -p /var/spool/pdf
sudo chmod 777  /var/spool/pdf

Den virtuellen Drucker kann man letztendlich per CUPS-Webinterface http://*computer-ip*:631 oder per lpadmin auf der Kommandozeile anlegen:

lpadmin -p <gewünschter Name des Druckers> -D <Beschreibung> -v <backend>:<Ausgabeverzeichnis> -E -P <Pfad zur PPD>
lpadmin -p pdfprinter -D "PDF-Drucker" -v pdf:/var/spool/pdf/ -E -P /usr/share/cups/model/pdfcolor.ppd.gz

Durch die Option -E sollte der Drucker sofort aktiviert werden.

Zum Testen können wir einen Text an den Drucker senden und sollten in /var/spool/pdf/ ein PDF erhalten.

echo "Dies ist ein Test" | lp -d pdfprinter

Den via lpadmin angelegten Drucker pdfprinter sollte man in /etc/cups/printers.conf wieder finden.

Außerdem ist dieser Drucker unter der URL ipp://*computer-ip*/printers/pdfprinter auch auf anderen Computern im lokalen Netzwerk nutzbar.

Druckerinfo

Eine Liste aller angelegten URIs, Druckern samt Status und Dokumenten in den Warteschlangen bekommt man mit dem Befehl

lpstat -t

Eine Liste aller Backends und Netzwerkdruckern bekommt man mit

lpinfo -v

AppArmor und Debugging

Unter Umständen kann das Sicherheits-Framework AppArmor dazwischenfunken (war bei mir nicht der Fall).
Nur um dies mal im Hinterkopf zu behalten.

Sollten keine PDFs erstellt werden, kann ein Blick in

/var/log/cups/error_log
helfen. Unter Umständen kann es auch sinnvoll sein, den LogLevel in
/etc/cups/cupsd.conf
von warn auf debug zu erhöhen, um mehr Informationen im Log zu erhalten (cups danach neustarten!).

PDF per E-Mail versenden

Auf der Seite http://alien.slackbook.org/dokuwiki/doku.php?id=slackware:cups gibt es einen interessanten Ansatz, den Druckauftrag direkt als PDF per E-Mail an die Person, die druckt, zu senden.

War für meinen Fall nicht relevant, da ich sowieso nicht vom Benutzernamen auf die E-Mail-Adresse schließen kann.

Von anderen Computern im Netzwerk aus nutzen: Mac

Wir hatten in /etc/cups/cupsd.conf generellen Zugriff für alle Rechner aus dem Lokalen Netzwerk gewährt, so dass es kein Problem ist, den virtuellen Drucker auch auf anderen Rechnern als Drucker einzurichten.

Die Einrichtung ist ein bisschen tricky:

Systemeinstellungen > Drucken und Faxen > Das unscheinbare + unter der Druckerliste anklicken.

IP-basierter Drucker hinzufügen, IPP-Protokoll und IP-Adresse des Servers eingeben, den wir zuvor eingerichtet hatten.
Der Warteliste-Name ist aus printers/ und dem Druckernamen, wie wir ihn auf dem Server mit lpadmin angelegt hatten, zusammengesetzt. In diesem Beispiel also printers/pdfprinter.
Name und optional Standort können wie gewünscht vergeben werden, bei "Drucken mit" habe ich ebenfalls eine Adobe-PDF-PPD gewählt, die auf meinem Mac installiert war.

Von anderen Computern im Netzwerk aus nutzen: Windows

Hier die besonders umständliche Variante. Wir haben drei Windows 2008 Server. Das mag schon schlimm genug sein, aber der virtuelle Netzwerk-PDF-Drucker meines UNIX-Servers muss dort als lokaler Drucker installiert werden.
Klingt seltsam, aber ist so (sonst kann die Software, die darauf läuft und die Druckjobs absendet nicht auf den Drucker zugreifen).

Interessanterweise kann man unter Windows einen Netzwekdrucker als Lokalen Drucker (statt Netzwerkdrucker) anlegen. Pervers, oder?
Ich weiche hierbei sogar noch von meiner Verfahrensweise aus diesem Artikel ab, wo ich das Problem schonmal hatte. Ich weiß nicht, ob es nun an diesem speziellen PDF-Drucker liegt oder daran, dass sich auf dem Server inzwischen die Windows-Version geändert hat.

Entweder als Administrator oder als normaler User – was auch immer funktionieren mag – einen neuen Drucker hinzufügen und im folgenden Fenster "Einen lokalen Drucker hinzufügen'' wählen.
 
Einen neuen Anschluss erstellen: Local Port
 
\\serverip\druckername (Druckername wie mit
lpadmin
angelegt) als Anschlussnamen verwenden
 
Einen Treiber wählen. Ich habe hierfür einen HP-Treiber für irgendeinen PostScript-Drucker ausgewählt. Funktioniert einwandfrei.
 
P.S. Die Standardmöglichkeit, den Drucker einfach als Netzwerkdrucker anzulegen und aus der Liste der im Netz gefundenen Drucker auszuwählen, dürfte natürlich in den meisten Fällen auch funktionieren und ausreichen.
Auf jeden Fall immer einen Treiber eines Farb-PostScript-Druckers verwenden.