Back to index

system-config-printer  1.3.9+20120706
probe_printer.py
Go to the documentation of this file.
00001 ## system-config-printer
00002 
00003 ## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
00004 ## Copyright (C) 2006 Florian Festi <ffesti@redhat.com>
00005 ## Copyright (C) 2007, 2008, 2009 Tim Waugh <twaugh@redhat.com>
00006 
00007 ## This program is free software; you can redistribute it and/or modify
00008 ## it under the terms of the GNU General Public License as published by
00009 ## the Free Software Foundation; either version 2 of the License, or
00010 ## (at your option) any later version.
00011 
00012 ## This program is distributed in the hope that it will be useful,
00013 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 ## GNU General Public License for more details.
00016 
00017 ## You should have received a copy of the GNU General Public License
00018 ## along with this program; if not, write to the Free Software
00019 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00020 
00021 import cupshelpers
00022 from debug import *
00023 import errno
00024 import socket, time
00025 import gtk
00026 from timedops import TimedOperation
00027 import subprocess
00028 import threading
00029 import errno
00030 import cups
00031 import gobject
00032 import smburi
00033 
00034 try:
00035     import pysmb
00036     PYSMB_AVAILABLE=True
00037 except:
00038     PYSMB_AVAILABLE=False
00039     class pysmb:
00040         class AuthContext:
00041             pass
00042 
00043 def wordsep (line):
00044     words = []
00045     escaped = False
00046     quoted = False
00047     in_word = False
00048     word = ''
00049     n = len (line)
00050     for i in range (n):
00051         ch = line[i]
00052         if escaped:
00053             word += ch
00054             escaped = False
00055             continue
00056 
00057         if ch == '\\':
00058             in_word = True
00059             escaped = True
00060             continue
00061 
00062         if in_word:
00063             if quoted:
00064                 if ch == '"':
00065                     quoted = False
00066                 else:
00067                     word += ch
00068             elif ch.isspace ():
00069                 words.append (word)
00070                 word = ''
00071                 in_word = False
00072             elif ch == '"':
00073                 quoted = True
00074             else:
00075                 word += ch
00076         else:
00077             if ch == '"':
00078                 in_word = True
00079                 quoted = True
00080             elif not ch.isspace ():
00081                 in_word = True
00082                 word += ch
00083 
00084     if word != '':
00085         words.append (word)
00086 
00087     return words
00088 
00089 ### should be ['network', 'foo bar', ' ofoo', '"', '2 3']
00090 ##print wordsep ('network "foo bar" \ ofoo "\\"" 2" "3')
00091 
00092 def open_socket(hostname, port):
00093     try:
00094         host, port = hostname.split(":", 1)
00095     except ValueError:
00096         host = hostname
00097         
00098     s = None
00099     try:
00100         ai = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
00101                                 socket.SOCK_STREAM)
00102     except socket.gaierror:
00103         ai = []
00104 
00105     for res in ai:
00106         af, socktype, proto, canonname, sa = res
00107         try:
00108             s = socket.socket(af, socktype, proto)
00109             s.settimeout(0.5)
00110         except socket.error, msg:
00111             s = None
00112             continue
00113         try:
00114             s.connect(sa)
00115         except socket.error, msg:
00116             s.close()
00117             s = None
00118             continue
00119         break
00120     return s
00121 
00122 class LpdServer:
00123     def __init__(self, hostname):
00124         self.hostname = hostname
00125         self.max_lpt_com = 8
00126         self.stop = False
00127 
00128     def probe_queue(self,name, result):
00129         s = open_socket(self.hostname, 515)
00130         if not s:
00131             return None
00132         print name
00133         
00134         try:
00135             s.send('\2%s\n' % name) # cmd send job to queue
00136             data = s.recv(1024) # receive status
00137             print repr(data)
00138         except socket.error, msg:
00139             print msg
00140             try:
00141                 s.close ()
00142             except:
00143                 pass
00144 
00145             return False
00146 
00147         if len(data)>0 and ord(data[0])==0:
00148             try:
00149                 s.send('\1\n') # abort job again
00150                 s.close ()
00151             except:
00152                 pass
00153 
00154             result.append(name)
00155             return True
00156 
00157         try:
00158             s.close()
00159         except:
00160             pass
00161 
00162         return False
00163 
00164     def get_possible_queue_names (self):
00165         candidate = ["PASSTHRU", "ps", "lp", "PORT1", ""]
00166         for nr in range (self.max_lpt_com):
00167             candidate.extend (["LPT%d" % nr,
00168                                "LPT%d_PASSTHRU" % nr,
00169                                "COM%d" % nr,
00170                                "COM%d_PASSTHRU" % nr])
00171         for nr in range (50):
00172             candidate.append ("pr%d" % nr)
00173 
00174         return candidate
00175 
00176     def destroy(self):
00177         debugprint ("LpdServer exiting: destroy called")
00178         self.stop = True
00179 
00180     def probe(self):
00181         result = []
00182         for name in self.get_possible_queue_names ():
00183             while gtk.events_pending ():
00184                 gtk.main_iteration ()
00185 
00186             if self.stop:
00187                 break
00188 
00189             found = self.probe_queue(name, result)
00190             if found == None:
00191                 # Couldn't even connect.
00192                 break
00193 
00194             if not found and name.startswith ("pr"):
00195                 break
00196             time.sleep(0.1) # avoid DOS and following counter measures 
00197 
00198         return result
00199 
00200 class BackgroundSmbAuthContext(pysmb.AuthContext):
00201     """An SMB AuthContext class that is only ever run from
00202     a non-GUI thread."""
00203 
00204     def __init__ (self, *args, **kwargs):
00205         self._gui_event = threading.Event ()
00206         pysmb.AuthContext.__init__ (self, *args, **kwargs)
00207 
00208     def _do_perform_authentication (self):
00209         gtk.gdk.threads_enter ()
00210         result = pysmb.AuthContext.perform_authentication (self)
00211         gtk.gdk.threads_leave ()
00212         self._do_perform_authentication_result = result
00213         self._gui_event.set ()
00214         
00215     def perform_authentication (self):
00216         if (self.passes == 0 or
00217             not self.has_failed or
00218             not self.auth_called or
00219             (self.auth_called and not self.tried_guest)):
00220             # Safe to call the base function.  It won't try any UI stuff.
00221             return pysmb.AuthContext.perform_authentication (self)
00222 
00223         self._gui_event.clear ()
00224         gobject.timeout_add (1, self._do_perform_authentication)
00225         self._gui_event.wait ()
00226         return self._do_perform_authentication_result
00227 
00228 class PrinterFinder:
00229     def __init__ (self):
00230         self.quit = False
00231 
00232     def find (self, hostname, callback_fn):
00233         self.hostname = hostname
00234         self.callback_fn = callback_fn
00235         self.op = TimedOperation (self._do_find, callback=lambda x, y: None)
00236 
00237     def cancel (self):
00238         self.op.cancel ()
00239         self.quit = True
00240 
00241     def _do_find (self):
00242         self._cached_attributes = dict()
00243         for fn in [self._probe_jetdirect,
00244                    self._probe_ipp,
00245                    self._probe_snmp,
00246                    self._probe_lpd,
00247                    self._probe_hplip,
00248                    self._probe_smb]:
00249             if self.quit:
00250                 return
00251 
00252             try:
00253                 fn ()
00254             except Exception, e:
00255                 nonfatalException ()
00256 
00257         # Signal that we've finished.
00258         if not self.quit:
00259             self.callback_fn (None)
00260 
00261     def _new_device (self, uri, info, location = None):
00262         device_dict = { 'device-class': 'network',
00263                         'device-info': "%s" % info }
00264         if location:
00265             device_dict['device-location']=location
00266         device_dict.update (self._cached_attributes)
00267         new_device = cupshelpers.Device (uri, **device_dict)
00268         debugprint ("Device found: %s" % uri)
00269         self.callback_fn (new_device)
00270 
00271     def _probe_snmp (self):
00272         # Run the CUPS SNMP backend, pointing it at the host.
00273         null = file ("/dev/null", "r+")
00274         try:
00275             debugprint ("snmp: trying")
00276             p = subprocess.Popen (args=["/usr/lib/cups/backend/snmp",
00277                                         self.hostname],
00278                                   close_fds=True,
00279                                   stdin=null,
00280                                   stdout=subprocess.PIPE,
00281                                   stderr=null)
00282         except OSError, e:
00283             debugprint ("snmp: no good")
00284             if e == errno.ENOENT:
00285                 return
00286 
00287             raise
00288 
00289         (stdout, stderr) = p.communicate ()
00290         if p.returncode != 0:
00291             debugprint ("snmp: no good (return code %d)" % p.returncode)
00292             return
00293 
00294         if self.quit:
00295             debugprint ("snmp: no good")
00296             return
00297 
00298         for line in stdout.split ('\n'):
00299             words = wordsep (line)
00300             n = len (words)
00301             if n == 6:
00302                 (device_class, uri, make_and_model,
00303                  info, device_id, device_location) = words
00304             elif n == 5:
00305                 (device_class, uri, make_and_model, info, device_id) = words
00306             elif n == 4:
00307                 (device_class, uri, make_and_model, info) = words
00308             else:
00309                 continue
00310 
00311             device_dict = { 'device-class': device_class,
00312                             'device-make-and-model': make_and_model,
00313                             'device-info': info }
00314             if n == 5:
00315                 debugprint ("snmp: Device ID found:\n%s" %
00316                             device_id)
00317                 device_dict['device-id'] = device_id
00318             if n == 6:
00319                 device_dict['device-location'] = device_location
00320 
00321             device = cupshelpers.Device (uri, **device_dict)
00322             debugprint ("Device found: %s" % uri)
00323             self.callback_fn (device)
00324 
00325             # Cache the make and model for use by other search methods
00326             # that are not able to determine it.
00327             self._cached_attributes['device-make-and-model'] = make_and_model
00328             self._cached_attributes['device_id'] = device_id
00329 
00330         debugprint ("snmp: done")
00331 
00332     def _probe_lpd (self):
00333         debugprint ("lpd: trying")
00334         lpd = LpdServer (self.hostname)
00335         for name in lpd.get_possible_queue_names ():
00336             if self.quit:
00337                 debugprint ("lpd: no good")
00338                 return
00339 
00340             found = lpd.probe_queue (name, [])
00341             if found == None:
00342                 # Couldn't even connect.
00343                 debugprint ("lpd: couldn't connect")
00344                 break
00345 
00346             if found:
00347                 uri = "lpd://%s/%s" % (self.hostname, name)
00348                 self._new_device(uri, self.hostname)
00349 
00350             if not found and name.startswith ("pr"):
00351                 break
00352 
00353             time.sleep(0.1) # avoid DOS and following counter measures 
00354 
00355         debugprint ("lpd: done")
00356 
00357     def _probe_hplip (self):
00358         null = file ("/dev/null", "r+")
00359         try:
00360             debugprint ("hplip: trying")
00361             p = subprocess.Popen (args=["hp-makeuri", "-c", self.hostname],
00362                                   close_fds=True,
00363                                   stdin=null,
00364                                   stdout=subprocess.PIPE,
00365                                   stderr=null)
00366         except OSError, e:
00367             if e == errno.ENOENT:
00368                 return
00369 
00370             raise
00371 
00372         (stdout, stderr) = p.communicate ()
00373         if p.returncode != 0:
00374             debugprint ("hplip: no good (return code %d)" % p.returncode)
00375             return
00376 
00377         if self.quit:
00378             debugprint ("hplip: no good")
00379             return
00380 
00381         uri = stdout.strip ()
00382         debugprint ("hplip: uri is %s" % uri)
00383         if uri.find (":") != -1:
00384             self._new_device(uri, uri)
00385 
00386         debugprint ("hplip: done")
00387 
00388     def _probe_smb (self):
00389         if not PYSMB_AVAILABLE:
00390             return
00391 
00392         smbc_auth = BackgroundSmbAuthContext ()
00393         debug = 0
00394         if get_debugging ():
00395             debug = 10
00396         ctx = pysmb.smbc.Context (debug=debug,
00397                                   auth_fn=smbc_auth.callback)
00398         entries = []
00399         uri = "smb://%s/" % self.hostname
00400         debugprint ("smb: trying")
00401         try:
00402             while smbc_auth.perform_authentication () > 0:
00403                 if self.quit:
00404                     debugprint ("smb: no good")
00405                     return
00406 
00407                 try:
00408                     entries = ctx.opendir (uri).getdents ()
00409                 except Exception, e:
00410                     smbc_auth.failed (e)
00411         except RuntimeError, (e, s):
00412             if e not in [errno.ENOENT, errno.EACCES, errno.EPERM]:
00413                 debugprint ("Runtime error: %s" % repr ((e, s)))
00414         except:
00415             nonfatalException ()
00416 
00417         if self.quit:
00418             debugprint ("smb: no good")
00419             return
00420 
00421         for entry in entries:
00422             if entry.smbc_type == pysmb.smbc.PRINTER_SHARE:
00423                 uri = "smb://%s/%s" % (smburi.urlquote (self.hostname),
00424                                        smburi.urlquote (entry.name))
00425                 info = "SMB (%s)" % self.hostname
00426                 self._new_device(uri, info)
00427 
00428         debugprint ("smb: done")
00429 
00430     def _probe_jetdirect (self):
00431         port = 9100    #jetdirect
00432         sock_address = (self.hostname, port)
00433         debugprint ("jetdirect: trying")
00434         s = open_socket(self.hostname, port)
00435         if not s:
00436             debugprint ("jetdirect: %s:%d CLOSED" % sock_address)
00437         else:
00438             # port is open so assume its a JetDirect device
00439             debugprint ("jetdirect %s:%d OPEN" % sock_address)
00440             uri = "socket://%s:%d" % sock_address
00441             info = "JetDirect (%s)" % self.hostname
00442             self._new_device(uri, info)
00443             s.close ()
00444 
00445         debugprint ("jetdirect: done")
00446 
00447     def _probe_ipp (self):
00448         debugprint ("ipp: trying")
00449         try:
00450             ai = socket.getaddrinfo(self.hostname, 631, socket.AF_UNSPEC,
00451                                     socket.SOCK_STREAM)
00452         except socket.gaierror:
00453             debugprint ("ipp: can't resolve %s" % self.hostname)
00454             debugprint ("ipp: no good")
00455             return
00456         for res in ai:
00457             af, socktype, proto, canonname, sa = res
00458             if (af == socket.AF_INET and sa[0] == '127.0.0.1' or
00459                 af == socket.AF_INET6 and sa[0] == '::1'):
00460                 debugprint ("ipp: do not probe local cups server")
00461                 debugprint ("ipp: no good")
00462                 return
00463 
00464         try:
00465             c = cups.Connection (host = self.hostname)
00466         except RuntimeError:
00467             debugprint ("ipp: can't connect to server/printer")
00468             debugprint ("ipp: no good")
00469             return
00470 
00471         try:
00472             printers = c.getPrinters ()
00473         except cups.IPPError:
00474             debugprint ("%s is probably not a cups server but IPP printer" %
00475                         self.hostname)
00476             uri = "ipp://%s:631/ipp" % (self.hostname)
00477             info = "IPP (%s)" % self.hostname
00478             self._new_device(uri, info)
00479             debugprint ("ipp: done")
00480             return
00481 
00482         for name, queue in printers.iteritems ():
00483             uri = queue['printer-uri-supported']
00484             info = queue['printer-info']
00485             location = queue['printer-location']
00486             self._new_device(uri, info, location)
00487 
00488         debugprint ("ipp: done")
00489 
00490 if __name__ == '__main__':
00491     import sys
00492     if len (sys.argv) < 2:
00493         print "Need printer address"
00494         sys.exit (1)
00495 
00496     set_debugging (True)
00497     loop = gobject.MainLoop ()
00498 
00499     def display (device):
00500         if device == None:
00501             loop.quit ()
00502 
00503     addr = sys.argv[1]
00504     p = PrinterFinder ()
00505     p.find (addr, display)
00506     loop.run ()