57
import logging.handlers
59
from contextlib import closing
65
31
from dbus.mainloop.glib import DBusGMainLoop
70
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
71
except AttributeError:
73
from IN import SO_BINDTODEVICE
75
# From /usr/include/asm/socket.h
35
import logging.handlers
81
37
logger = logging.Logger('mandos')
82
syslogger = (logging.handlers.SysLogHandler
83
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
84
address = "/dev/log"))
85
syslogger.setFormatter(logging.Formatter
86
('Mandos [%(process)d]: %(levelname)s:'
38
syslogger = logging.handlers.SysLogHandler\
39
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
40
syslogger.setFormatter(logging.Formatter\
41
('%(levelname)s: %(message)s'))
88
42
logger.addHandler(syslogger)
90
console = logging.StreamHandler()
91
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
92
' %(levelname)s: %(message)s'))
93
logger.addHandler(console)
95
class AvahiError(Exception):
96
def __init__(self, value, *args, **kwargs):
98
super(AvahiError, self).__init__(value, *args, **kwargs)
99
def __unicode__(self):
100
return unicode(repr(self.value))
102
class AvahiServiceError(AvahiError):
105
class AvahiGroupError(AvahiError):
109
class AvahiService(object):
110
"""An Avahi (Zeroconf) service.
113
interface: integer; avahi.IF_UNSPEC or an interface index.
114
Used to optionally bind to the specified interface.
115
name: string; Example: 'Mandos'
116
type: string; Example: '_mandos._tcp'.
117
See <http://www.dns-sd.org/ServiceTypes.html>
118
port: integer; what port to announce
119
TXT: list of strings; TXT record for the service
120
domain: string; Domain to publish on, default to .local if empty.
121
host: string; Host to publish records for, default is localhost
122
max_renames: integer; maximum number of renames
123
rename_count: integer; counter so we only rename after collisions
124
a sensible number of times
126
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
127
servicetype = None, port = None, TXT = None,
128
domain = "", host = "", max_renames = 32768,
129
protocol = avahi.PROTO_UNSPEC):
130
self.interface = interface
132
self.type = servicetype
134
self.TXT = TXT if TXT is not None else []
137
self.rename_count = 0
138
self.max_renames = max_renames
139
self.protocol = protocol
141
"""Derived from the Avahi example code"""
142
if self.rename_count >= self.max_renames:
143
logger.critical(u"No suitable Zeroconf service name found"
144
u" after %i retries, exiting.",
146
raise AvahiServiceError(u"Too many renames")
147
self.name = server.GetAlternativeServiceName(self.name)
148
logger.info(u"Changing Zeroconf service name to %r ...",
150
syslogger.setFormatter(logging.Formatter
151
('Mandos (%s) [%%(process)d]:'
152
' %%(levelname)s: %%(message)s'
156
self.rename_count += 1
158
"""Derived from the Avahi example code"""
159
if group is not None:
162
"""Derived from the Avahi example code"""
165
group = dbus.Interface(bus.get_object
167
server.EntryGroupNew()),
168
avahi.DBUS_INTERFACE_ENTRY_GROUP)
169
group.connect_to_signal('StateChanged',
170
entry_group_state_changed)
171
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
172
service.name, service.type)
174
self.interface, # interface
175
self.protocol, # protocol
176
dbus.UInt32(0), # flags
177
self.name, self.type,
178
self.domain, self.host,
179
dbus.UInt16(self.port),
180
avahi.string_array_to_txt_array(self.TXT))
183
# From the Avahi example code:
184
group = None # our entry group
45
# This variable is used to optionally bind to a specified interface.
46
# It is a global variable to fit in with the other variables from the
47
# Avahi server example code.
48
serviceInterface = avahi.IF_UNSPEC
49
# From the Avahi server example code:
50
serviceName = "Mandos"
51
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
52
servicePort = None # Not known at startup
53
serviceTXT = [] # TXT record for the service
54
domain = "" # Domain to publish on, default to .local
55
host = "" # Host to publish records for, default to localhost
56
group = None #our entry group
57
rename_count = 12 # Counter so we only rename after collisions a
58
# sensible number of times
185
59
# End of Avahi example code
188
def _datetime_to_dbus(dt, variant_level=0):
189
"""Convert a UTC datetime.datetime() to a D-Bus type."""
190
return dbus.String(dt.isoformat(), variant_level=variant_level)
193
62
class Client(object):
194
63
"""A representation of a client host served by this server.
197
name: string; from the config file, used in log messages and
65
name: string; from the config file, used in log messages
199
66
fingerprint: string (40 or 32 hexadecimal digits); used to
200
67
uniquely identify the client
201
secret: bytestring; sent verbatim (over TLS) to client
202
host: string; available for use by the checker command
203
created: datetime.datetime(); (UTC) object creation
204
last_enabled: datetime.datetime(); (UTC)
206
last_checked_ok: datetime.datetime(); (UTC) or None
207
timeout: datetime.timedelta(); How long from last_checked_ok
208
until this client is invalid
209
interval: datetime.timedelta(); How often to start a new checker
210
disable_hook: If set, called by disable() as disable_hook(self)
211
checker: subprocess.Popen(); a running checker process used
212
to see if the client lives.
213
'None' if no process is running.
68
secret: bytestring; sent verbatim (over TLS) to client
69
fqdn: string (FQDN); available for use by the checker command
70
created: datetime.datetime()
71
last_seen: datetime.datetime() or None if not yet seen
72
timeout: datetime.timedelta(); How long from last_seen until
73
this client is invalid
74
interval: datetime.timedelta(); How often to start a new checker
75
stop_hook: If set, called by stop() as stop_hook(self)
76
checker: subprocess.Popen(); a running checker process used
77
to see if the client lives.
78
Is None if no process is running.
214
79
checker_initiator_tag: a gobject event source tag, or None
215
disable_initiator_tag: - '' -
80
stop_initiator_tag: - '' -
216
81
checker_callback_tag: - '' -
217
82
checker_command: string; External command which is run to check if
218
client lives. %() expansions are done at
83
client lives. %()s expansions are done at
219
84
runtime with vars(self) as dict, so that for
220
85
instance %(name)s can be used in the command.
221
current_checker_command: string; current running checker_command
87
_timeout: Real variable for 'timeout'
88
_interval: Real variable for 'interval'
89
_timeout_milliseconds: Used by gobject.timeout_add()
90
_interval_milliseconds: - '' -
223
def timeout_milliseconds(self):
224
"Return the 'timeout' attribute in milliseconds"
225
return ((self.timeout.days * 24 * 60 * 60 * 1000)
226
+ (self.timeout.seconds * 1000)
227
+ (self.timeout.microseconds // 1000))
229
def interval_milliseconds(self):
230
"Return the 'interval' attribute in milliseconds"
231
return ((self.interval.days * 24 * 60 * 60 * 1000)
232
+ (self.interval.seconds * 1000)
233
+ (self.interval.microseconds // 1000))
235
def __init__(self, name = None, disable_hook=None, config=None):
236
"""Note: the 'checker' key in 'config' sets the
237
'checker_command' attribute and *not* the 'checker'
92
def _set_timeout(self, timeout):
93
"Setter function for 'timeout' attribute"
94
self._timeout = timeout
95
self._timeout_milliseconds = ((self.timeout.days
96
* 24 * 60 * 60 * 1000)
97
+ (self.timeout.seconds * 1000)
98
+ (self.timeout.microseconds
100
timeout = property(lambda self: self._timeout,
103
def _set_interval(self, interval):
104
"Setter function for 'interval' attribute"
105
self._interval = interval
106
self._interval_milliseconds = ((self.interval.days
107
* 24 * 60 * 60 * 1000)
108
+ (self.interval.seconds
110
+ (self.interval.microseconds
112
interval = property(lambda self: self._interval,
115
def __init__(self, name=None, options=None, stop_hook=None,
116
fingerprint=None, secret=None, secfile=None,
117
fqdn=None, timeout=None, interval=-1, checker=None):
242
logger.debug(u"Creating client %r", self.name)
243
# Uppercase and remove spaces from fingerprint for later
244
# comparison purposes with return value from the fingerprint()
246
self.fingerprint = (config["fingerprint"].upper()
248
logger.debug(u" Fingerprint: %s", self.fingerprint)
249
if "secret" in config:
250
self.secret = config["secret"].decode(u"base64")
251
elif "secfile" in config:
252
with closing(open(os.path.expanduser
254
(config["secfile"])))) as secfile:
255
self.secret = secfile.read()
257
raise TypeError(u"No secret or secfile for client %s"
259
self.host = config.get("host", "")
260
self.created = datetime.datetime.utcnow()
262
self.last_enabled = None
263
self.last_checked_ok = None
264
self.timeout = string_to_delta(config["timeout"])
265
self.interval = string_to_delta(config["interval"])
266
self.disable_hook = disable_hook
119
# Uppercase and remove spaces from fingerprint
120
# for later comparison purposes with return value of
121
# the fingerprint() function
122
self.fingerprint = fingerprint.upper().replace(u" ", u"")
124
self.secret = secret.decode(u"base64")
127
self.secret = sf.read()
130
raise RuntimeError(u"No secret or secfile for client %s"
132
self.fqdn = fqdn # string
133
self.created = datetime.datetime.now()
134
self.last_seen = None
136
self.timeout = options.timeout
138
self.timeout = string_to_delta(timeout)
140
self.interval = options.interval
142
self.interval = string_to_delta(interval)
143
self.stop_hook = stop_hook
267
144
self.checker = None
268
145
self.checker_initiator_tag = None
269
self.disable_initiator_tag = None
146
self.stop_initiator_tag = None
270
147
self.checker_callback_tag = None
271
self.checker_command = config["checker"]
272
self.current_checker_command = None
273
self.last_connect = None
276
"""Start this client's checker and timeout hooks"""
277
self.last_enabled = datetime.datetime.utcnow()
148
self.check_command = checker
150
"""Start this clients checker and timeout hooks"""
278
151
# Schedule a new checker to be started an 'interval' from now,
279
152
# and every interval from then on.
280
self.checker_initiator_tag = (gobject.timeout_add
281
(self.interval_milliseconds(),
153
self.checker_initiator_tag = gobject.timeout_add\
154
(self._interval_milliseconds,
283
156
# Also start a new checker *right now*.
284
157
self.start_checker()
285
# Schedule a disable() when 'timeout' has passed
286
self.disable_initiator_tag = (gobject.timeout_add
287
(self.timeout_milliseconds(),
292
"""Disable this client."""
293
if not getattr(self, "enabled", False):
295
logger.info(u"Disabling client %s", self.name)
296
if getattr(self, "disable_initiator_tag", False):
297
gobject.source_remove(self.disable_initiator_tag)
298
self.disable_initiator_tag = None
299
if getattr(self, "checker_initiator_tag", False):
158
# Schedule a stop() when 'timeout' has passed
159
self.stop_initiator_tag = gobject.timeout_add\
160
(self._timeout_milliseconds,
164
The possibility that this client might be restarted is left
165
open, but not currently used."""
166
logger.debug(u"Stopping client %s", self.name)
168
if self.stop_initiator_tag:
169
gobject.source_remove(self.stop_initiator_tag)
170
self.stop_initiator_tag = None
171
if self.checker_initiator_tag:
300
172
gobject.source_remove(self.checker_initiator_tag)
301
173
self.checker_initiator_tag = None
302
174
self.stop_checker()
303
if self.disable_hook:
304
self.disable_hook(self)
306
177
# Do not run this again if called by a gobject.timeout_add
309
179
def __del__(self):
310
self.disable_hook = None
313
def checker_callback(self, pid, condition, command):
180
# Some code duplication here and in stop()
181
if hasattr(self, "stop_initiator_tag") \
182
and self.stop_initiator_tag:
183
gobject.source_remove(self.stop_initiator_tag)
184
self.stop_initiator_tag = None
185
if hasattr(self, "checker_initiator_tag") \
186
and self.checker_initiator_tag:
187
gobject.source_remove(self.checker_initiator_tag)
188
self.checker_initiator_tag = None
190
def checker_callback(self, pid, condition):
314
191
"""The checker has completed, so take appropriate actions."""
315
self.checker_callback_tag = None
317
if os.WIFEXITED(condition):
318
exitstatus = os.WEXITSTATUS(condition)
320
logger.info(u"Checker for %(name)s succeeded",
324
logger.info(u"Checker for %(name)s failed",
192
now = datetime.datetime.now()
193
if os.WIFEXITED(condition) \
194
and (os.WEXITSTATUS(condition) == 0):
195
logger.debug(u"Checker for %(name)s succeeded",
198
gobject.source_remove(self.stop_initiator_tag)
199
self.stop_initiator_tag = gobject.timeout_add\
200
(self._timeout_milliseconds,
202
elif not os.WIFEXITED(condition):
327
203
logger.warning(u"Checker for %(name)s crashed?",
330
def checked_ok(self):
331
"""Bump up the timeout for this client.
333
This should only be called when the client has been seen,
336
self.last_checked_ok = datetime.datetime.utcnow()
337
gobject.source_remove(self.disable_initiator_tag)
338
self.disable_initiator_tag = (gobject.timeout_add
339
(self.timeout_milliseconds(),
206
logger.debug(u"Checker for %(name)s failed",
209
self.checker_callback_tag = None
342
210
def start_checker(self):
343
211
"""Start a new checker subprocess if one is not running.
345
212
If a checker already exists, leave it running and do
347
# The reason for not killing a running checker is that if we
348
# did that, then if a checker (for some reason) started
349
# running slowly and taking more than 'interval' time, the
350
# client would inevitably timeout, since no checker would get
351
# a chance to run to completion. If we instead leave running
352
# checkers alone, the checker would have to take more time
353
# than 'timeout' for the client to be declared invalid, which
354
# is as it should be.
356
# If a checker exists, make sure it is not a zombie
357
if self.checker is not None:
358
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
360
logger.warning("Checker was a zombie")
361
gobject.source_remove(self.checker_callback_tag)
362
self.checker_callback(pid, status,
363
self.current_checker_command)
364
# Start a new checker if needed
365
214
if self.checker is None:
367
# In case checker_command has exactly one % operator
368
command = self.checker_command % self.host
216
command = self.check_command % self.fqdn
369
217
except TypeError:
370
# Escape attributes for the shell
371
218
escaped_attrs = dict((key, re.escape(str(val)))
373
220
vars(self).iteritems())
375
command = self.checker_command % escaped_attrs
222
command = self.check_command % escaped_attrs
376
223
except TypeError, error:
377
logger.error(u'Could not format string "%s":'
378
u' %s', self.checker_command, error)
224
logger.critical(u'Could not format string "%s":'
225
u' %s', self.check_command, error)
379
226
return True # Try again later
380
self.current_checker_command = command
382
logger.info(u"Starting checker %r for %s",
384
# We don't need to redirect stdout and stderr, since
385
# in normal mode, that is already done by daemon(),
386
# and in debug mode we don't want to. (Stdin is
387
# always replaced by /dev/null.)
388
self.checker = subprocess.Popen(command,
391
self.checker_callback_tag = (gobject.child_watch_add
393
self.checker_callback,
395
# The checker may have completed before the gobject
396
# watch was added. Check for this.
397
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
399
gobject.source_remove(self.checker_callback_tag)
400
self.checker_callback(pid, status, command)
401
except OSError, error:
228
logger.debug(u"Starting checker %r for %s",
230
self.checker = subprocess.\
232
close_fds=True, shell=True,
234
self.checker_callback_tag = gobject.child_watch_add\
236
self.checker_callback)
237
except subprocess.OSError, error:
402
238
logger.error(u"Failed to start subprocess: %s",
404
240
# Re-run this periodically if run by gobject.timeout_add
407
242
def stop_checker(self):
408
243
"""Force the checker process, if any, to stop."""
409
if self.checker_callback_tag:
410
gobject.source_remove(self.checker_callback_tag)
411
self.checker_callback_tag = None
412
if getattr(self, "checker", None) is None:
244
if not hasattr(self, "checker") or self.checker is None:
414
logger.debug(u"Stopping checker for %(name)s", vars(self))
416
os.kill(self.checker.pid, signal.SIGTERM)
418
#if self.checker.poll() is None:
419
# os.kill(self.checker.pid, signal.SIGKILL)
420
except OSError, error:
421
if error.errno != errno.ESRCH: # No such process
246
gobject.source_remove(self.checker_callback_tag)
247
self.checker_callback_tag = None
248
os.kill(self.checker.pid, signal.SIGTERM)
249
if self.checker.poll() is None:
250
os.kill(self.checker.pid, signal.SIGKILL)
423
251
self.checker = None
425
def still_valid(self):
252
def still_valid(self, now=None):
426
253
"""Has the timeout not yet passed for this client?"""
427
if not getattr(self, "enabled", False):
429
now = datetime.datetime.utcnow()
430
if self.last_checked_ok is None:
255
now = datetime.datetime.now()
256
if self.last_seen is None:
431
257
return now < (self.created + self.timeout)
433
return now < (self.last_checked_ok + self.timeout)
436
class ClientDBus(Client, dbus.service.Object):
437
"""A Client class using D-Bus
440
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
442
# dbus.service.Object doesn't use super(), so we can't either.
444
def __init__(self, *args, **kwargs):
445
Client.__init__(self, *args, **kwargs)
446
# Only now, when this client is initialized, can it show up on
448
self.dbus_object_path = (dbus.ObjectPath
450
+ self.name.replace(".", "_")))
451
dbus.service.Object.__init__(self, bus,
452
self.dbus_object_path)
454
oldstate = getattr(self, "enabled", False)
455
r = Client.enable(self)
456
if oldstate != self.enabled:
458
self.PropertyChanged(dbus.String(u"enabled"),
459
dbus.Boolean(True, variant_level=1))
460
self.PropertyChanged(dbus.String(u"last_enabled"),
461
(_datetime_to_dbus(self.last_enabled,
465
def disable(self, signal = True):
466
oldstate = getattr(self, "enabled", False)
467
r = Client.disable(self)
468
if signal and oldstate != self.enabled:
470
self.PropertyChanged(dbus.String(u"enabled"),
471
dbus.Boolean(False, variant_level=1))
474
def __del__(self, *args, **kwargs):
476
self.remove_from_connection()
479
if hasattr(dbus.service.Object, "__del__"):
480
dbus.service.Object.__del__(self, *args, **kwargs)
481
Client.__del__(self, *args, **kwargs)
483
def checker_callback(self, pid, condition, command,
485
self.checker_callback_tag = None
488
self.PropertyChanged(dbus.String(u"checker_running"),
489
dbus.Boolean(False, variant_level=1))
490
if os.WIFEXITED(condition):
491
exitstatus = os.WEXITSTATUS(condition)
493
self.CheckerCompleted(dbus.Int16(exitstatus),
494
dbus.Int64(condition),
495
dbus.String(command))
498
self.CheckerCompleted(dbus.Int16(-1),
499
dbus.Int64(condition),
500
dbus.String(command))
502
return Client.checker_callback(self, pid, condition, command,
505
def checked_ok(self, *args, **kwargs):
506
r = Client.checked_ok(self, *args, **kwargs)
508
self.PropertyChanged(
509
dbus.String(u"last_checked_ok"),
510
(_datetime_to_dbus(self.last_checked_ok,
514
def start_checker(self, *args, **kwargs):
515
old_checker = self.checker
516
if self.checker is not None:
517
old_checker_pid = self.checker.pid
519
old_checker_pid = None
520
r = Client.start_checker(self, *args, **kwargs)
521
# Only if new checker process was started
522
if (self.checker is not None
523
and old_checker_pid != self.checker.pid):
525
self.CheckerStarted(self.current_checker_command)
526
self.PropertyChanged(
527
dbus.String("checker_running"),
528
dbus.Boolean(True, variant_level=1))
531
def stop_checker(self, *args, **kwargs):
532
old_checker = getattr(self, "checker", None)
533
r = Client.stop_checker(self, *args, **kwargs)
534
if (old_checker is not None
535
and getattr(self, "checker", None) is None):
536
self.PropertyChanged(dbus.String(u"checker_running"),
537
dbus.Boolean(False, variant_level=1))
540
## D-Bus methods & signals
541
_interface = u"se.bsnet.fukt.Mandos.Client"
544
CheckedOK = dbus.service.method(_interface)(checked_ok)
545
CheckedOK.__name__ = "CheckedOK"
547
# CheckerCompleted - signal
548
@dbus.service.signal(_interface, signature="nxs")
549
def CheckerCompleted(self, exitcode, waitstatus, command):
553
# CheckerStarted - signal
554
@dbus.service.signal(_interface, signature="s")
555
def CheckerStarted(self, command):
559
# GetAllProperties - method
560
@dbus.service.method(_interface, out_signature="a{sv}")
561
def GetAllProperties(self):
563
return dbus.Dictionary({
565
dbus.String(self.name, variant_level=1),
566
dbus.String("fingerprint"):
567
dbus.String(self.fingerprint, variant_level=1),
569
dbus.String(self.host, variant_level=1),
570
dbus.String("created"):
571
_datetime_to_dbus(self.created, variant_level=1),
572
dbus.String("last_enabled"):
573
(_datetime_to_dbus(self.last_enabled,
575
if self.last_enabled is not None
576
else dbus.Boolean(False, variant_level=1)),
577
dbus.String("enabled"):
578
dbus.Boolean(self.enabled, variant_level=1),
579
dbus.String("last_checked_ok"):
580
(_datetime_to_dbus(self.last_checked_ok,
582
if self.last_checked_ok is not None
583
else dbus.Boolean (False, variant_level=1)),
584
dbus.String("timeout"):
585
dbus.UInt64(self.timeout_milliseconds(),
587
dbus.String("interval"):
588
dbus.UInt64(self.interval_milliseconds(),
590
dbus.String("checker"):
591
dbus.String(self.checker_command,
593
dbus.String("checker_running"):
594
dbus.Boolean(self.checker is not None,
596
dbus.String("object_path"):
597
dbus.ObjectPath(self.dbus_object_path,
601
# IsStillValid - method
602
@dbus.service.method(_interface, out_signature="b")
603
def IsStillValid(self):
604
return self.still_valid()
606
# PropertyChanged - signal
607
@dbus.service.signal(_interface, signature="sv")
608
def PropertyChanged(self, property, value):
612
# ReceivedSecret - signal
613
@dbus.service.signal(_interface)
614
def ReceivedSecret(self):
619
@dbus.service.signal(_interface)
624
# SetChecker - method
625
@dbus.service.method(_interface, in_signature="s")
626
def SetChecker(self, checker):
627
"D-Bus setter method"
628
self.checker_command = checker
630
self.PropertyChanged(dbus.String(u"checker"),
631
dbus.String(self.checker_command,
635
@dbus.service.method(_interface, in_signature="s")
636
def SetHost(self, host):
637
"D-Bus setter method"
640
self.PropertyChanged(dbus.String(u"host"),
641
dbus.String(self.host, variant_level=1))
643
# SetInterval - method
644
@dbus.service.method(_interface, in_signature="t")
645
def SetInterval(self, milliseconds):
646
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
648
self.PropertyChanged(dbus.String(u"interval"),
649
(dbus.UInt64(self.interval_milliseconds(),
653
@dbus.service.method(_interface, in_signature="ay",
655
def SetSecret(self, secret):
656
"D-Bus setter method"
657
self.secret = str(secret)
659
# SetTimeout - method
660
@dbus.service.method(_interface, in_signature="t")
661
def SetTimeout(self, milliseconds):
662
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
664
self.PropertyChanged(dbus.String(u"timeout"),
665
(dbus.UInt64(self.timeout_milliseconds(),
669
Enable = dbus.service.method(_interface)(enable)
670
Enable.__name__ = "Enable"
672
# StartChecker - method
673
@dbus.service.method(_interface)
674
def StartChecker(self):
679
@dbus.service.method(_interface)
684
# StopChecker - method
685
StopChecker = dbus.service.method(_interface)(stop_checker)
686
StopChecker.__name__ = "StopChecker"
691
class ClientHandler(SocketServer.BaseRequestHandler, object):
692
"""A class to handle client connections.
694
Instantiated once for each connection to handle it.
259
return now < (self.last_seen + self.timeout)
262
def peer_certificate(session):
263
"Return an OpenPGP data packet string for the peer's certificate"
264
# If not an OpenPGP certificate...
265
if gnutls.library.functions.gnutls_certificate_type_get\
266
(session._c_object) \
267
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
268
# ...do the normal thing
269
return session.peer_certificate
270
list_size = ctypes.c_uint()
271
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
272
(session._c_object, ctypes.byref(list_size))
273
if list_size.value == 0:
276
return ctypes.string_at(cert.data, cert.size)
279
def fingerprint(openpgp):
280
"Convert an OpenPGP data string to a hexdigit fingerprint string"
281
# New empty GnuTLS certificate
282
crt = gnutls.library.types.gnutls_openpgp_crt_t()
283
gnutls.library.functions.gnutls_openpgp_crt_init\
285
# New GnuTLS "datum" with the OpenPGP public key
286
datum = gnutls.library.types.gnutls_datum_t\
287
(ctypes.cast(ctypes.c_char_p(openpgp),
288
ctypes.POINTER(ctypes.c_ubyte)),
289
ctypes.c_uint(len(openpgp)))
290
# Import the OpenPGP public key into the certificate
291
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
294
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
295
# New buffer for the fingerprint
296
buffer = ctypes.create_string_buffer(20)
297
buffer_length = ctypes.c_size_t()
298
# Get the fingerprint from the certificate into the buffer
299
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
300
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
301
# Deinit the certificate
302
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
303
# Convert the buffer to a Python bytestring
304
fpr = ctypes.string_at(buffer, buffer_length.value)
305
# Convert the bytestring to hexadecimal notation
306
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
310
class tcp_handler(SocketServer.BaseRequestHandler, object):
311
"""A TCP request handler class.
312
Instantiated by IPv6_TCPServer for each request to handle it.
695
313
Note: This will run in its own forked process."""
697
315
def handle(self):
698
logger.info(u"TCP connection from: %s",
699
unicode(self.client_address))
700
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
701
# Open IPC pipe to parent process
702
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
703
session = (gnutls.connection
704
.ClientSession(self.request,
708
line = self.request.makefile().readline()
709
logger.debug(u"Protocol version: %r", line)
711
if int(line.strip().split()[0]) > 1:
713
except (ValueError, IndexError, RuntimeError), error:
714
logger.error(u"Unknown protocol version: %s", error)
717
# Note: gnutls.connection.X509Credentials is really a
718
# generic GnuTLS certificate credentials object so long as
719
# no X.509 keys are added to it. Therefore, we can use it
720
# here despite using OpenPGP certificates.
722
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
723
# "+AES-256-CBC", "+SHA1",
724
# "+COMP-NULL", "+CTYPE-OPENPGP",
726
# Use a fallback default, since this MUST be set.
727
priority = self.server.gnutls_priority
730
(gnutls.library.functions
731
.gnutls_priority_set_direct(session._c_object,
736
except gnutls.errors.GNUTLSError, error:
737
logger.warning(u"Handshake failed: %s", error)
738
# Do not run session.bye() here: the session is not
739
# established. Just abandon the request.
741
logger.debug(u"Handshake succeeded")
743
fpr = self.fingerprint(self.peer_certificate(session))
744
except (TypeError, gnutls.errors.GNUTLSError), error:
745
logger.warning(u"Bad certificate: %s", error)
748
logger.debug(u"Fingerprint: %s", fpr)
750
for c in self.server.clients:
751
if c.fingerprint == fpr:
316
logger.debug(u"TCP connection from: %s",
317
unicode(self.client_address))
318
session = gnutls.connection.ClientSession(self.request,
322
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
323
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
325
priority = "SECURE256"
327
gnutls.library.functions.gnutls_priority_set_direct\
328
(session._c_object, priority, None);
332
except gnutls.errors.GNUTLSError, error:
333
logger.debug(u"Handshake failed: %s", error)
334
# Do not run session.bye() here: the session is not
335
# established. Just abandon the request.
338
fpr = fingerprint(peer_certificate(session))
339
except (TypeError, gnutls.errors.GNUTLSError), error:
340
logger.debug(u"Bad certificate: %s", error)
343
logger.debug(u"Fingerprint: %s", fpr)
346
if c.fingerprint == fpr:
349
# Have to check if client.still_valid(), since it is possible
350
# that the client timed out while establishing the GnuTLS
352
if (not client) or (not client.still_valid()):
354
logger.debug(u"Client %(name)s is invalid",
755
ipc.write("NOTFOUND %s\n" % fpr)
758
# Have to check if client.still_valid(), since it is
759
# possible that the client timed out while establishing
760
# the GnuTLS session.
761
if not client.still_valid():
762
ipc.write("INVALID %s\n" % client.name)
765
ipc.write("SENDING %s\n" % client.name)
767
while sent_size < len(client.secret):
768
sent = session.send(client.secret[sent_size:])
769
logger.debug(u"Sent: %d, remaining: %d",
770
sent, len(client.secret)
771
- (sent_size + sent))
357
logger.debug(u"Client not found for fingerprint: %s",
776
def peer_certificate(session):
777
"Return the peer's OpenPGP certificate as a bytestring"
778
# If not an OpenPGP certificate...
779
if (gnutls.library.functions
780
.gnutls_certificate_type_get(session._c_object)
781
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
782
# ...do the normal thing
783
return session.peer_certificate
784
list_size = ctypes.c_uint(1)
785
cert_list = (gnutls.library.functions
786
.gnutls_certificate_get_peers
787
(session._c_object, ctypes.byref(list_size)))
788
if not bool(cert_list) and list_size.value != 0:
789
raise gnutls.errors.GNUTLSError("error getting peer"
791
if list_size.value == 0:
794
return ctypes.string_at(cert.data, cert.size)
797
def fingerprint(openpgp):
798
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
799
# New GnuTLS "datum" with the OpenPGP public key
800
datum = (gnutls.library.types
801
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
804
ctypes.c_uint(len(openpgp))))
805
# New empty GnuTLS certificate
806
crt = gnutls.library.types.gnutls_openpgp_crt_t()
807
(gnutls.library.functions
808
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
809
# Import the OpenPGP public key into the certificate
810
(gnutls.library.functions
811
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
812
gnutls.library.constants
813
.GNUTLS_OPENPGP_FMT_RAW))
814
# Verify the self signature in the key
815
crtverify = ctypes.c_uint()
816
(gnutls.library.functions
817
.gnutls_openpgp_crt_verify_self(crt, 0,
818
ctypes.byref(crtverify)))
819
if crtverify.value != 0:
820
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
821
raise (gnutls.errors.CertificateSecurityError
823
# New buffer for the fingerprint
824
buf = ctypes.create_string_buffer(20)
825
buf_len = ctypes.c_size_t()
826
# Get the fingerprint from the certificate into the buffer
827
(gnutls.library.functions
828
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
829
ctypes.byref(buf_len)))
830
# Deinit the certificate
831
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
832
# Convert the buffer to a Python bytestring
833
fpr = ctypes.string_at(buf, buf_len.value)
834
# Convert the bytestring to hexadecimal notation
835
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
839
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
840
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
842
Assumes a gobject.MainLoop event loop.
844
def process_request(self, request, client_address):
845
"""Overrides and wraps the original process_request().
847
This function creates a new pipe in self.pipe
849
self.pipe = os.pipe()
850
super(ForkingMixInWithPipe,
851
self).process_request(request, client_address)
852
os.close(self.pipe[1]) # close write end
853
# Call "handle_ipc" for both data and EOF events
854
gobject.io_add_watch(self.pipe[0],
855
gobject.IO_IN | gobject.IO_HUP,
857
def handle_ipc(source, condition):
858
"""Dummy function; override as necessary"""
863
class IPv6_TCPServer(ForkingMixInWithPipe,
864
SocketServer.TCPServer, object):
865
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
362
while sent_size < len(client.secret):
363
sent = session.send(client.secret[sent_size:])
364
logger.debug(u"Sent: %d, remaining: %d",
365
sent, len(client.secret)
366
- (sent_size + sent))
371
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
372
"""IPv6 TCP server. Accepts 'None' as address and/or port.
868
enabled: Boolean; whether this server is activated yet
869
interface: None or a network interface name (string)
870
use_ipv6: Boolean; to use IPv6 or not
374
options: Command line options
872
375
clients: Set() of Client objects
873
gnutls_priority GnuTLS priority string
874
use_dbus: Boolean; to emit D-Bus signals or not
876
def __init__(self, server_address, RequestHandlerClass,
877
interface=None, use_ipv6=True, clients=None,
878
gnutls_priority=None, use_dbus=True):
880
self.interface = interface
882
self.address_family = socket.AF_INET6
883
self.clients = clients
884
self.use_dbus = use_dbus
885
self.gnutls_priority = gnutls_priority
886
SocketServer.TCPServer.__init__(self, server_address,
377
address_family = socket.AF_INET6
378
def __init__(self, *args, **kwargs):
379
if "options" in kwargs:
380
self.options = kwargs["options"]
381
del kwargs["options"]
382
if "clients" in kwargs:
383
self.clients = kwargs["clients"]
384
del kwargs["clients"]
385
return super(type(self), self).__init__(*args, **kwargs)
888
386
def server_bind(self):
889
387
"""This overrides the normal server_bind() function
890
388
to bind to an interface if one was specified, and also NOT to
891
389
bind to an address or port if they were not specified."""
892
if self.interface is not None:
390
if self.options.interface:
391
if not hasattr(socket, "SO_BINDTODEVICE"):
392
# From /usr/include/asm-i486/socket.h
393
socket.SO_BINDTODEVICE = 25
894
395
self.socket.setsockopt(socket.SOL_SOCKET,
896
self.interface + '\0')
396
socket.SO_BINDTODEVICE,
397
self.options.interface)
897
398
except socket.error, error:
898
399
if error[0] == errno.EPERM:
899
logger.error(u"No permission to"
900
u" bind to interface %s",
400
logger.warning(u"No permission to"
401
u" bind to interface %s",
402
self.options.interface)
904
405
# Only bind(2) the socket if we really need to.
905
406
if self.server_address[0] or self.server_address[1]:
906
407
if not self.server_address[0]:
907
if self.address_family == socket.AF_INET6:
908
any_address = "::" # in6addr_any
910
any_address = socket.INADDR_ANY
911
self.server_address = (any_address,
409
self.server_address = (in6addr_any,
912
410
self.server_address[1])
913
elif not self.server_address[1]:
411
elif self.server_address[1] is None:
914
412
self.server_address = (self.server_address[0],
917
# self.server_address = (self.server_address[0],
922
return SocketServer.TCPServer.server_bind(self)
923
def server_activate(self):
925
return SocketServer.TCPServer.server_activate(self)
928
def handle_ipc(self, source, condition, file_objects={}):
930
gobject.IO_IN: "IN", # There is data to read.
931
gobject.IO_OUT: "OUT", # Data can be written (without
933
gobject.IO_PRI: "PRI", # There is urgent data to read.
934
gobject.IO_ERR: "ERR", # Error condition.
935
gobject.IO_HUP: "HUP" # Hung up (the connection has been
936
# broken, usually for pipes and
939
conditions_string = ' | '.join(name
941
condition_names.iteritems()
943
logger.debug("Handling IPC: FD = %d, condition = %s", source,
946
# Turn the pipe file descriptor into a Python file object
947
if source not in file_objects:
948
file_objects[source] = os.fdopen(source, "r", 1)
950
# Read a line from the file object
951
cmdline = file_objects[source].readline()
952
if not cmdline: # Empty line means end of file
954
file_objects[source].close()
955
del file_objects[source]
957
# Stop calling this function
960
logger.debug("IPC command: %r", cmdline)
962
# Parse and act on command
963
cmd, args = cmdline.rstrip("\r\n").split(None, 1)
965
if cmd == "NOTFOUND":
966
logger.warning(u"Client not found for fingerprint: %s",
970
mandos_dbus_service.ClientNotFound(args)
971
elif cmd == "INVALID":
972
for client in self.clients:
973
if client.name == args:
974
logger.warning(u"Client %s is invalid", args)
980
logger.error(u"Unknown client %s is invalid", args)
981
elif cmd == "SENDING":
982
for client in self.clients:
983
if client.name == args:
984
logger.info(u"Sending secret to %s", client.name)
988
client.ReceivedSecret()
991
logger.error(u"Sending secret to unknown client %s",
994
logger.error("Unknown IPC command: %r", cmdline)
996
# Keep calling this function
414
return super(type(self), self).server_bind()
1000
417
def string_to_delta(interval):
1001
418
"""Parse a string and return a datetime.timedelta
1003
420
>>> string_to_delta('7d')
1004
421
datetime.timedelta(7)
1005
422
>>> string_to_delta('60s')
1111
######################################################################
1112
# Parsing of options, both command line and config file
1114
parser = optparse.OptionParser(version = "%%prog %s" % version)
561
def killme(status = 0):
562
logger.debug("Stopping server with exit status %d", status)
564
if main_loop_started:
570
if __name__ == '__main__':
572
main_loop_started = False
573
parser = OptionParser()
1115
574
parser.add_option("-i", "--interface", type="string",
1116
metavar="IF", help="Bind to interface IF")
1117
parser.add_option("-a", "--address", type="string",
1118
help="Address to listen for requests on")
1119
parser.add_option("-p", "--port", type="int",
575
default=None, metavar="IF",
576
help="Bind to interface IF")
577
parser.add_option("-p", "--port", type="int", default=None,
1120
578
help="Port number to receive requests on")
1121
parser.add_option("--check", action="store_true",
579
parser.add_option("--timeout", type="string", # Parsed later
581
help="Amount of downtime allowed for clients")
582
parser.add_option("--interval", type="string", # Parsed later
584
help="How often to check that a client is up")
585
parser.add_option("--check", action="store_true", default=False,
1122
586
help="Run self-test")
1123
parser.add_option("--debug", action="store_true",
1124
help="Debug mode; run in foreground and log to"
1126
parser.add_option("--priority", type="string", help="GnuTLS"
1127
" priority string (see GnuTLS documentation)")
1128
parser.add_option("--servicename", type="string", metavar="NAME",
1129
help="Zeroconf service name")
1130
parser.add_option("--configdir", type="string",
1131
default="/etc/mandos", metavar="DIR",
1132
help="Directory to search for configuration"
1134
parser.add_option("--no-dbus", action="store_false",
1136
help="Do not provide D-Bus system bus"
1138
parser.add_option("--no-ipv6", action="store_false",
1139
dest="use_ipv6", help="Do not use IPv6")
1140
options = parser.parse_args()[0]
587
parser.add_option("--debug", action="store_true", default=False,
589
(options, args) = parser.parse_args()
1142
591
if options.check:
1144
593
doctest.testmod()
1147
# Default values for config file for server-global settings
1148
server_defaults = { "interface": "",
1153
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1154
"servicename": "Mandos",
1159
# Parse config file for server-global settings
1160
server_config = ConfigParser.SafeConfigParser(server_defaults)
1162
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1163
# Convert the SafeConfigParser object to a dict
1164
server_settings = server_config.defaults()
1165
# Use the appropriate methods on the non-string config options
1166
server_settings["debug"] = server_config.getboolean("DEFAULT",
1168
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1170
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1172
if server_settings["port"]:
1173
server_settings["port"] = server_config.getint("DEFAULT",
1177
# Override the settings from the config file with command line
1179
for option in ("interface", "address", "port", "debug",
1180
"priority", "servicename", "configdir",
1181
"use_dbus", "use_ipv6"):
1182
value = getattr(options, option)
1183
if value is not None:
1184
server_settings[option] = value
1186
# Now we have our good server settings in "server_settings"
1188
##################################################################
1191
debug = server_settings["debug"]
1192
use_dbus = server_settings["use_dbus"]
1193
use_ipv6 = server_settings["use_ipv6"]
1196
syslogger.setLevel(logging.WARNING)
1197
console.setLevel(logging.WARNING)
1199
if server_settings["servicename"] != "Mandos":
1200
syslogger.setFormatter(logging.Formatter
1201
('Mandos (%s) [%%(process)d]:'
1202
' %%(levelname)s: %%(message)s'
1203
% server_settings["servicename"]))
1205
# Parse config file with clients
1206
client_defaults = { "timeout": "1h",
1208
"checker": "fping -q -- %%(host)s",
1211
client_config = ConfigParser.SafeConfigParser(client_defaults)
1212
client_config.read(os.path.join(server_settings["configdir"],
1215
global mandos_dbus_service
1216
mandos_dbus_service = None
1219
tcp_server = IPv6_TCPServer((server_settings["address"],
1220
server_settings["port"]),
1223
server_settings["interface"],
1227
server_settings["priority"],
1229
pidfilename = "/var/run/mandos.pid"
1231
pidfile = open(pidfilename, "w")
1233
logger.error("Could not open file %r", pidfilename)
1236
uid = pwd.getpwnam("_mandos").pw_uid
1237
gid = pwd.getpwnam("_mandos").pw_gid
1240
uid = pwd.getpwnam("mandos").pw_uid
1241
gid = pwd.getpwnam("mandos").pw_gid
1244
uid = pwd.getpwnam("nobody").pw_uid
1245
gid = pwd.getpwnam("nogroup").pw_gid
1252
except OSError, error:
1253
if error[0] != errno.EPERM:
1256
# Enable all possible GnuTLS debugging
1258
# "Use a log level over 10 to enable all debugging options."
1260
gnutls.library.functions.gnutls_global_set_log_level(11)
1262
@gnutls.library.types.gnutls_log_func
1263
def debug_gnutls(level, string):
1264
logger.debug("GnuTLS: %s", string[:-1])
1266
(gnutls.library.functions
1267
.gnutls_global_set_log_function(debug_gnutls))
1270
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1271
service = AvahiService(name = server_settings["servicename"],
1272
servicetype = "_mandos._tcp",
1273
protocol = protocol)
1274
if server_settings["interface"]:
1275
service.interface = (if_nametoindex
1276
(server_settings["interface"]))
1281
# From the Avahi example code
596
# Parse the time arguments
598
options.timeout = string_to_delta(options.timeout)
600
parser.error("option --timeout: Unparseable time")
602
options.interval = string_to_delta(options.interval)
604
parser.error("option --interval: Unparseable time")
607
defaults = { "checker": "fping -q -- %%(fqdn)s" }
608
client_config = ConfigParser.SafeConfigParser(defaults)
609
#client_config.readfp(open("secrets.conf"), "secrets.conf")
610
client_config.read("mandos-clients.conf")
612
# From the Avahi server example code
1282
613
DBusGMainLoop(set_as_default=True )
1283
614
main_loop = gobject.MainLoop()
1284
615
bus = dbus.SystemBus()
1285
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1286
avahi.DBUS_PATH_SERVER),
1287
avahi.DBUS_INTERFACE_SERVER)
616
server = dbus.Interface(
617
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
618
avahi.DBUS_INTERFACE_SERVER )
1288
619
# End of Avahi example code
1290
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1292
client_class = Client
1294
client_class = ClientDBus
1296
client_class(name = section,
1297
config= dict(client_config.items(section)))
1298
for section in client_config.sections()))
1300
logger.warning(u"No clients defined")
621
debug = options.debug
1303
# Redirect stdin so all checkers get /dev/null
1304
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1305
os.dup2(null, sys.stdin.fileno())
1309
# No console logging
1310
logger.removeHandler(console)
1311
# Close all input and output, do double fork, etc.
1315
with closing(pidfile):
1317
pidfile.write(str(pid) + "\n")
1320
logger.error(u"Could not write to file %r with PID %d",
1323
# "pidfile" was never created
624
console = logging.StreamHandler()
625
# console.setLevel(logging.DEBUG)
626
console.setFormatter(logging.Formatter\
627
('%(levelname)s: %(message)s'))
628
logger.addHandler(console)
632
def remove_from_clients(client):
633
clients.remove(client)
635
logger.debug(u"No clients left, exiting")
638
clients.update(Set(Client(name=section, options=options,
639
stop_hook = remove_from_clients,
640
**(dict(client_config\
642
for section in client_config.sections()))
1328
648
"Cleanup function; run on exit"
1330
# From the Avahi example code
650
# From the Avahi server example code
1331
651
if not group is None:
1334
654
# End of Avahi example code
1337
client = clients.pop()
1338
client.disable_hook = None
656
for client in clients:
657
client.stop_hook = None
1341
660
atexit.register(cleanup)
1344
663
signal.signal(signal.SIGINT, signal.SIG_IGN)
1345
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1346
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1349
class MandosDBusService(dbus.service.Object):
1350
"""A D-Bus proxy object"""
1352
dbus.service.Object.__init__(self, bus, "/")
1353
_interface = u"se.bsnet.fukt.Mandos"
1355
@dbus.service.signal(_interface, signature="oa{sv}")
1356
def ClientAdded(self, objpath, properties):
1360
@dbus.service.signal(_interface, signature="s")
1361
def ClientNotFound(self, fingerprint):
1365
@dbus.service.signal(_interface, signature="os")
1366
def ClientRemoved(self, objpath, name):
1370
@dbus.service.method(_interface, out_signature="ao")
1371
def GetAllClients(self):
1373
return dbus.Array(c.dbus_object_path for c in clients)
1375
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1376
def GetAllClientsWithProperties(self):
1378
return dbus.Dictionary(
1379
((c.dbus_object_path, c.GetAllProperties())
1383
@dbus.service.method(_interface, in_signature="o")
1384
def RemoveClient(self, object_path):
1387
if c.dbus_object_path == object_path:
1389
c.remove_from_connection()
1390
# Don't signal anything except ClientRemoved
1391
c.disable(signal=False)
1393
self.ClientRemoved(object_path, c.name)
1399
mandos_dbus_service = MandosDBusService()
664
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
665
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
1401
667
for client in clients:
1404
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1405
client.GetAllProperties())
1409
tcp_server.server_activate()
1411
# Find out what port we got
1412
service.port = tcp_server.socket.getsockname()[1]
1414
logger.info(u"Now listening on address %r, port %d,"
1415
" flowinfo %d, scope_id %d"
1416
% tcp_server.socket.getsockname())
1418
logger.info(u"Now listening on address %r, port %d"
1419
% tcp_server.socket.getsockname())
1421
#service.interface = tcp_server.socket.getsockname()[3]
1424
# From the Avahi example code
1425
server.connect_to_signal("StateChanged", server_state_changed)
1427
server_state_changed(server.GetState())
1428
except dbus.exceptions.DBusException, error:
1429
logger.critical(u"DBusException: %s", error)
1431
# End of Avahi example code
1433
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1434
lambda *args, **kwargs:
1435
(tcp_server.handle_request
1436
(*args[2:], **kwargs) or True))
1438
logger.debug(u"Starting main loop")
670
tcp_server = IPv6_TCPServer((None, options.port),
674
# Find out what random port we got
675
servicePort = tcp_server.socket.getsockname()[1]
676
logger.debug(u"Now listening on port %d", servicePort)
678
if options.interface is not None:
679
serviceInterface = if_nametoindex(options.interface)
681
# From the Avahi server example code
682
server.connect_to_signal("StateChanged", server_state_changed)
684
server_state_changed(server.GetState())
685
except dbus.exceptions.DBusException, error:
686
logger.critical(u"DBusException: %s", error)
688
# End of Avahi example code
690
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
691
lambda *args, **kwargs:
692
tcp_server.handle_request(*args[2:],
695
main_loop_started = True
1440
except AvahiError, error:
1441
logger.critical(u"AvahiError: %s", error)
1443
697
except KeyboardInterrupt:
1446
logger.debug("Server received KeyboardInterrupt")
1447
logger.debug("Server exiting")
1449
if __name__ == '__main__':