/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to server.py

  • Committer: Teddy Hogeborn
  • Date: 2008-07-19 18:43:24 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080719184324-iwhoa5in75xa0u2u
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
                       [braxen_client]/password): Removed.
  ([foo]/fingerprint, [braxen_client]/fingerprint): New.
  ([foo]/checker): New.
  ([foo]/secfile): New.
  ([braxen_client]/secret): New.

* server.py: New "--debug" option to set debug flag.  Removed "cert",
             "key", "ca", "crl", and "cred" variables.  Added default
             value for "checker" config file setting.  Do not pass
             credentials to IPv6_TCPServer constructor.
  (debug): New global debug flag.  Used by most debugging output code.
  (Client.__init__): Keyword argument "dn" replaced by "fingerprint",
                     "password" renamed to "secret", and "passfile"
                     renamed to "secfile".  New keyword argument
                     "checker". All callers changed.
  (Client.dn): Removed.
  (Client.fingerprint): New.
  (Client.password): Renamed to "secret"; all users changed.
  (Client.passfile): Renamed to "secfile"; all users changed.
  (Client.timeout, Client.interval): Changed to be properties; now
                                     automatically updates the
                                     "_timeout_milliseconds" and
                                     "_interval_milliseconds" values.
  (Client.timeout_milliseconds): Renamed to "_timeout_milliseconds".
  (Client.interval_milliseconds): Renamed to "_interval_milliseconds".
  (Client.check_command): New.
  (Client.start_checker): Use the new "check_command" attribute.
  (peer_certificate, fingerprint): New functions.

  (tcp_handler.handle): Use ClientSession with empty credentials
                        object instead of ServerSession.  Set gnutls
                        priority string.  Do not verify peer.  Use
                        fingerprint instead of DN when searching for
                        clients.  Bug fix: Loop sending data so even large
                        secret data strings are sent.
  (IPv6_TCPServer.credentials): Removed.
  (if_nametoindex): Do not import ctypes since that is now imported
                    globally.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
import gnutls.crypto
12
12
import gnutls.connection
13
13
import gnutls.errors
 
14
import gnutls.library.functions
 
15
import gnutls.library.constants
 
16
import gnutls.library.types
14
17
import ConfigParser
15
18
import sys
16
19
import re
23
26
import gobject
24
27
import avahi
25
28
from dbus.mainloop.glib import DBusGMainLoop
 
29
import ctypes
26
30
 
27
 
# This variable is used to optionally bind to a specified
28
 
# interface.
 
31
# This variable is used to optionally bind to a specified interface.
 
32
# It is a global variable to fit in with the other variables from the
 
33
# Avahi server example code.
29
34
serviceInterface = avahi.IF_UNSPEC
30
 
# It is a global variable to fit in with the rest of the
31
 
# variables from the Avahi server example code:
 
35
# From the Avahi server example code:
32
36
serviceName = "Mandos"
33
37
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
34
38
servicePort = None                      # Not known at startup
44
48
class Client(object):
45
49
    """A representation of a client host served by this server.
46
50
    Attributes:
47
 
    password:  string
48
 
    fqdn:      string, FQDN (used by the checker)
 
51
    name:      string; from the config file, used in log messages
 
52
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
53
                 uniquely identify the client
 
54
    secret:    bytestring; sent verbatim (over TLS) to client
 
55
    fqdn:      string (FQDN); available for use by the checker command
49
56
    created:   datetime.datetime()
50
57
    last_seen: datetime.datetime() or None if not yet seen
51
58
    timeout:   datetime.timedelta(); How long from last_seen until
52
59
                                     this client is invalid
53
60
    interval:  datetime.timedelta(); How often to start a new checker
54
 
    timeout_milliseconds: Used by gobject.timeout_add()
55
 
    interval_milliseconds: - '' -
56
61
    stop_hook: If set, called by stop() as stop_hook(self)
57
62
    checker:   subprocess.Popen(); a running checker process used
58
63
                                   to see if the client lives.
60
65
    checker_initiator_tag: a gobject event source tag, or None
61
66
    stop_initiator_tag:    - '' -
62
67
    checker_callback_tag:  - '' -
 
68
    checker_command: string; External command which is run to check if
 
69
                     client lives.  %()s expansions are done at
 
70
                     runtime with vars(self) as dict, so that for
 
71
                     instance %(name)s can be used in the command.
 
72
    Private attibutes:
 
73
    _timeout: Real variable for 'timeout'
 
74
    _interval: Real variable for 'interval'
 
75
    _timeout_milliseconds: Used by gobject.timeout_add()
 
76
    _interval_milliseconds: - '' -
63
77
    """
 
