1# vim: set ts=4 sw=4 et: coding=UTF-8
2#
3# Copyright (C) 2008, 2013 Novell, Inc.
4# Copyright (C) 2008, 2009, 2010, 2012, 2014 Red Hat, Inc.
5# Copyright (C) 2008, 2009, 2010, 2012, 2014 Tim Waugh <twaugh@redhat.com>
6#
7# Authors: Vincent Untz
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
22#
23
24# check FIXME/TODO here
25# check FIXME/TODO in cups-pk-helper
26# define fine-grained policy (more than one level of permission)
27# add missing methods
28
29import os
30import sys
31
32import tempfile
33
34import cups
35import dbus
36from debug import debugprint
37
38from dbus.mainloop.glib import DBusGMainLoop
39from functools import reduce
40DBusGMainLoop(set_as_default=True)
41
42CUPS_PK_NAME  = 'org.opensuse.CupsPkHelper.Mechanism'
43CUPS_PK_PATH  = '/'
44CUPS_PK_IFACE = 'org.opensuse.CupsPkHelper.Mechanism'
45
46CUPS_PK_NEED_AUTH = 'org.opensuse.CupsPkHelper.Mechanism.NotPrivileged'
47
48
49# we can't subclass cups.Connection, even when adding
50# Py_TPFLAGS_BASETYPE to cupsconnection.c
51# So we'll hack this...
52class Connection:
53    def __init__(self, host, port, encryption):
54        self._parent = None
55
56        try:
57            self._session_bus = dbus.SessionBus()
58            self._system_bus = dbus.SystemBus()
59        except dbus.exceptions.DBusException:
60            # One or other bus not running.
61            self._session_bus = self._system_bus = None
62
63        self._connection = cups.Connection(host=host,
64                                           port=port,
65                                           encryption=encryption)
66
67        self._hack_subclass()
68
69
70    def _hack_subclass(self):
71        # here's how to subclass without really subclassing. Just provide
72        # the same methods
73        methodtype = type(self._connection.getPrinters)
74        for fname in dir(self._connection):
75            if fname[0] == '_':
76                continue
77            fn = getattr(self._connection, fname)
78            if type(fn) != methodtype:
79                continue
80            if not hasattr(self, fname):
81                setattr(self, fname, fn.__call__)
82
83
84    def set_parent(self, parent):
85        self._parent = parent
86
87
88    def _get_cups_pk(self):
89        try:
90            object = self._system_bus.get_object(CUPS_PK_NAME, CUPS_PK_PATH)
91            return dbus.Interface(object, CUPS_PK_IFACE)
92        except dbus.exceptions.DBusException:
93            # Failed to get object or interface.
94            return None
95        except AttributeError:
96            # No system D-Bus
97            return None
98
99
100    def _call_with_pk_and_fallback(self, use_fallback, pk_function_name, pk_args, fallback_function, *args, **kwds):
101        pk_function = None
102        # take signature from kwds if is provided
103        dbus_args_signature = kwds.pop('signature', None)
104
105        if not use_fallback:
106            cups_pk = self._get_cups_pk()
107            if cups_pk:
108                try:
109                    pk_function = cups_pk.get_dbus_method(pk_function_name)
110                except dbus.exceptions.DBusException:
111                    pass
112
113        if use_fallback or not pk_function:
114            return fallback_function(*args, **kwds)
115
116        pk_retval = 'PolicyKit communication issue'
117
118        while True:
119            try:
120                # FIXME: async call or not?
121                pk_retval = pk_function(*pk_args, signature = dbus_args_signature)
122
123                # if the PK call has more than one return values, we pop the
124                # first one as the error message
125                if type(pk_retval) == tuple:
126                    retval = pk_retval[1:]
127                    # if there's no error, then we can safely return what we
128                    # got
129                    if pk_retval[0] == '':
130                        # if there's only one item left in the tuple, we don't
131                        # want to return the tuple, but the item
132                        if len(retval) == 1:
133                            return retval[0]
134                        else:
135                            return retval
136                break
137            except dbus.exceptions.DBusException as e:
138                if e.get_dbus_name() == CUPS_PK_NEED_AUTH:
139                    debugprint ("DBus exception: %s" % e.get_dbus_message ())
140                    raise cups.IPPError(cups.IPP_NOT_AUTHORIZED, 'pkcancel')
141
142                break
143
144        # The PolicyKit call did not work (either a PK-error and we got a dbus
145        # exception that wasn't handled, or an error in the mechanism itself)
146        if pk_retval != '':
147            debugprint ('PolicyKit call to %s did not work: %s' %
148                        (pk_function_name, repr (pk_retval)))
149            return fallback_function(*args, **kwds)
150
151
152    def _args_to_tuple(self, types, *args):
153        retval = [ False ]
154
155        if len(types) != len(args):
156            retval[0] = True
157            # We do this to have the right length for the returned value
158            retval.extend(types)
159            return tuple(types)
160
161        exception = False
162
163        for i in range(len(types)):
164            if type(args[i]) != types[i]:
165                if types[i] == str and type(args[i]) == int:
166                    # we accept a mix between int and str
167                    retval.append(str(args[i]))
168                    continue
169                elif types[i] == str and type(args[i]) == float:
170                    # we accept a mix between float and str
171                    retval.append(str(args[i]))
172                    continue
173                elif types[i] == str and type(args[i]) == bool:
174                    # we accept a mix between bool and str
175                    retval.append(str(args[i]))
176                    continue
177                elif types[i] == str and args[i] is None:
178                    # None is an empty string for dbus
179                    retval.append('')
180                    continue
181                elif types[i] == list and type(args[i]) == tuple:
182                    # we accept a mix between list and tuple
183                    retval.append(list(args[i]))
184                    continue
185                elif types[i] == list and args[i] is None:
186                    # None is an empty list
187                    retval.append([])
188                    continue
189                else:
190                    exception = True
191            retval.append(args[i])
192
193        retval[0] = exception
194
195        return tuple(retval)
196
197
198    def _kwds_to_vars(self, names, **kwds):
199        ret = []
200
201        for name in names:
202            if name in kwds:
203                ret.append(kwds[name])
204            else:
205                ret.append('')
206
207        return tuple(ret)
208
209
210#    getPrinters
211#    getDests
212#    getClasses
213#    getPPDs
214#    getServerPPD
215#    getDocument
216
217
218    def getDevices(self, *args, **kwds):
219        use_pycups = False
220
221        limit = 0
222        include_schemes = []
223        exclude_schemes = []
224        timeout = 0
225
226        if len(args) == 4:
227            (use_pycups, limit, include_schemes, exclude_schemes, timeout) = self._args_to_tuple([int, str, str, int], *args)
228        else:
229            if 'timeout' in kwds:
230                timeout = kwds['timeout']
231
232            if 'limit' in kwds:
233                limit = kwds['limit']
234
235            if 'include_schemes' in kwds:
236                include_schemes = kwds['include_schemes']
237
238            if 'exclude_schemes' in kwds:
239                exclude_schemes = kwds['exclude_schemes']
240
241        pk_args = (timeout, limit, include_schemes, exclude_schemes)
242
243        try:
244            result = self._call_with_pk_and_fallback(use_pycups,
245                                                     'DevicesGet', pk_args,
246                                                     self._connection.getDevices,
247                                                     *args, **kwds)
248        except TypeError:
249            debugprint ("DevicesGet API exception; using old signature")
250            if 'timeout' in kwds:
251                use_pycups = True
252
253            # Convert from list to string
254            if len (include_schemes) > 0:
255                include_schemes = reduce (lambda x, y: x + "," + y,
256                                          include_schemes)
257            else:
258                include_schemes = ""
259
260            if len (exclude_schemes) > 0:
261                exclude_schemes = reduce (lambda x, y: x + "," + y,
262                                          exclude_schemes)
263            else:
264                exclude_schemes = ""
265
266            pk_args = (limit, include_schemes, exclude_schemes)
267            result = self._call_with_pk_and_fallback(use_pycups,
268                                                     'DevicesGet', pk_args,
269                                                     self._connection.getDevices,
270                                                     *args, **kwds)
271
272        # return 'result' if fallback was called
273        if len (result.keys()) > 0 and type (result[list(result.keys())[0]]) == dict:
274             return result
275
276        result_str = {}
277        if result is not None:
278            for i in result.keys():
279                if type(i) == dbus.String:
280                    result_str[str(i)] = str(result[i])
281                else:
282                    result_str[i] = result[i]
283
284        # cups-pk-helper returns all devices in one dictionary.
285        # Keys of different devices are distinguished by ':n' postfix.
286
287        devices = {}
288        n = 0
289        postfix = ':' + str (n)
290        device_keys = [x for x in result_str.keys() if x.endswith(postfix)]
291        while len (device_keys) > 0:
292
293            device_uri = None
294            device_dict = {}
295            for i in device_keys:
296                key = i[:len(i) - len(postfix)]
297                if key != 'device-uri':
298                    device_dict[key] = result_str[i]
299                else:
300                    device_uri = result_str[i]
301
302            if device_uri is not None:
303                devices[device_uri] = device_dict
304
305            n += 1
306            postfix = ':' + str (n)
307            device_keys = [x for x in result_str.keys() if x.endswith(postfix)]
308
309        return devices
310
311
312#    getJobs
313#    getJobAttributes
314
315    def cancelJob(self, *args, **kwds):
316        (use_pycups, jobid) = self._args_to_tuple([int], *args)
317        pk_args = (jobid, )
318
319        self._call_with_pk_and_fallback(use_pycups,
320                                        'JobCancel', pk_args,
321                                        self._connection.cancelJob,
322                                        *args, **kwds)
323
324
325#    cancelAllJobs
326#    authenticateJob
327    def setJobHoldUntil(self, *args, **kwds):
328        (use_pycups, jobid, job_hold_until) = self._args_to_tuple([int, str], *args)
329        pk_args = (jobid, job_hold_until, )
330
331        self._call_with_pk_and_fallback(use_pycups,
332                                        'JobSetHoldUntil', pk_args,
333                                        self._connection.setJobHoldUntil,
334                                        *args, **kwds)
335
336    def restartJob(self, *args, **kwds):
337        (use_pycups, jobid) = self._args_to_tuple([int], *args)
338        pk_args = (jobid, )
339
340        self._call_with_pk_and_fallback(use_pycups,
341                                        'JobRestart', pk_args,
342                                        self._connection.restartJob,
343                                        *args, **kwds)
344
345    def getFile(self, *args, **kwds):
346        ''' Keeping this as an alternative for the code.
347            We don't use it because it's not possible to know if the call was a
348            PK-one (and so we push the content of a temporary filename to fd or
349            file) or a non-PK-one (in which case nothing should be done).
350
351                filename = None
352                fd = None
353                file = None
354                if use_pycups:
355                    if len(kwds) != 1:
356                        use_pycups = True
357                    elif kwds.has_key('filename'):
358                        filename = kwds['filename']
359                    elif kwds.has_key('fd'):
360                        fd = kwds['fd']
361                    elif kwds.has_key('file'):
362                        file = kwds['file']
363                    else:
364                        use_pycups = True
365
366                    if fd or file:
367        '''
368
369        file_object = None
370        fd = None
371        if len(args) == 2:
372            (use_pycups, resource, filename) = self._args_to_tuple([str, str], *args)
373        else:
374            (use_pycups, resource) = self._args_to_tuple([str], *args)
375            if 'filename' in kwds:
376                filename = kwds['filename']
377            elif 'fd' in kwds:
378                fd = kwds['fd']
379            elif 'file' in kwds:
380                file_object = kwds['file']
381            else:
382                if not use_pycups:
383                    raise TypeError()
384                else:
385                    filename = None
386
387        if (not use_pycups) and (fd is not None or file_object is not None):
388            # Create the temporary file in /tmp to ensure that
389            # cups-pk-helper-mechanism is able to write to it.
390            (tmpfd, tmpfname) = tempfile.mkstemp(dir="/tmp")
391            os.close (tmpfd)
392
393            pk_args = (resource, tmpfname)
394            self._call_with_pk_and_fallback(use_pycups,
395                                            'FileGet', pk_args,
396                                            self._connection.getFile,
397                                            *args, **kwds)
398
399            tmpfd = os.open (tmpfname, os.O_RDONLY)
400            tmpfile = os.fdopen (tmpfd, 'rt')
401            tmpfile.seek (0)
402
403            if fd is not None:
404                os.lseek (fd, 0, os.SEEK_SET)
405                line = tmpfile.readline()
406                while line != '':
407                    os.write (fd, line.encode('UTF-8'))
408                    line = tmpfile.readline()
409            else:
410                file_object.seek (0)
411                line = tmpfile.readline()
412                while line != '':
413                    file_object.write (line.encode('UTF-8'))
414                    line = tmpfile.readline()
415
416            tmpfile.close ()
417            os.remove (tmpfname)
418        else:
419            pk_args = (resource, filename)
420
421            self._call_with_pk_and_fallback(use_pycups,
422                                            'FileGet', pk_args,
423                                            self._connection.getFile,
424                                            *args, **kwds)
425
426
427    def putFile(self, *args, **kwds):
428        if len(args) == 2:
429            (use_pycups, resource, filename) = self._args_to_tuple([str, str], *args)
430        else:
431            (use_pycups, resource) = self._args_to_tuple([str], *args)
432            if 'filename' in kwds:
433                filename = kwds['filename']
434            elif 'fd' in kwds:
435                fd = kwds['fd']
436            elif 'file' in kwds:
437                file_object = kwds['file']
438            else:
439                if not use_pycups:
440                    raise TypeError()
441                else:
442                    filename = None
443
444        if (not use_pycups) and (fd is not None or file_object is not None):
445            (tmpfd, tmpfname) = tempfile.mkstemp()
446            os.lseek (tmpfd, 0, os.SEEK_SET)
447
448            if fd is not None:
449                os.lseek (fd, 0, os.SEEK_SET)
450                buf = os.read (fd, 512)
451                while buf != '' and buf != b'':
452                    os.write (tmpfd, buf)
453                    buf = os.read (fd, 512)
454            else:
455                file_object.seek (0)
456                line = file_object.readline ()
457                while line != '':
458                    os.write (tmpfd, line)
459                    line = file_object.readline ()
460
461            os.close (tmpfd)
462
463            pk_args = (resource, tmpfname)
464
465            self._call_with_pk_and_fallback(use_pycups,
466                                            'FilePut', pk_args,
467                                            self._connection.putFile,
468                                            *args, **kwds)
469
470            os.remove (tmpfname)
471        else:
472
473            pk_args = (resource, filename)
474
475            self._call_with_pk_and_fallback(use_pycups,
476                                            'FilePut', pk_args,
477                                            self._connection.putFile,
478                                            *args, **kwds)
479
480
481    def addPrinter(self, *args, **kwds):
482        (use_pycups, name) = self._args_to_tuple([str], *args)
483        (filename, ppdname, info, location, device, ppd) = self._kwds_to_vars(['filename', 'ppdname', 'info', 'location', 'device', 'ppd'], **kwds)
484
485        need_unlink = False
486        if not ppdname and not filename and ppd:
487            (fd, filename) = tempfile.mkstemp (text=True)
488            ppd.writeFd(fd)
489            os.close(fd)
490            need_unlink = True
491
492        if filename and not ppdname:
493            pk_args = (name, device, filename, info, location)
494            self._call_with_pk_and_fallback(use_pycups,
495                                            'PrinterAddWithPpdFile', pk_args,
496                                            self._connection.addPrinter,
497                                            *args, **kwds)
498            if need_unlink:
499                os.unlink(filename)
500        else:
501            pk_args = (name, device, ppdname, info, location)
502            self._call_with_pk_and_fallback(use_pycups,
503                                            'PrinterAdd', pk_args,
504                                            self._connection.addPrinter,
505                                            *args, **kwds)
506
507
508    def setPrinterDevice(self, *args, **kwds):
509        (use_pycups, name, device) = self._args_to_tuple([str, str], *args)
510        pk_args = (name, device)
511
512        self._call_with_pk_and_fallback(use_pycups,
513                                        'PrinterSetDevice', pk_args,
514                                        self._connection.setPrinterDevice,
515                                        *args, **kwds)
516
517
518    def setPrinterInfo(self, *args, **kwds):
519        (use_pycups, name, info) = self._args_to_tuple([str, str], *args)
520        pk_args = (name, info)
521
522        self._call_with_pk_and_fallback(use_pycups,
523                                        'PrinterSetInfo', pk_args,
524                                        self._connection.setPrinterInfo,
525                                        *args, **kwds)
526
527
528    def setPrinterLocation(self, *args, **kwds):
529        (use_pycups, name, location) = self._args_to_tuple([str, str], *args)
530        pk_args = (name, location)
531
532        self._call_with_pk_and_fallback(use_pycups,
533                                        'PrinterSetLocation', pk_args,
534                                        self._connection.setPrinterLocation,
535                                        *args, **kwds)
536
537
538    def setPrinterShared(self, *args, **kwds):
539        (use_pycups, name, shared) = self._args_to_tuple([str, bool], *args)
540        pk_args = (name, shared)
541
542        self._call_with_pk_and_fallback(use_pycups,
543                                        'PrinterSetShared', pk_args,
544                                        self._connection.setPrinterShared,
545                                        *args, **kwds)
546
547
548    def setPrinterJobSheets(self, *args, **kwds):
549        (use_pycups, name, start, end) = self._args_to_tuple([str, str, str], *args)
550        pk_args = (name, start, end)
551
552        self._call_with_pk_and_fallback(use_pycups,
553                                        'PrinterSetJobSheets', pk_args,
554                                        self._connection.setPrinterJobSheets,
555                                        *args, **kwds)
556
557
558    def setPrinterErrorPolicy(self, *args, **kwds):
559        (use_pycups, name, policy) = self._args_to_tuple([str, str], *args)
560        pk_args = (name, policy)
561
562        self._call_with_pk_and_fallback(use_pycups,
563                                        'PrinterSetErrorPolicy', pk_args,
564                                        self._connection.setPrinterErrorPolicy,
565                                        *args, **kwds)
566
567
568    def setPrinterOpPolicy(self, *args, **kwds):
569        (use_pycups, name, policy) = self._args_to_tuple([str, str], *args)
570        pk_args = (name, policy)
571
572        self._call_with_pk_and_fallback(use_pycups,
573                                        'PrinterSetOpPolicy', pk_args,
574                                        self._connection.setPrinterOpPolicy,
575                                        *args, **kwds)
576
577
578    def setPrinterUsersAllowed(self, *args, **kwds):
579        (use_pycups, name, users) = self._args_to_tuple([str, list], *args)
580        pk_args = (name, users)
581
582        self._call_with_pk_and_fallback(use_pycups,
583                                        'PrinterSetUsersAllowed', pk_args,
584                                        self._connection.setPrinterUsersAllowed,
585                                        *args, **kwds)
586
587
588    def setPrinterUsersDenied(self, *args, **kwds):
589        (use_pycups, name, users) = self._args_to_tuple([str, list], *args)
590        pk_args = (name, users)
591
592        self._call_with_pk_and_fallback(use_pycups,
593                                        'PrinterSetUsersDenied', pk_args,
594                                        self._connection.setPrinterUsersDenied,
595                                        *args, **kwds)
596
597    def addPrinterOptionDefault(self, *args, **kwds):
598        # The values can be either a single string, or a list of strings, so
599        # we have to handle this
600        (use_pycups, name, option, value) = self._args_to_tuple([str, str, str], *args)
601        # success
602        if not use_pycups:
603            values = (value,)
604        # okay, maybe we directly have values
605        else:
606            (use_pycups, name, option, values) = self._args_to_tuple([str, str, list], *args)
607        pk_args = (name, option, values)
608
609        self._call_with_pk_and_fallback(use_pycups,
610                                        'PrinterAddOptionDefault', pk_args,
611                                        self._connection.addPrinterOptionDefault,
612                                        *args, **kwds)
613
614
615    def deletePrinterOptionDefault(self, *args, **kwds):
616        (use_pycups, name, option) = self._args_to_tuple([str, str], *args)
617        pk_args = (name, option)
618
619        self._call_with_pk_and_fallback(use_pycups,
620                                        'PrinterDeleteOptionDefault', pk_args,
621                                        self._connection.deletePrinterOptionDefault,
622                                        *args, **kwds)
623
624
625    def deletePrinter(self, *args, **kwds):
626        (use_pycups, name) = self._args_to_tuple([str], *args)
627        pk_args = (name,)
628
629        self._call_with_pk_and_fallback(use_pycups,
630                                        'PrinterDelete', pk_args,
631                                        self._connection.deletePrinter,
632                                        *args, **kwds)
633
634#    getPrinterAttributes
635
636    def addPrinterToClass(self, *args, **kwds):
637        (use_pycups, printer, name) = self._args_to_tuple([str, str], *args)
638        pk_args = (name, printer)
639
640        self._call_with_pk_and_fallback(use_pycups,
641                                        'ClassAddPrinter', pk_args,
642                                        self._connection.addPrinterToClass,
643                                        *args, **kwds)
644
645
646    def deletePrinterFromClass(self, *args, **kwds):
647        (use_pycups, printer, name) = self._args_to_tuple([str, str], *args)
648        pk_args = (name, printer)
649
650        self._call_with_pk_and_fallback(use_pycups,
651                                        'ClassDeletePrinter', pk_args,
652                                        self._connection.deletePrinterFromClass,
653                                        *args, **kwds)
654
655
656    def deleteClass(self, *args, **kwds):
657        (use_pycups, name) = self._args_to_tuple([str], *args)
658        pk_args = (name,)
659
660        self._call_with_pk_and_fallback(use_pycups,
661                                        'ClassDelete', pk_args,
662                                        self._connection.deleteClass,
663                                        *args, **kwds)
664
665#    getDefault
666
667    def setDefault(self, *args, **kwds):
668        (use_pycups, name) = self._args_to_tuple([str], *args)
669        pk_args = (name,)
670
671        self._call_with_pk_and_fallback(use_pycups,
672                                        'PrinterSetDefault', pk_args,
673                                        self._connection.setDefault,
674                                        *args, **kwds)
675
676#    getPPD
677
678    def enablePrinter(self, *args, **kwds):
679        (use_pycups, name) = self._args_to_tuple([str], *args)
680        pk_args = (name, True)
681
682        self._call_with_pk_and_fallback(use_pycups,
683                                        'PrinterSetEnabled', pk_args,
684                                        self._connection.enablePrinter,
685                                        *args, **kwds)
686
687
688    def disablePrinter(self, *args, **kwds):
689        (use_pycups, name) = self._args_to_tuple([str], *args)
690        pk_args = (name, False)
691
692        self._call_with_pk_and_fallback(use_pycups,
693                                        'PrinterSetEnabled', pk_args,
694                                        self._connection.disablePrinter,
695                                        *args, **kwds)
696
697
698    def acceptJobs(self, *args, **kwds):
699        (use_pycups, name) = self._args_to_tuple([str], *args)
700        pk_args = (name, True, '')
701
702        self._call_with_pk_and_fallback(use_pycups,
703                                        'PrinterSetAcceptJobs', pk_args,
704                                        self._connection.acceptJobs,
705                                        *args, **kwds)
706
707
708    def rejectJobs(self, *args, **kwds):
709        (use_pycups, name) = self._args_to_tuple([str], *args)
710        (reason,) = self._kwds_to_vars(['reason'], **kwds)
711        pk_args = (name, False, reason)
712
713        self._call_with_pk_and_fallback(use_pycups,
714                                        'PrinterSetAcceptJobs', pk_args,
715                                        self._connection.rejectJobs,
716                                        *args, **kwds)
717
718
719#    printTestPage
720
721    def adminGetServerSettings(self, *args, **kwds):
722        use_pycups = False
723        pk_args = ()
724
725        result = self._call_with_pk_and_fallback(use_pycups,
726                                               'ServerGetSettings', pk_args,
727                                               self._connection.adminGetServerSettings,
728                                               *args, **kwds)
729        settings = {}
730        if result is not None:
731            for i in result.keys():
732                if type(i) == dbus.String:
733                    settings[str(i)] = str(result[i])
734                else:
735                    settings[i] = result[i]
736
737        return settings
738
739
740    def adminSetServerSettings(self, *args, **kwds):
741        (use_pycups, settings) = self._args_to_tuple([dict], *args)
742        pk_args = (settings,)
743
744        self._call_with_pk_and_fallback(use_pycups,
745                                        'ServerSetSettings', pk_args,
746                                        self._connection.adminSetServerSettings,
747                                        *args, **kwds)
748
749
750#    getSubscriptions
751#    createSubscription
752#    getNotifications
753#    cancelSubscription
754#    renewSubscription
755#    printFile
756#    printFiles
757