Beispiel #1
0
class FilePrintServer(StubServerMixin):
    def __init__(self):
        self.lpadmin = ""
        self.lpinfo = ""
        self.add_printer_options = []
        self.file_transfer = FileTransferAttributes()

    def init(self, opts):
        self.file_transfer.init_opts(opts, can_ask=False)
        self.lpadmin = opts.lpadmin
        self.lpinfo = opts.lpinfo
        self.add_printer_options = opts.add_printer_options
        #server-side printer handling is only for posix via pycups for now:
        self.postscript_printer = opts.postscript_printer
        self.pdf_printer = opts.pdf_printer

    def threaded_setup(self):
        self.init_printing()

    def init_sockets(self, sockets):
        #verify we have a local socket for printing:
        unixsockets = [
            info for socktype, _, info, _ in sockets
            if socktype == "unix-domain"
        ]
        printlog("local unix domain sockets we can use for printing: %s",
                 unixsockets)
        if not unixsockets and self.file_transfer.printing:
            if not WIN32:
                printlog.warn("Warning: no local sockets defined,")
                printlog.warn(" disabling printer forwarding")
            printlog("printer forwarding disabled")
            self.file_transfer.printing = False

    def get_server_features(self, _source):
        f = self.file_transfer.get_file_transfer_features()
        f["printer.attributes"] = ("printer-info", "device-uri")
        ftf = self.file_transfer.get_file_transfer_features()
        if self.file_transfer.file_transfer:
            ftf["request-file"] = True
        f.update(ftf)
        return f

    def get_info(self, _proto):
        d = {}
        if POSIX:
            d.update({
                "lpadmin": self.lpadmin,
                "lpinfo": self.lpinfo,
                "add-printer-options": self.add_printer_options,
            })
        if self.file_transfer.printing:
            from xpra.platform.printing import get_info
            d.update(get_info())
        info = {"printing": d}
        if self.file_transfer.file_transfer:
            fti = self.file_transfer.get_info()
            if self.file_transfer.file_transfer:
                fti["request-file"] = True
            info["file"] = fti
        return info

    def init_printing(self):
        printing = self.file_transfer.printing
        if not printing or WIN32:
            return
        try:
            from xpra.platform import pycups_printing
            pycups_printing.set_lpadmin_command(self.lpadmin)
            pycups_printing.set_lpinfo_command(self.lpinfo)
            pycups_printing.set_add_printer_options(self.add_printer_options)
            if self.postscript_printer:
                pycups_printing.add_printer_def("application/postscript",
                                                self.postscript_printer)
            if self.pdf_printer:
                pycups_printing.add_printer_def("application/pdf",
                                                self.pdf_printer)
            printer_definitions = pycups_printing.validate_setup()
            printing = bool(printer_definitions)
            if printing:
                printlog.info(
                    "printer forwarding enabled using %s", " and ".join(
                        x.replace("application/", "")
                        for x in printer_definitions))
            else:
                printlog.warn("Warning: no printer definitions found,")
                printlog.warn(" cannot enable printer forwarding")
        except ImportError as e:
            printlog("printing module is not installed: %s", e)
            printing = False
        except Exception:
            printlog.error("Error: failed to set lpadmin and lpinfo commands",
                           exc_info=True)
            printing = False
        #verify that we can talk to the socket:
        auth_class = self.auth_classes.get("unix-domain")
        if printing and auth_class:
            try:
                #this should be the name of the auth module:
                auth_name = auth_class[0]
            except:
                auth_name = str(auth_class)
            if auth_name not in ("none", "file"):
                printlog.warn("Warning: printer forwarding cannot be used,")
                printlog.warn(
                    " it conflicts with socket authentication module '%r'",
                    auth_name)
                printing = False
        #update file transfer attributes since printing nay have been disabled here
        self.file_transfer.printing = printing
        printlog("init_printing() printing=%s", printing)

    def _process_print(self, _proto, packet):
        #ie: from the xpraforwarder we call this command:
        #command = ["xpra", "print", "socket:/path/tosocket",
        #           filename, mimetype, source, title, printer, no_copies, print_options]
        assert self.file_transfer.printing
        #printlog("_process_print(%s, %s)", proto, packet)
        if len(packet) < 3:
            printlog.error("Error: invalid print packet, only %i arguments",
                           len(packet))
            printlog.error(" %s", [repr_ellipsized(x) for x in packet])
            return

        def s(b):
            try:
                return b.decode("utf-8")
            except Exception:
                return bytestostr(b)

        filename = s(packet[1])
        file_data = packet[2]
        mimetype, source_uuid, title, printer, no_copies, print_options = "", "*", "unnamed document", "", 1, ""
        if len(packet) >= 4:
            mimetype = bytestostr(packet[3])
        if len(packet) >= 5:
            source_uuid = bytestostr(packet[4])
        if len(packet) >= 6:
            title = s(packet[5])
        if len(packet) >= 7:
            printer = bytestostr(packet[6])
        if len(packet) >= 8:
            no_copies = int(packet[7])
        if len(packet) >= 9:
            print_options = packet[8]
        #parse and validate:
        if len(mimetype) >= 128:
            printlog.error("Error: invalid mimetype in print packet:")
            printlog.error(" %s", repr_ellipsized(mimetype))
            return
        if not isinstance(print_options, dict):
            s = bytestostr(print_options)
            print_options = {}
            for x in s.split(" "):
                parts = x.split("=", 1)
                if len(parts) == 2:
                    print_options[parts[0]] = parts[1]
        printlog("process_print: %s",
                 (filename, mimetype, "%s bytes" % len(file_data), source_uuid,
                  title, printer, no_copies, print_options))
        printlog("process_print: got %s bytes for file %s", len(file_data),
                 filename)
        #parse the print options:
        u = hashlib.sha1()
        u.update(file_data)
        printlog("sha1 digest: %s", u.hexdigest())
        options = {
            "printer": printer,
            "title": title,
            "copies": no_copies,
            "options": print_options,
            "sha1": u.hexdigest(),
        }
        printlog("parsed printer options: %s", options)
        if SAVE_PRINT_JOBS:
            self._save_print_job(filename, file_data)

        sent = 0
        sources = tuple(self._server_sources.values())
        printlog("will try to send to %i clients: %s", len(sources), sources)
        for ss in sources:
            if source_uuid not in ("*", ss.uuid):
                printlog("not sending to %s (uuid=%s, wanted uuid=%s)", ss,
                         ss.uuid, source_uuid)
                continue
            if not ss.printing:
                if source_uuid != '*':
                    printlog.warn("Warning: printing is not enabled for:")
                    printlog.warn(" %s", ss)
                else:
                    printlog("printing is not enabled for %s", ss)
                continue
            if not ss.printers:
                printlog.warn("Warning: client %s does not have any printers",
                              ss.uuid)
                continue
            if printer not in ss.printers:
                printlog.warn(
                    "Warning: client %s does not have a '%s' printer", ss.uuid,
                    printer)
                continue
            printlog("'%s' sent to %s for printing on '%s'",
                     bytestostr(title or filename), ss, printer)
            if ss.send_file(filename, mimetype, file_data, len(file_data),
                            True, True, options):
                sent += 1
        #warn if not sent:
        if sent == 0:
            l = printlog.warn
        else:
            l = printlog.info
        unit_str, v = to_std_unit(len(file_data), unit=1024)
        l("'%s' (%i%sB) sent to %i client%s for printing", title or filename,
          v, unit_str, sent, engs(sent))

    def _save_print_job(self, filename, file_data):
        try:
            save_filename = os.path.join(SAVE_PRINT_JOBS, filename)
            with open(save_filename, "wb") as f:
                f.write(file_data)
            printlog.info("saved print job to: %s", save_filename)
        except Exception as e:
            printlog.error("Error: failed to save print job to %s",
                           save_filename)
            printlog.error(" %s", e)

    def _process_printers(self, proto, packet):
        if not self.file_transfer.printing or WIN32:
            printlog.error("Error: received printer definitions data")
            printlog.error(
                " but this server does not support printer forwarding")
            return
        ss = self.get_server_source(proto)
        if ss is None:
            return
        printers = packet[1]
        auth_class = self.auth_classes.get("unix-domain")
        ss.set_printers(printers, self.password_file, auth_class,
                        self.encryption, self.encryption_keyfile)

    ######################################################################
    # file transfers:
    def _process_send_file(self, proto, packet):
        ss = self.get_server_source(proto)
        if not ss:
            printlog.warn(
                "Warning: invalid client source for send-file packet")
            return
        ss._process_send_file(packet)

    def _process_ack_file_chunk(self, proto, packet):
        ss = self.get_server_source(proto)
        if not ss:
            printlog.warn(
                "Warning: invalid client source for ack-file-chunk packet")
            return
        ss._process_ack_file_chunk(packet)

    def _process_send_file_chunk(self, proto, packet):
        ss = self.get_server_source(proto)
        if not ss:
            printlog.warn(
                "Warning: invalid client source for send-file-chunk packet")
            return
        ss._process_send_file_chunk(packet)

    def _process_send_data_request(self, proto, packet):
        ss = self.get_server_source(proto)
        if not ss:
            printlog.warn(
                "Warning: invalid client source for send-file-request packet")
            return
        ss._process_send_data_request(packet)

    def _process_send_data_response(self, proto, packet):
        ss = self.get_server_source(proto)
        if not ss:
            printlog.warn(
                "Warning: invalid client source for send-data-response packet")
            return
        ss._process_send_data_response(packet)

    def _process_request_file(self, proto, packet):
        ss = self.get_server_source(proto)
        if not ss:
            printlog.warn(
                "Warning: invalid client source for send-data-response packet")
            return
        try:
            argf = packet[1].decode("utf-8")
        except UnicodeDecodeError:
            argf = bytestostr(packet[1])
        openit = packet[2]
        filename = os.path.abspath(osexpand(argf))
        if not os.path.exists(filename):
            filelog.warn("Warning: the file requested does not exist:")
            filelog.warn(" %s", filename)
            ss.may_notify(XPRA_FILETRANSFER_NOTIFICATION_ID,
                          "File not found",
                          "The file requested does not exist:\n%s" % filename,
                          icon_name="file")
            return
        try:
            stat = os.stat(filename)
            filelog("os.stat(%s)=%s", filename, stat)
        except os.error:
            filelog("os.stat(%s)", filename, exc_info=True)
        else:
            file_size_MB = stat.st_size // 1024 // 1024
            if file_size_MB > self.file_transfer.file_size_limit or file_size_MB > ss.file_size_limit:
                ss.may_notify(
                    XPRA_FILETRANSFER_NOTIFICATION_ID,
                    "File too large",
                    "The file requested is too large to send:\n%s\nis %iMB" %
                    (argf, file_size_MB),
                    icon_name="file")
                return
        data = load_binary_file(filename)
        ss.send_file(filename, "", data, len(data), openit=openit)

    def init_packet_handlers(self):
        if self.file_transfer.printing:
            self.add_packet_handlers(
                {
                    "printers": self._process_printers,
                    "print": self._process_print,
                }, False)
        if self.file_transfer.printing or self.file_transfer.file_transfer:
            self.add_packet_handlers(
                {
                    "send-file": self._process_send_file,
                    "ack-file-chunk": self._process_ack_file_chunk,
                    "send-file-chunk": self._process_send_file_chunk,
                    "send-data-request": self._process_send_data_request,
                    "send-data-response": self._process_send_data_response,
                }, False)
        if self.file_transfer.file_transfer:
            self.add_packet_handler("request-file", self._process_request_file,
                                    False)
Beispiel #2
0
 def test_file_transfer_attributes(self):
     fta = FileTransferAttributes()
     assert fta.get_file_transfer_features()
     assert fta.get_info()