78
    def _set_timeout(self, timeout):
 
79
        "Setter function for 'timeout' attribute"
 
80
        self._timeout = timeout
 
81
        self._timeout_milliseconds = ((self.timeout.days
 
82
                                       * 24 * 60 * 60 * 1000)
 
83
                                      + (self.timeout.seconds * 1000)
 
84
                                      + (self.timeout.microseconds
 
85
                                         // 1000))
 
86
    timeout = property(lambda self: self._timeout,
 
87
                       _set_timeout)
 
88
    del _set_timeout
 
89
    def _set_interval(self, interval):
 
90
        "Setter function for 'interval' attribute"
 
91
        self._interval = interval
 
92
        self._interval_milliseconds = ((self.interval.days
 
93
                                        * 24 * 60 * 60 * 1000)
 
94
                                       + (self.interval.seconds
 
95
                                          * 1000)
 
96
                                       + (self.interval.microseconds
 
97
                                          // 1000))
 
98
    interval = property(lambda self: self._interval,
 
99
                        _set_interval)
 
100
    del _set_interval
64
101
    def __init__(self, name=None, options=None, stop_hook=None,
65
 
                 dn=None, password=None, passfile=None, fqdn=None,
66
 
                 timeout=None, interval=-1):
 
102
                 fingerprint=None, secret=None, secfile=None, fqdn=None,
 
103
                 timeout=None, interval=-1, checker=None):
67
104
        self.name = name
68
 
        self.dn = dn
69
 
        if password:
70
 
            self.password = password
71
 
        elif passfile:
72
 
            self.password = open(passfile).readall()
 
105
        # Uppercase and remove spaces from fingerprint
 
106
        # for later comparison purposes with return value of
 
107
        # the fingerprint() function
 
108
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
 
109
        if secret:
 
110
            self.secret = secret.decode(u"base64")
 
111
        elif secfile:
 
112
            sf = open(secfile)
 
113
            self.secret = sf.read()
 
114
            sf.close()
73
115
        else:
74
 
            raise RuntimeError(u"No Password or Passfile for client %s"
 
116
            raise RuntimeError(u"No secret or secfile for client %s"
75
117
                               % self.name)
76
118
        self.fqdn = fqdn                # string
77
119
        self.created = datetime.datetime.now()
79
121
        if timeout is None:
80
122
            timeout = options.timeout
81
123
        self.timeout = timeout
82
 
        self.timeout_milliseconds = ((self.timeout.days
83
 
                                      * 24 * 60 * 60 * 1000)
84
 
                                     + (self.timeout.seconds * 1000)
85
 
                                     + (self.timeout.microseconds
86
 
                                        // 1000))
87
124
        if interval == -1:
88
125
            interval = options.interval
89
126
        else:
90
127
            interval = string_to_delta(interval)
91
128
        self.interval = interval
92
 
        self.interval_milliseconds = ((self.interval.days
93
 
                                       * 24 * 60 * 60 * 1000)
94
 
                                      + (self.interval.seconds * 1000)
95
 
                                      + (self.interval.microseconds
96
 
                                         // 1000))
97
129
        self.stop_hook = stop_hook
98
130
        self.checker = None
99
131
        self.checker_initiator_tag = None
100
132
        self.stop_initiator_tag = None
101
133
        self.checker_callback_tag = None
 
134
        self.check_command = checker
102
135
    def start(self):
103
136
        """Start this clients checker and timeout hooks"""
104
137
        # Schedule a new checker to be started an 'interval' from now,
105
138
        # and every interval from then on.
106
 
        self.checker_initiator_tag = gobject.\
107
 
                                     timeout_add(self.interval_milliseconds,
108
 
                                                 self.start_checker)
 
139
        self.checker_initiator_tag = gobject.timeout_add\
 
140
                                     (self._interval_milliseconds,
 
141
                                      self.start_checker)
109
142
        # Also start a new checker *right now*.
110
143
        self.start_checker()
111
144
        # Schedule a stop() when 'timeout' has passed
112
 
        self.stop_initiator_tag = gobject.\
113
 
                                     timeout_add(self.timeout_milliseconds,
114
 
                                                 self.stop)
 
145
        self.stop_initiator_tag = gobject.timeout_add\
 
146
                                  (self._timeout_milliseconds,
 
147
                                   self.stop)
115
148
    def stop(self):
116
149
        """Stop this client.
117
150
        The possibility that this client might be restarted is left
118
151
        open, but not currently used."""
119
 
        # print "Stopping client", self.name
120
 
        self.password = None
 
152
        if debug:
 
153
            sys.stderr.write(u"Stopping client %s\n" % self.name)
 
154
        self.secret = None
121
155
        if self.stop_initiator_tag:
122
156
            gobject.source_remove(self.stop_initiator_tag)
123
157
            self.stop_initiator_tag = None
145
179
        now = datetime.datetime.now()
146
180
        if os.WIFEXITED(condition) \
147
181
               and (os.WEXITSTATUS(condition) == 0):
148
 
            #print "Checker for %(name)s succeeded" % vars(self)
 
182
            if debug:
 
183
                sys.stderr.write(u"Checker for %(name)s succeeded\n"
 
184
                                 % vars(self))
149
185
            self.last_seen = now
150
186
            gobject.source_remove(self.stop_initiator_tag)
151
 
            self.stop_initiator_tag = gobject.\
152
 
                                      timeout_add(self.timeout_milliseconds,
153
 
                                                  self.stop)
154
 
        #else:
155
 
        #    if not os.WIFEXITED(condition):
156
 
        #        print "Checker for %(name)s crashed?" % vars(self)
157
 
        #    else:
158
 
        #        print "Checker for %(name)s failed" % vars(self)
 
187
            self.stop_initiator_tag = gobject.timeout_add\
 
188
                                      (self._timeout_milliseconds,
 
189
                                       self.stop)
 
190
        elif debug:
 
191
            if not os.WIFEXITED(condition):
 
192
                sys.stderr.write(u"Checker for %(name)s crashed?\n"
 
193
                                 % vars(self))
 
194
            else:
 
195
                sys.stderr.write(u"Checker for %(name)s failed\n"
 
196
                                 % vars(self))
159
197
        self.checker = None
160
198
        self.checker_callback_tag = None
161
199
    def start_checker(self):
163
201
        If a checker already exists, leave it running and do
164
202
        nothing."""
165
203
        if self.checker is None:
166
 
            #print "Starting checker for", self.name
 
204
            if debug:
 
205
                sys.stderr.write(u"Starting checker for %s\n"
 
206
                                 % self.name)
 
207
            try:
 
208
                command = self.check_command % self.fqdn
 
209
            except TypeError:
 
210
                escaped_attrs = dict((key, re.escape(str(val)))
 
211
                                     for key, val in
 
212
                                     vars(self).iteritems())
 
213
                command = self.check_command % escaped_attrs
167
214
            try:
168
215
                self.checker = subprocess.\
169
 
                               Popen("sleep 1; fping -q -- %s"
170
 
                                     % re.escape(self.fqdn),
 
216
                               Popen(command,
171
217
                                     stdout=subprocess.PIPE,
172
218
                                     close_fds=True, shell=True,
173
219
                                     cwd="/")
200
246
            return now < (self.last_seen + self.timeout)
201
247
 
202
248
 
 
249
def peer_certificate(session):
 
250
    # If not an OpenPGP certificate...
 
251
    if gnutls.library.functions.gnutls_certificate_type_get\
 
252
            (session._c_object) \
 
253
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
254
        # ...do the normal thing
 
255
        return session.peer_certificate
 
256
    list_size = ctypes.c_uint()
 
257
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
 
258
        (session._c_object, ctypes.byref(list_size))
 
259
    if list_size.value == 0:
 
260
        return None
 
261
    cert = cert_list[0]
 
262
    return ctypes.string_at(cert.data, cert.size)
 
263
 
 
264
 
 
265
def fingerprint(openpgp):
 
266
    # New empty GnuTLS certificate
 
267
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
268
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
269
        (ctypes.byref(crt))
 
270
    # New GnuTLS "datum" with the OpenPGP public key
 
271
    datum = gnutls.library.types.gnutls_datum_t\
 
272
        (ctypes.cast(ctypes.c_char_p(openpgp),
 
273
                     ctypes.POINTER(ctypes.c_ubyte)),
 
274
         ctypes.c_uint(len(openpgp)))
 
275
    # Import the OpenPGP public key into the certificate
 
276
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
 
277
        (crt,
 
278
         ctypes.byref(datum),
 
279
         gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
280
    # New buffer for the fingerprint
 
281
    buffer = ctypes.create_string_buffer(20)
 
282
    buffer_length = ctypes.c_size_t()
 
283
    # Get the fingerprint from the certificate into the buffer
 
284
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
 
285
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
 
286
    # Deinit the certificate
 
287
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
288
    # Convert the buffer to a Python bytestring
 
289
    fpr = ctypes.string_at(buffer, buffer_length.value)
 
290
    # Convert the bytestring to hexadecimal notation
 
291
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
292
    return hex_fpr
 
293
 
 
294
 
203
295
class tcp_handler(SocketServer.BaseRequestHandler, object):
204
296
    """A TCP request handler class.
205
297
    Instantiated by IPv6_TCPServer for each request to handle it.
206
298
    Note: This will run in its own forked process."""
 
299
    
207
300
    def handle(self):
208
 
        #print u"TCP request came"
209
 
        #print u"Request:", self.request
210
 
        #print u"Client Address:", self.client_address
211
 
        #print u"Server:", self.server
212
 
        session = gnutls.connection.ServerSession(self.request,
213
 
                                                  self.server\
214
 
                                                  .credentials)
 
301
        if debug:
 
302
            sys.stderr.write(u"TCP request came\n")
 
303
            sys.stderr.write(u"Request: %s\n" % self.request)
 
304
            sys.stderr.write(u"Client Address: %s\n"
 
305
                             % unicode(self.client_address))
 
306
            sys.stderr.write(u"Server: %s\n" % self.server)
 
307
        session = gnutls.connection.ClientSession(self.request,
 
308
                                                  gnutls.connection.\
 
309
                                                  X509Credentials())
 
310
        
 
311
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
312
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
313
        #                "+DHE-DSS"))
 
314
        priority = "SECURE256"
 
315
        
 
316
        gnutls.library.functions.gnutls_priority_set_direct\
 
317
            (session._c_object, priority, None);
 
318
        
215
319
        try:
216
320
            session.handshake()
217
321
        except gnutls.errors.GNUTLSError, error:
218
 
            #sys.stderr.write(u"Handshake failed: %s\n" % error)
 
322
            if debug:
 
323
                sys.stderr.write(u"Handshake failed: %s\n" % error)
219
324
            # Do not run session.bye() here: the session is not
220
325
            # established.  Just abandon the request.
221
326
            return
222
 
        #if session.peer_certificate:
223
 
        #    print "DN:", session.peer_certificate.subject
224
327
        try:
225
 
            session.verify_peer()
226
 
        except gnutls.errors.CertificateError, error:
227
 
            #sys.stderr.write(u"Verify failed: %s\n" % error)
 
328
            fpr = fingerprint(peer_certificate(session))
 
329
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
330
            if debug:
 
331
                sys.stderr.write(u"Bad certificate: %s\n" % error)
228
332
            session.bye()
229
333
            return
 
334
        if debug:
 
335
            sys.stderr.write(u"Fingerprint: %s\n" % fpr)
230
336
        client = None
231
337
        for c in clients:
232
 
            if c.dn == session.peer_certificate.subject:
 
338
            if c.fingerprint == fpr:
233
339
                client = c
234
340
                break
235
341
        # Have to check if client.still_valid(), since it is possible
236
342
        # that the client timed out while establishing the GnuTLS
237
343
        # session.
238
 
        if client and client.still_valid():
239
 
            session.send(client.password)
240
 
        else:
241
 
            #if client:
242
 
            #    sys.stderr.write(u"Client %(name)s is invalid\n"
243
 
            #                     % vars(client))
244
 
            #else:
245
 
            #    sys.stderr.write(u"Client not found for DN: %s\n"
246
 
            #                     % session.peer_certificate.subject)
247
 
            #session.send("gazonk")
248
 
            pass
 
344
        if (not client) or (not client.still_valid()):
 
345
            if debug:
 
346
                if client:
 
347
                    sys.stderr.write(u"Client %(name)s is invalid\n"
 
348
                                     % vars(client))
 
349
                else:
 
350
                    sys.stderr.write(u"Client not found for "
 
351
                                     u"fingerprint: %s\n" % fpr)
 
352
            session.bye()
 
353
            return
 
354
        sent_size = 0
 
355
        while sent_size < len(client.secret):
 
356
            sent = session.send(client.secret[sent_size:])
 
357
            if debug:
 
358
                sys.stderr.write(u"Sent: %d, remaining: %d\n"
 
359
                                 % (sent, len(client.secret)
 
360
                                    - (sent_size + sent)))
 
361
            sent_size += sent
249
362
        session.bye()
250
363
 
251
364
 
254
367
    Attributes:
255
368
        options:        Command line options
256
369
        clients:        Set() of Client objects
257
 
        credentials:    GnuTLS X.509 credentials
258
370
    """
259
371
    address_family = socket.AF_INET6
260
372
    def __init__(self, *args, **kwargs):
264
376
        if "clients" in kwargs:
265
377
            self.clients = kwargs["clients"]
266
378
            del kwargs["clients"]
267
 
        if "credentials" in kwargs:
268
 
            self.credentials = kwargs["credentials"]
269
 
            del kwargs["credentials"]
270
379
        return super(type(self), self).__init__(*args, **kwargs)
271
380
    def server_bind(self):
272
381
        """This overrides the normal server_bind() function
282
391
                                       self.options.interface)
283
392
            except socket.error, error:
284
393
                if error[0] == errno.EPERM:
285
 
                    sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
 
394
                    sys.stderr.write(u"Warning: No permission to" \
 
395
                                     u" bind to interface %s\n"
286
396
                                     % self.options.interface)
287
397
                else:
288
398
                    raise error
343
453
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
344
454
        group.connect_to_signal('StateChanged',
345
455
                                entry_group_state_changed)
346
 
    
347
 
    # print "Adding service '%s' of type '%s' ..." % (serviceName,
348
 
    #                                                 serviceType)
 
456
    if debug:
 
457
        sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
 
458
                         % (serviceName, serviceType))
349
459
    
350
460
    group.AddService(
351
461
            serviceInterface,           # interface
369
479
def server_state_changed(state):
370
480
    """From the Avahi server example code"""
371
481
    if state == avahi.SERVER_COLLISION:
372
 
        print "WARNING: Server name collision"
 
482
        sys.stderr.write(u"WARNING: Server name collision\n")
373
483
        remove_service()
374
484
    elif state == avahi.SERVER_RUNNING:
375
485
        add_service()
379
489
    """From the Avahi server example code"""
380
490
    global serviceName, server, rename_count
381
491
    
382
 
    # print "state change: %i" % state
 
492
    if debug:
 
493
        sys.stderr.write(u"state change: %i\n" % state)
383
494
    
384
495
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
385
 
        pass
386
 
        # print "Service established."
 
496
        if debug:
 
497
            sys.stderr.write(u"Service established.\n")
387
498
    elif state == avahi.ENTRY_GROUP_COLLISION:
388
499
        
389
500
        rename_count = rename_count - 1
390
501
        if rename_count > 0:
391
502
            name = server.GetAlternativeServiceName(name)
392
 
            print "WARNING: Service name collision, changing name to '%s' ..." % name
 
503
            sys.stderr.write(u"WARNING: Service name collision, "
 
504
                             u"changing name to '%s' ...\n" % name)
393
505
            remove_service()
394
506
            add_service()
395
507
            
396
508
        else:
397
 
            print "ERROR: No suitable service name found after %i retries, exiting." % n_rename
 
509
            sys.stderr.write(u"ERROR: No suitable service name found "
 
510
                             u"after %i retries, exiting.\n"
 
511
                             % n_rename)
398
512
            main_loop.quit()
399
513
    elif state == avahi.ENTRY_GROUP_FAILURE:
400
 
        print "Error in group state changed", error
 
514
        sys.stderr.write(u"Error in group state changed %s\n"
 
515
                         % unicode(error))
401
516
        main_loop.quit()
402
517
        return
403
518
 
405
520
def if_nametoindex(interface):
406
521
    """Call the C function if_nametoindex()"""
407
522
    try:
408
 
        if "ctypes" not in sys.modules:
409
 
            import ctypes
410
523
        libc = ctypes.cdll.LoadLibrary("libc.so.6")
411
524
        return libc.if_nametoindex(interface)
412
 
    except (ImportError, OSError, AttributeError):
 
525
    except (OSError, AttributeError):
413
526
        if "struct" not in sys.modules:
414
527
            import struct
415
528
        if "fcntl" not in sys.modules:
450
563
                      help="How often to check that a client is up")
451
564
    parser.add_option("--check", action="store_true", default=False,
452
565
                      help="Run self-test")
 
566
    parser.add_option("--debug", action="store_true", default=False,
 
567
                      help="Debug mode")
453
568
    (options, args) = parser.parse_args()
454
569
    
455
570
    if options.check:
467
582
    except ValueError:
468
583
        parser.error("option --interval: Unparseable time")
469
584
    
470
 
    cert = gnutls.crypto.X509Certificate(open(options.cert).read())
471
 
    key = gnutls.crypto.X509PrivateKey(open(options.key).read())
472
 
    ca = gnutls.crypto.X509Certificate(open(options.ca).read())
473
 
    crl = gnutls.crypto.X509CRL(open(options.crl).read())
474
 
    cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
475
 
    
476
585
    # Parse config file
477
 
    defaults = {}
 
586
    defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
478
587
    client_config = ConfigParser.SafeConfigParser(defaults)
479
588
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
480
589
    client_config.read("mandos-clients.conf")
488
597
            avahi.DBUS_INTERFACE_SERVER )
489
598
    # End of Avahi example code
490
599
    
 
600
    debug = options.debug
 
601
    
491
602
    clients = Set()
492
603
    def remove_from_clients(client):
493
604
        clients.remove(client)
494
605
        if not clients:
495
 
            print "No clients left, exiting"
 
606
            if debug:
 
607
                sys.stderr.write(u"No clients left, exiting\n")
496
608
            main_loop.quit()
497
609
    
498
610
    clients.update(Set(Client(name=section, options=options,
506
618
    tcp_server = IPv6_TCPServer((None, options.port),
507
619
                                tcp_handler,
508
620
                                options=options,
509
 
                                clients=clients,
510
 
                                credentials=cred)
 
621
                                clients=clients)
511
622
    # Find out what random port we got
512
623
    servicePort = tcp_server.socket.getsockname()[1]
513
 
    #sys.stderr.write("Now listening on port %d\n" % servicePort)
 
624
    if debug:
 
625
        sys.stderr.write(u"Now listening on port %d\n" % servicePort)
514
626
    
515
627
    if options.interface is not None:
516
628
        serviceInterface = if_nametoindex(options.interface)