source: trunk/debathena/config/printing-config/files/usr/bin/lpr.debathena @ 24384

Revision 24384, 9.9 KB checked in by geofft, 12 years ago (diff)
In printing-config: * Allow the IPP URL's path component to start with /classes/ in addition to /printers/, since duplex queues are implemented as classes, so that we deal with Kerberized duplex queues correctly (see [help.mit.edu #1163697]).
  • Property svn:executable set to *
Line 
1#!/usr/bin/python
2
3# lpr.debathena
4#
5# also lp.debathena, lpq.debathena, and lprm.debathena
6#
7# Wrapper script that intelligently determines whether a command was
8# intended for should go to CUPS or LPRng and sends it off in the
9# right direction
10
11import cups
12import errno
13import hesiod
14import getopt
15import os
16import shlex
17import socket
18import sys
19from subprocess import call, PIPE
20import urllib
21
22try:
23    cupsd = cups.Connection()
24except RuntimeError: # Is this actually safer than "except:"?
25    cupsd = None
26
27def hesiod_lookup(hes_name, hes_type):
28    """A wrapper with somewhat graceful error handling."""
29    try:
30        h = hesiod.Lookup(hes_name, hes_type)
31        if len(h.results) > 0:
32            return h.results
33    except IOError:
34        return []
35
36cups_frontends = ['cups.mit.edu',
37                  'printers.mit.edu',
38                  'cluster-printers.mit.edu']
39cups_backends = [s.lower() for s in
40                 hesiod_lookup('cups-print', 'sloc')
41               + hesiod_lookup('cups-cluster', 'sloc')]
42
43
44def lpropt_transform(args):
45    if 'LPROPT' in os.environ:
46        return shlex.split(os.environ['LPROPT']) + args
47    return args
48
49def zephyr_transform(options):
50    zephyr = True
51    return_options = []
52    for o, a in options:
53        if o == '-N':
54            zephyr = False
55        else:
56            return_options.append((o, a))
57
58    if zephyr and os.environ.get('ATHENA_USER'):
59        return_options.append(('-m', 'zephyr%' + os.environ['ATHENA_USER']))
60
61    return return_options
62
63opts = {
64    'cups': {
65        'lp': ('EU:cd:h:mn:o:q:st:H:P:i:', None, '-d'),
66        'lpq': ('EU:h:P:al', None, '-P'),
67        'lpr': ('EH:U:P:#:hlmo:pqrC:J:T:',
68            (lpropt_transform, zephyr_transform), '-P'),
69        'lprm': ('EU:h:P:', None, '-P'),
70    },
71    'lprng': {
72        'lp': ('ckmprswBGYd:D:f:n:q:t:', None, '-d'),
73        'lpq': ('aAlLVcvP:st:D:', None, '-P'),
74        'lpr': ('ABblC:D:F:Ghi:kJ:K:#:m:NP:rR:sT:U:Vw:X:YZ:z1:2:3:4:',
75            (lpropt_transform, zephyr_transform), '-P'),
76        'lprm': ('aAD:P:VU:', None, '-P'),
77    }
78}
79
80preflist = {
81    'cups': ['cups', 'lprng'],
82    'lprng': ['lprng', 'cups']
83}
84
85def error(code, message):
86    """Exit out with an error
87    """
88    sys.stderr.write(message)
89    sys.exit(code)
90
91def execCups(command, queue, args):
92    """Pass the command and arguments on to the CUPS versions of the command
93    """
94    new_command = '/usr/bin/cups-%s' % command
95    os.execv(new_command, [command] + args)
96
97def execLprng(command, queue, args):
98    """Pass the command and arguments on to the LPRng versions of the command
99    """
100    new_command = '/usr/bin/mit-%s' % command
101    os.execv(new_command, [command] + args)
102
103def getPrintQueue(command, args):
104    """Given argv, extract the printer name, using knowledge of the possible
105    command line options for both the CUPS and LPRng versions of the command
106    that this script was called as. Also, return whether the command line
107    options imply a particular version of the command.
108    """
109
110    preference = 'cups'
111    for version in ['cups', 'lprng']:
112        try:
113            # Get the set of options that correspond to the command that this
114            # script was invoked as
115            (cmd_opts, transformers, destopt) = opts[version][command]
116            if transformers:
117                (transform_args, transform_opts) = transformers
118            else:
119                transform_args = transform_opts = lambda x: x
120        except KeyError:
121            error(1, """
122Error: this script was called as %s, when it must be called as
123one of lpr, lpq, lprm, or lp
124
125""" % command)
126
127        # Attempt to parse it with the current version of this command
128        targs = transform_args(args)
129        try:
130            options, realargs = getopt.gnu_getopt(targs, cmd_opts)
131        except getopt.GetoptError:
132            # That's the wrong version, so try the next one.
133            continue
134
135        options = transform_opts(options)
136        ttargs = [o + a for o, a in options] + realargs
137        if destopt:
138            for o, a in options:
139                if o == destopt:
140                    return (version, a, ttargs)
141        else:
142            if len(realargs) > 0:
143                return (version, realargs[0], ttargs)
144        # Since we've successfully getopt'd, don't try any other versions,
145        # but do note that we like this version.
146        preference = version
147        break
148
149    # Either we failed to getopt, or nobody told us what printer to use,
150    # so let's use the default printer
151    default = os.getenv('PRINTER')
152    if not default:
153        if cupsd:
154            default = cupsd.getDefault()
155    if not default:
156        h = hesiod_lookup(os.uname()[1], 'cluster')
157        for result in h:
158            (key, value) = result.split(None, 1)
159            if key == 'lpr':
160                default = value
161
162    if default:
163        return (preference, default, args)
164    else:
165        error(2, """
166No default printer configured. Specify a -P option, or configure a
167default printer via e.g. System | Administration | Printing.
168
169""")
170
171def getCupsURI(printer):
172    if not cupsd:
173        return None
174    try:
175        attrs = cupsd.getPrinterAttributes(printer)
176        return attrs.get('device-uri')
177    except cups.IPPError:
178        return None
179
180def translate_lprng_args_to_cups(command, args):
181    # TODO yell at user if/when we decide that these args are deprecated
182
183    # If getopt fails, something went very wrong -- _we_ generated this
184    options, realargs = getopt.gnu_getopt(args, opts['lprng'][command][0])
185    cupsargs = []
186    for (o, a) in options:
187        if o in ('-a') and command == 'lpq':
188            cupsargs += [('-a', a)]
189        elif o in ('-b', '-l') and command == 'lpr':
190            cupsargs += [('-l', a)]
191        elif o in ('-h') and command == 'lpr':
192            cupsargs += [('-h', a)]
193        elif o in ('-J') and command == 'lpr':
194            cupsargs += [('-J', a)]
195        elif o in ('-K', '-#') and command == 'lpr':
196            cupsargs += [('-#', a)]
197        elif o in ('-P'):
198            cupsargs += [('-P', a)]
199        elif o in ('-T') and command == 'lpr':
200            cupsargs += [('-T', a)]
201        elif o in ('-U') and command == 'lpr':
202            cupsargs += [('-U', a)]
203        elif o in ('-v') and command == 'lpq':
204            cupsargs += [('-l', a)]
205        elif o in ('-Z') and command == 'lpr':
206            if a == 'simplex':
207                cupsargs += [('-o', 'sides=one-sided')]
208            elif a == 'duplex':
209                cupsargs += [('-o', 'sides=two-sided-long-edge')]
210            elif a == 'duplexshort':
211                cupsargs += [('-o', 'sides=two-sided-short-edge')]
212            # TODO attempt to deal banner=staff
213        elif o in ('-m') and command == 'lpr':
214            # TODO figure out if CUPS can do mail/zephyr
215            pass # Don't warn about this, we probably generated it
216        else:
217            sys.stderr.write("Warning: option %s%s not converted to CUPS\n"
218                             % (o, a))
219    joincupsargs = [o + a for o, a in cupsargs] + realargs
220    sys.stderr.write("Using cups-%s %s\n" % (command, ' '.join(joincupsargs)))
221    return joincupsargs
222
223if __name__ == '__main__':
224    # Remove the command name from the arguments when we extract it
225    command = os.path.basename(sys.argv.pop(0))
226
227    # Determine if the arguments prefer one version of this command,
228    # For instance, "lpr -Zduplex" wants mit-lpr.
229    (preference, queue, args) = getPrintQueue(command, sys.argv)
230
231    # Figure out if the queue exists on the local cupsd, and if so, if
232    # it's an Athena queue.
233    # Note that the "local" cupsd might not be on localhost; client.conf or
234    # CUPS_SERVER might have it pointing to e.g. cups.mit.edu, but that's okay.
235    url = getCupsURI(queue)
236    if url:
237        proto = rest = hostport = path = host = port = None
238
239        (proto, rest) = urllib.splittype(url)
240        if rest:
241            (hostport, path) = urllib.splithost(rest)
242        if hostport:
243            (host, port) = urllib.splitport(hostport)
244        if (proto and host and path and
245            proto == 'ipp' and
246            host.lower() in cups_frontends + cups_backends):
247            # Canonicalize the queue name to Athena's, in case someone
248            # has a local printer called something memorable like 'w20'
249            # that points to 'ajax' or something
250            if path[0:10] == '/printers/':
251                queue = path[10:]
252            elif path[0:9] == '/classes/':
253                queue = path[9:]
254            else: # we can't parse CUPS' URL, punt to CUPS
255                execCups(command, queue, args)
256        else:
257            execCups(command, queue, args)
258
259    # Get rid of any instance on the queue name
260    # TODO The purpose of instances is to have different sets of default
261    # options. Queues may also have default options on the null
262    # instance. Figure out if we need to do anything about them
263    queue = queue.split('/')[0]
264
265    # If we're still here, the queue is definitely an Athena print
266    # queue; it was either in the local cupsd pointing to Athena, or the
267    # local cupsd didn't know about it.
268    # Figure out what Athena thinks the backend server is, and whether
269    # that server is running a cupsd; if not, fall back to LPRng
270
271    pcap = hesiod_lookup(queue, 'pcap')
272    rm = None
273    if pcap:
274        for field in pcap[0].split(':'):
275            if field[0:3] == 'rm=':
276                rm = field[3:]
277    if not rm:
278        # In the unlikely event we're wrong about it being an Athena
279        # print queue, the local cupsd is good enough
280        execCups(command, queue, args)
281
282    try:
283        # See if rm is running a cupsd. If not, assume it's an LPRng server.
284        s = socket.socket()
285        s.settimeout(0.3)
286        s.connect((rm, 631))
287        s.close()
288    except (socket.error, socket.timeout):
289        execLprng(command, queue, args)
290
291    os.environ['CUPS_SERVER'] = rm
292    if preference == 'lprng':
293        args = translate_lprng_args_to_cups(command, args)
294    execCups(command, queue, args)
Note: See TracBrowser for help on using the repository browser.