#!/usr/bin/env python # -*- coding: utf-8 -*- # # xpraforwarder - A CUPS backend written in Python. # Forwards the print job via xpra. # # It is based on pdf2email by Georde Notaras. # # Copyright (c) George Notaras # Copyright (C) 2014-2017 Antoine Martin # # License: GPLv2 # # This program is released with absolutely no warranty, expressed or implied, # and absolutely no support. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the: # # Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, # MA 02111-1307 USA # import sys, os, syslog import subprocess, traceback try: #py2: from urlparse import urlparse, parse_qs except ImportError: from urllib.parse import urlparse, parse_qs __version__ = "3.0" #Writes a syslog entry (msg) at the default facility: def debug(msg): syslog.syslog(syslog.LOG_DEBUG, msg) def info(msg): syslog.syslog(syslog.LOG_INFO, msg) def err(msg): syslog.syslog(syslog.LOG_ERR, msg) def exec_command(command, env=os.environ.copy()): info("running: %s" % command) PIPE = subprocess.PIPE proc = subprocess.Popen(command, stdin=None, stdout=PIPE, stderr=PIPE, env=env) out,err = proc.communicate() info("returncode=%s" % proc.returncode) if proc.returncode!=0: info("stdout=%s" % out) info("stderr=%s" % err) return proc.returncode def xpra_print(socket_path, socket_dir, password_file, encryption, encryption_keyfile, display, filename, mimetype, source, title, printer, no_copies, print_options): if socket_path: #use direct connection to a given socket path: command = ["xpra", "print", "socket:%s" % socket_path] else: #use the display and hope we can find its corresponding socket: command = ["xpra", "print", display] if socket_dir: command.append("--socket-dir=%s" % socket_dir) if encryption: command += ["--encryption=%s" % encryption, "--encryption-keyfile=%s" % encryption_keyfile] if password_file: command += ["--password-file=%s" % password_file] command += [filename, mimetype, source, title, printer, no_copies, print_options] #in this case, running as root cannot be avoided, so skip the warning: #(using "su" causes even more problems) env = os.environ.copy() env["XPRA_NO_ROOT_WARNING"] = "1" return exec_command(command, env=env) def do_main(): info(" ".join(["'%s'" % x for x in sys.argv])) if len(sys.argv) == 1: # Without arguments should give backend info. # This is also used when lpinfo -v is issued, where it should include "direct this_backend" sys.stdout.write("direct %s \"Unknown\" \"Direct pdf/postscript printing/forwarding to host via xpra\"\n" % os.path.basename(sys.argv[0])) sys.stdout.flush() return 0 if len(sys.argv) not in (6,7): sys.stdout.write("Usage: %s job-id user title copies options [file]\n" % os.path.basename(sys.argv[0])) sys.stdout.flush() err("Wrong number of arguments. Usage: %s job-id user title copies options [file]" % sys.argv[0]) return 1 job_id, username, title, no_copies, print_options = sys.argv[1:6] if len(sys.argv)==7: filename = sys.argv[6] else: filename = "-" info("version %s, username: %s, title: %s, filename: %s, job_id: %s" % (__version__, username, title, filename, job_id)) try: info("uid=%s, gid=%s" % (os.getresuid(), os.getresgid())) except: #osx doesn't have getresuid or getresgid pass dev_uri = os.environ['DEVICE_URI'] info("DEVICE_URI=%s" % dev_uri) parsed_url = urlparse(dev_uri) attributes = parse_qs(parsed_url.query) info("parsed attributes=%s" % attributes) def aget(k, default_value=""): v = attributes.get(k) if v is None: return default_value assert len(v)==1 return v[0] source = aget("source") if not source: raise Exception("Device URI: client source uuid is missing") socket_path = aget("socket-path") display = aget("display", os.environ.get("DISPLAY")) socket_dir = aget("socket-dir") if not display and not socket_path: raise Exception("Device URI: display number and socket path are not specified!") mimetype = aget("mimetype", "application/postscript") printer = aget("remote-printer") password_file = aget("password-file") encryption = aget("encryption") encryption_keyfile = aget("encryption-keyfile") info("xpra display: %s, socket-path: %s" % (display, socket_path)) return xpra_print(socket_path, socket_dir, password_file, encryption, encryption_keyfile, display, filename, mimetype, source, title, printer, no_copies, print_options,) def main(): try: r = do_main() except Exception as e: err("failure in xpraforwarder main: %s" % e) for x in traceback.format_tb(sys.exc_info()[2]): err(x) r = 1 sys.exit(r) if __name__=='__main__': main()