#!/usr/bin/env python # -*- coding: utf-8 -*- # # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P. # # 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 # # Author: Don Welch # # Thanks to Henrique M. Holschuh for various security patches # __version__ = '9.0' __title__ = 'PC Sendfax Utility' __mod__ = 'hp-sendfax' __doc__ = "PC send fax for HPLIP supported multifunction printers." # Std Lib import sys import os import os.path import getopt import signal import time import operator # Local from base.g import * import base.utils as utils from base import device, tui, module username = prop.username faxnum_list = [] recipient_list = [] group_list = [] prettyprint = False mod = module.Module(__mod__, __title__, __version__, __doc__, None, (GUI_MODE, NON_INTERACTIVE_MODE), (UI_TOOLKIT_QT3, UI_TOOLKIT_QT4)) mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS | module.USAGE_FLAG_SUPRESS_G_DEBUG_FLAG, extra_options=[ ("Specify the fax number(s):", "-f or --faxnum= or --fax-num= or --num=(-n only)", "option", False), ("Specify the recipient(s):", "-r or --recipient= (-n only)", "option", False), ("Specify the groups(s):", "--group= or --groups= (-n only)", "option", False) ], see_also_list=['hp-faxsetup', 'hp-fab']) opts, device_uri, printer_name, mode, ui_toolkit, loc = \ mod.parseStdOpts('f:r:g:', ['faxnum=', 'fax-num=', 'recipient=', 'group=', 'groups=', 'gg'], supress_g_debug_flag=True) for o, a in opts: if o == '--gg': log.set_level('debug') elif o in ('-z', '--logfile'): log.set_logfile(a) log.set_where(log.LOG_TO_CONSOLE_AND_FILE) elif o == '--fax': printer_name = a elif o in ('-f', '--faxnum', '--fax-num', '--num'): faxnum_list.extend(a.split(',')) elif o in ('-r', '--recipient'): recipient_list.extend(a.split(',')) elif o in ('-g', '--group'): group_list.extend(a.split(',')) if not prop.fax_build: log.error("Fax is disabled (turned off during build). Exiting") sys.exit(1) printer_name, device_uri = mod.getPrinterName(printer_name, device_uri, filter={'fax-type': (operator.gt, 0)}, back_end_filter=['hpfax']) if mode == GUI_MODE: if ui_toolkit == 'qt3': if not utils.canEnterGUIMode(): log.error("%s requires GUI support (try running with --qt4). Also, try using non-interactive (-n) mode." % __mod__) sys.exit(1) else: if not utils.canEnterGUIMode4(): log.error("%s requires GUI support (try running with --qt3). Also, try using non-interactive (-n) mode." % __mod__) sys.exit(1) if mode == GUI_MODE: if ui_toolkit == 'qt3': app = None sendfax = None try: from qt import * from ui.faxsendjobform import FaxSendJobForm except ImportError: log.error("Unable to load Qt3 support. Is it installed?") sys.exit(1) # create the main application object app = QApplication(sys.argv) if loc is None: loc = user_conf.get('ui', 'loc', 'system') if loc.lower() == 'system': loc = str(QTextCodec.locale()) log.debug("Using system locale: %s" % loc) if loc.lower() != 'c': e = 'utf8' try: l, x = loc.split('.') loc = '.'.join([l, e]) except ValueError: l = loc loc = '.'.join([loc, e]) log.debug("Trying to load .qm file for %s locale." % loc) trans = QTranslator(None) qm_file = 'hplip_%s.qm' % l log.debug("Name of .qm file: %s" % qm_file) loaded = trans.load(qm_file, prop.localization_dir) if loaded: app.installTranslator(trans) else: loc = 'c' if loc == 'c': log.debug("Using default 'C' locale") else: log.debug("Using locale: %s" % loc) QLocale.setDefault(QLocale(loc)) prop.locale = loc try: locale.setlocale(locale.LC_ALL, locale.normalize(loc)) except locale.Error: pass if os.geteuid() == 0: log.error("You must not be root to run this utility.") QMessageBox.critical(None, "HP Device Manager - Send Fax", "You must not be root to run hp-sendfax.", QMessageBox.Ok, QMessageBox.NoButton, QMessageBox.NoButton) sys.exit(1) # TODO: Fix instance lock sendfax = FaxSendJobForm(device_uri, printer_name, mod.args) app.setMainWidget(sendfax) pid = os.getpid() log.debug('pid=%d' % pid) sendfax.show() try: log.debug("Starting GUI loop...") app.exec_loop() except KeyboardInterrupt: pass else: # qt4 #try: if 1: from PyQt4.QtGui import QApplication from ui4.sendfaxdialog import SendFaxDialog #except ImportError: if 0: log.error("Unable to load Qt4 support. Is it installed?") sys.exit(1) app = QApplication(sys.argv) dlg = SendFaxDialog(None, printer_name, device_uri, mod.args) dlg.show() try: log.debug("Starting GUI loop...") app.exec_() except KeyboardInterrupt: sys.exit(0) else: # NON_INTERACTIVE_MODE if os.getuid() == 0: log.error("%s cannot be run as root." % __mod__) sys.exit(1) try: import struct, Queue from prnt import cups from base import magic try: from fax import fax except ImportError: # This can fail on Python < 2.3 due to the datetime module log.error("Fax address book disabled - Python 2.3+ required.") sys.exit(1) db = fax.FaxAddressBook() # FAB instance try: import dbus except ImportError: log.error("PC send fax requires dBus and python-dbus") sys.exit(1) import warnings # Ignore: .../dbus/connection.py:242: DeprecationWarning: object.__init__() takes no parameters # (occurring on Python 2.6/dBus 0.83/Ubuntu 9.04) warnings.simplefilter("ignore", DeprecationWarning) dbus_avail, service, session_bus = device.init_dbus() if not dbus_avail or service is None: log.error("Unable to initialize dBus. PC send fax requires dBus and hp-systray support. Exiting.") sys.exit(1) phone_num_list = [] log.debug("Faxnum list = %s" % faxnum_list) faxnum_list = utils.uniqueList(faxnum_list) log.debug("Unique list=%s" % faxnum_list) for f in faxnum_list: for c in f: if c not in '0123456789-(+) *#': log.error("Invalid character in fax number '%s'. Only the characters '0123456789-(+) *#' are valid." % f) sys.exit(1) log.debug("Group list = %s" % group_list) group_list = utils.uniqueList(group_list) log.debug("Unique list=%s" % group_list) for g in group_list: entries = db.group_members(g) if not entries: log.warn("Unknown group name: %s" % g) else: for e in entries: recipient_list.append(e) log.debug("Recipient list = %s" % recipient_list) recipient_list = utils.uniqueList(recipient_list) log.debug("Unique list=%s" % recipient_list) for r in recipient_list: if db.get(r) is None: log.error("Unknown fax recipient '%s' in the recipient list." % r) all_entries = db.get_all_records() log.info(log.bold("\nKnown recipients (entries):")) for a in all_entries: aa = db.get(a) log.info("%s (fax number: %s)" % (a, aa['fax'])) print sys.exit(1) for p in recipient_list: a = db.get(p) if a['fax']: phone_num_list.append(a) log.debug("Name=%s Number=%s" % (a['name'], a['fax'])) for p in faxnum_list: phone_num_list.append({'fax': p, 'name': u'Unknown'}) log.debug("Number=%s" % p) log.debug("Phone num list = %s" % phone_num_list) if not phone_num_list: mod.usage(error_msg=["No recipients specified. Please use -f, -r, and/or -g to specify recipients."]) allowable_mime_types = cups.getAllowableMIMETypes() for f in mod.args: path = os.path.realpath(f) log.debug(path) if os.path.exists(path): mime_type = magic.mime_type(path) log.debug(mime_type) else: log.error("File '%s' does not exist." % path) sys.exit(1) if mime_type not in allowable_mime_types: log.error("File '%s' has a non-allowed mime-type of '%s'" % (path, mime_type)) sys.exit(1) log.info(log.bold("Using fax %s (%s)" % (printer_name, device_uri))) #ok, lock_file = utils.lock_app('%s-%s' % (__mod__, printer_name), True) mod.lockInstance(printer_name) try: ppd_file = cups.getPPD(printer_name) if ppd_file is not None and os.path.exists(ppd_file): if file(ppd_file, 'r').read(8192).find('HP Fax') == -1: log.error("Fax configuration error. The CUPS fax queue for '%s' is incorrectly configured. Please make sure that the CUPS fax queue is configured with the 'HP Fax' Model/Driver." % printer_name) sys.exit(1) if not mod.args: mod.usage(error_msg=["No files specfied to send. Please specify the file(s) to send on the command line."]) file_list = [] for f in mod.args: # # Submit each file to CUPS for rendering by hpijsfax # path = os.path.realpath(f) log.debug(path) mime_type = magic.mime_type(path) if mime_type == 'application/hplip-fax': # .g3 log.info("\nPreparing fax file %s..." % f) fax_file_fd = file(f, 'r') header = fax_file_fd.read(fax.FILE_HEADER_SIZE) fax_file_fd.close() mg, version, pages, hort_dpi, vert_dpi, page_size, \ resolution, encoding, reserved1, reserved2 = struct.unpack(">8sBIHHBBBII", header) if mg != 'hplip_g3': log.error("%s: Invalid file header. Bad magic." % f) sys.exit(1) file_list.append((f, mime_type, "", "", pages)) else: all_pages = True page_range = '' page_set = 0 nup = 1 cups.resetOptions() if mime_type in ["application/x-cshell", "application/x-perl", "application/x-python", "application/x-shell", "text/plain",] and prettyprint: cups.addOption('prettyprint') if nup > 1: cups.addOption('number-up=%d' % nup) while True: cups_printers = cups.getPrinters() printer_state = cups.IPP_PRINTER_STATE_STOPPED for p in cups_printers: if p.name == printer_name: printer_state = p.state log.debug("Printer state = %d" % printer_state) if printer_state == cups.IPP_PRINTER_STATE_IDLE: log.debug("Printer name = %s file = %s" % (printer_name, path)) sent_job_id = cups.printFile(printer_name, path, os.path.basename(path)) log.info("\nRendering file '%s' (job %d)..." % (path, sent_job_id)) log.debug("Job ID=%d" % sent_job_id) break elif printer_state == cups.IPP_PRINTER_STATE_PROCESSING: log.debug("Waiting for CUPS queue '%s' to become idle." % printer_name) else: log.error("The CUPS queue for '%s' is in a stopped or busy state (%d). Please check the queue and try again." % (printer_name, printer_state)) sys.exit(1) cups.resetOptions() # # Wait for fax to finish rendering # end_time = time.time() + 120.0 while time.time() < end_time: log.debug("Waiting for fax...") try: result = list(service.CheckForWaitingFax(device_uri, prop.username, sent_job_id)) log.debug(repr(result)) except dbus.exceptions.DBusException: log.error("Cannot communicate with hp-systray. Canceling...") cups.cancelJob(sent_job_id) sys.exit(1) fax_file = str(result[7]) log.info(fax_file) if fax_file: log.debug("Fax file=%s" % fax_file) title = str(result[5]) break time.sleep(1) else: log.error("Timeout waiting for rendering. Canceling job #%d..." % sent_job_id) cups.cancelJob(sent_job_id) sys.exit(1) # open the rendered file to read the file header f = file(fax_file, 'r') header = f.read(fax.FILE_HEADER_SIZE) if len(header) != fax.FILE_HEADER_SIZE: log.error("Invalid fax file! (truncated header or no data)") sys.exit(1) mg, version, total_pages, hort_dpi, vert_dpi, page_size, \ resolution, encoding, reserved1, reserved2 = \ struct.unpack(">8sBIHHBBBII", header[:fax.FILE_HEADER_SIZE]) log.debug("Magic=%s Ver=%d Pages=%d hDPI=%d vDPI=%d Size=%d Res=%d Enc=%d" % (mg, version, total_pages, hort_dpi, vert_dpi, page_size, resolution, encoding)) file_list.append((fax_file, mime_type, "", title, total_pages)) f.close() # # Insure that the device is in an OK state # dev = None log.debug("\nChecking device state...") try: dev = fax.getFaxDevice(device_uri, printer_name) try: dev.open() except Error, e: log.warn(e.msg) try: dev.queryDevice(quick=True) except Error, e: log.error("Query device error (%s)." % e.msg) dev.error_state = ERROR_STATE_ERROR if dev.error_state > ERROR_STATE_MAX_OK and \ dev.error_state not in (ERROR_STATE_LOW_SUPPLIES, ERROR_STATE_LOW_PAPER): log.error("Device is busy or in an error state (code=%d). Please wait for the device to become idle or clear the error and try again." % dev.error_state) sys.exit(1) user_conf.set('last_used', 'device_uri', dev.device_uri) log.debug("File list:") for f in file_list: log.debug(str(f)) service.SendEvent(device_uri, printer_name, EVENT_START_FAX_JOB, prop.username, 0, '') update_queue = Queue.Queue() event_queue = Queue.Queue() log.info("\nSending fax...") if not dev.sendFaxes(phone_num_list, file_list, "", "", None, False, printer_name, update_queue, event_queue): log.error("Send fax is active. Please wait for operation to complete.") service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_FAIL, prop.username, 0, '') sys.exit(1) try: cont = True while cont: while update_queue.qsize(): try: status, page_num, phone_num = update_queue.get(0) except Queue.Empty: break if status == fax.STATUS_IDLE: log.debug("Idle") elif status == fax.STATUS_PROCESSING_FILES: log.info("\nProcessing page %d" % page_num) elif status == fax.STATUS_DIALING: log.info("\nDialing %s..." % phone_num) elif status == fax.STATUS_CONNECTING: log.info("\nConnecting to %s..." % phone_num) elif status == fax.STATUS_SENDING: log.info("\nSending page %d to %s..." % (page_num, phone_num)) elif status == fax.STATUS_CLEANUP: log.info("\nCleaning up...") elif status in (fax.STATUS_ERROR, fax.STATUS_BUSY, fax.STATUS_COMPLETED): cont = False if status == fax.STATUS_ERROR: log.error("Fax send error.") service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_FAIL, prop.username, 0, '') elif status == fax.STATUS_BUSY: log.error("Fax device is busy. Please try again later.") service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_FAIL, prop.username, 0, '') elif status == fax.STATUS_COMPLETED: log.info("\nCompleted successfully.") service.SendEvent(device_uri, printer_name, EVENT_END_FAX_JOB, prop.username, 0, '') update_spinner() time.sleep(2) cleanup_spinner() except KeyboardInterrupt: event_queue.put((fax.EVENT_FAX_SEND_CANCELED, '', '', '')) service.SendEvent(device_uri, printer_name, EVENT_FAX_JOB_CANCELED, prop.username, 0, '') log.error("Cancelling...") finally: log.debug("Waiting for send fax thread to exit...") if dev is not None: dev.waitForSendFaxThread() log.debug("Closing device...") dev.close() finally: mod.unlockInstance() except KeyboardInterrupt: log.error("User exit") log.info("") log.info("Done.")