57
import logging.handlers
59
from contextlib import closing
65
31
from dbus.mainloop.glib import DBusGMainLoop
35
import logging.handlers
71
37
logger = logging.Logger('mandos')
72
syslogger = (logging.handlers.SysLogHandler
73
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
74
address = "/dev/log"))
75
syslogger.setFormatter(logging.Formatter
76
('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'))
78
42
logger.addHandler(syslogger)
80
console = logging.StreamHandler()
81
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
82
' %(levelname)s: %(message)s'))
83
logger.addHandler(console)
85
class AvahiError(Exception):
86
def __init__(self, value, *args, **kwargs):
88
super(AvahiError, self).__init__(value, *args, **kwargs)
89
def __unicode__(self):
90
return unicode(repr(self.value))
92
class AvahiServiceError(AvahiError):
95
class AvahiGroupError(AvahiError):
99
class AvahiService(object):
100
"""An Avahi (Zeroconf) service.
102
interface: integer; avahi.IF_UNSPEC or an interface index.
103
Used to optionally bind to the specified interface.
104
name: string; Example: 'Mandos'
105
type: string; Example: '_mandos._tcp'.
106
See <http://www.dns-sd.org/ServiceTypes.html>
107
port: integer; what port to announce
108
TXT: list of strings; TXT record for the service
109
domain: string; Domain to publish on, default to .local if empty.
110
host: string; Host to publish records for, default is localhost
111
max_renames: integer; maximum number of renames
112
rename_count: integer; counter so we only rename after collisions
113
a sensible number of times
115
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
116
servicetype = None, port = None, TXT = None,
117
domain = "", host = "", max_renames = 32768,
118
protocol = avahi.PROTO_UNSPEC):
119
self.interface = interface
121
self.type = servicetype
123
self.TXT = TXT if TXT is not None else []
126
self.rename_count = 0
127
self.max_renames = max_renames
128
self.protocol = protocol
130
"""Derived from the Avahi example code"""
131
if self.rename_count >= self.max_renames:
132
logger.critical(u"No suitable Zeroconf service name found"
133
u" after %i retries, exiting.",
135
raise AvahiServiceError(u"Too many renames")
136
self.name = server.GetAlternativeServiceName(self.name)
137
logger.info(u"Changing Zeroconf service name to %r ...",
139
syslogger.setFormatter(logging.Formatter
140
('Mandos (%s) [%%(process)d]:'
141
' %%(levelname)s: %%(message)s'
145
self.rename_count += 1
147
"""Derived from the Avahi example code"""
148
if group is not None:
151
"""Derived from the Avahi example code"""
154
group = dbus.Interface(bus.get_object
156
server.EntryGroupNew()),
157
avahi.DBUS_INTERFACE_ENTRY_GROUP)
158
group.connect_to_signal('StateChanged',
159
entry_group_state_changed)
160
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
161
service.name, service.type)
163
self.interface, # interface
164
self.protocol, # protocol
165
dbus.UInt32(0), # flags
166
self.name, self.type,
167
self.domain, self.host,
168
dbus.UInt16(self.port),
169
avahi.string_array_to_txt_array(self.TXT))
172
# From the Avahi example code:
173
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
174
59
# End of Avahi example code
177
def _datetime_to_dbus(dt, variant_level=0):
178
"""Convert a UTC datetime.datetime() to a D-Bus type."""
179
return dbus.String(dt.isoformat(), variant_level=variant_level)
182
class Client(dbus.service.Object):
183
63
"""A representation of a client host served by this server.
185
name: string; from the config file, used in log messages and
65
name: string; from the config file, used in log messages
187
66
fingerprint: string (40 or 32 hexadecimal digits); used to
188
67
uniquely identify the client
189
secret: bytestring; sent verbatim (over TLS) to client
190
host: string; available for use by the checker command
191
created: datetime.datetime(); (UTC) object creation
192
last_enabled: datetime.datetime(); (UTC)
194
last_checked_ok: datetime.datetime(); (UTC) or None
195
timeout: datetime.timedelta(); How long from last_checked_ok
196
until this client is invalid
197
interval: datetime.timedelta(); How often to start a new checker
198
disable_hook: If set, called by disable() as disable_hook(self)
199
checker: subprocess.Popen(); a running checker process used
200
to see if the client lives.
201
'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.
202
79
checker_initiator_tag: a gobject event source tag, or None
203
disable_initiator_tag: - '' -
80
stop_initiator_tag: - '' -
204
81
checker_callback_tag: - '' -
205
82
checker_command: string; External command which is run to check if
206
client lives. %() expansions are done at
83
client lives. %()s expansions are done at
207
84
runtime with vars(self) as dict, so that for
208
85
instance %(name)s can be used in the command.
209
current_checker_command: string; current running checker_command
210
use_dbus: bool(); Whether to provide D-Bus interface and signals
211
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
87
_timeout: Real variable for 'timeout'
88
_interval: Real variable for 'interval'
89
_timeout_milliseconds: Used by gobject.timeout_add()
90
_interval_milliseconds: - '' -
213
def timeout_milliseconds(self):
214
"Return the 'timeout' attribute in milliseconds"
215
return ((self.timeout.days * 24 * 60 * 60 * 1000)
216
+ (self.timeout.seconds * 1000)
217
+ (self.timeout.microseconds // 1000))
219
def interval_milliseconds(self):
220
"Return the 'interval' attribute in milliseconds"
221
return ((self.interval.days * 24 * 60 * 60 * 1000)
222
+ (self.interval.seconds * 1000)
223
+ (self.interval.microseconds // 1000))
225
def __init__(self, name = None, disable_hook=None, config=None,
227
"""Note: the 'checker' key in 'config' sets the
228
'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):
233
logger.debug(u"Creating client %r", self.name)
234
self.use_dbus = False # During __init__
235
# Uppercase and remove spaces from fingerprint for later
236
# comparison purposes with return value from the fingerprint()
238
self.fingerprint = (config["fingerprint"].upper()
240
logger.debug(u" Fingerprint: %s", self.fingerprint)
241
if "secret" in config:
242
self.secret = config["secret"].decode(u"base64")
243
elif "secfile" in config:
244
with closing(open(os.path.expanduser
246
(config["secfile"])))) as secfile:
247
self.secret = secfile.read()
249
raise TypeError(u"No secret or secfile for client %s"
251
self.host = config.get("host", "")
252
self.created = datetime.datetime.utcnow()
254
self.last_enabled = None
255
self.last_checked_ok = None
256
self.timeout = string_to_delta(config["timeout"])
257
self.interval = string_to_delta(config["interval"])
258
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
259
144
self.checker = None
260
145
self.checker_initiator_tag = None
261
self.disable_initiator_tag = None
146
self.stop_initiator_tag = None
262
147
self.checker_callback_tag = None
263
self.checker_command = config["checker"]
264
self.current_checker_command = None
265
self.last_connect = None
266
# Only now, when this client is initialized, can it show up on
268
self.use_dbus = use_dbus
270
self.dbus_object_path = (dbus.ObjectPath
272
+ self.name.replace(".", "_")))
273
dbus.service.Object.__init__(self, bus,
274
self.dbus_object_path)
277
"""Start this client's checker and timeout hooks"""
278
self.last_enabled = datetime.datetime.utcnow()
148
self.check_command = checker
150
"""Start this clients checker and timeout hooks"""
279
151
# Schedule a new checker to be started an 'interval' from now,
280
152
# and every interval from then on.
281
self.checker_initiator_tag = (gobject.timeout_add
282
(self.interval_milliseconds(),
153
self.checker_initiator_tag = gobject.timeout_add\
154
(self._interval_milliseconds,
284
156
# Also start a new checker *right now*.
285
157
self.start_checker()
286
# Schedule a disable() when 'timeout' has passed
287
self.disable_initiator_tag = (gobject.timeout_add
288
(self.timeout_milliseconds(),
293
self.PropertyChanged(dbus.String(u"enabled"),
294
dbus.Boolean(True, variant_level=1))
295
self.PropertyChanged(dbus.String(u"last_enabled"),
296
(_datetime_to_dbus(self.last_enabled,
300
"""Disable this client."""
301
if not getattr(self, "enabled", False):
303
logger.info(u"Disabling client %s", self.name)
304
if getattr(self, "disable_initiator_tag", False):
305
gobject.source_remove(self.disable_initiator_tag)
306
self.disable_initiator_tag = None
307
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:
308
172
gobject.source_remove(self.checker_initiator_tag)
309
173
self.checker_initiator_tag = None
310
174
self.stop_checker()
311
if self.disable_hook:
312
self.disable_hook(self)
316
self.PropertyChanged(dbus.String(u"enabled"),
317
dbus.Boolean(False, variant_level=1))
318
177
# Do not run this again if called by a gobject.timeout_add
321
179
def __del__(self):
322
self.disable_hook = None
325
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):
326
191
"""The checker has completed, so take appropriate actions."""
327
self.checker_callback_tag = None
331
self.PropertyChanged(dbus.String(u"checker_running"),
332
dbus.Boolean(False, variant_level=1))
333
if os.WIFEXITED(condition):
334
exitstatus = os.WEXITSTATUS(condition)
336
logger.info(u"Checker for %(name)s succeeded",
340
logger.info(u"Checker for %(name)s failed",
344
self.CheckerCompleted(dbus.Int16(exitstatus),
345
dbus.Int64(condition),
346
dbus.String(command))
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):
348
203
logger.warning(u"Checker for %(name)s crashed?",
352
self.CheckerCompleted(dbus.Int16(-1),
353
dbus.Int64(condition),
354
dbus.String(command))
356
def checked_ok(self):
357
"""Bump up the timeout for this client.
358
This should only be called when the client has been seen,
361
self.last_checked_ok = datetime.datetime.utcnow()
362
gobject.source_remove(self.disable_initiator_tag)
363
self.disable_initiator_tag = (gobject.timeout_add
364
(self.timeout_milliseconds(),
368
self.PropertyChanged(
369
dbus.String(u"last_checked_ok"),
370
(_datetime_to_dbus(self.last_checked_ok,
206
logger.debug(u"Checker for %(name)s failed",
209
self.checker_callback_tag = None
373
210
def start_checker(self):
374
211
"""Start a new checker subprocess if one is not running.
375
212
If a checker already exists, leave it running and do
377
# The reason for not killing a running checker is that if we
378
# did that, then if a checker (for some reason) started
379
# running slowly and taking more than 'interval' time, the
380
# client would inevitably timeout, since no checker would get
381
# a chance to run to completion. If we instead leave running
382
# checkers alone, the checker would have to take more time
383
# than 'timeout' for the client to be declared invalid, which
384
# is as it should be.
386
# If a checker exists, make sure it is not a zombie
387
if self.checker is not None:
388
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
390
logger.warning("Checker was a zombie")
391
gobject.source_remove(self.checker_callback_tag)
392
self.checker_callback(pid, status,
393
self.current_checker_command)
394
# Start a new checker if needed
395
214
if self.checker is None:
397
# In case checker_command has exactly one % operator
398
command = self.checker_command % self.host
216
command = self.check_command % self.fqdn
399
217
except TypeError:
400
# Escape attributes for the shell
401
218
escaped_attrs = dict((key, re.escape(str(val)))
403
220
vars(self).iteritems())
405
command = self.checker_command % escaped_attrs
222
command = self.check_command % escaped_attrs
406
223
except TypeError, error:
407
logger.error(u'Could not format string "%s":'
408
u' %s', self.checker_command, error)
224
logger.critical(u'Could not format string "%s":'
225
u' %s', self.check_command, error)
409
226
return True # Try again later
410
self.current_checker_command = command
412
logger.info(u"Starting checker %r for %s",
414
# We don't need to redirect stdout and stderr, since
415
# in normal mode, that is already done by daemon(),
416
# and in debug mode we don't want to. (Stdin is
417
# always replaced by /dev/null.)
418
self.checker = subprocess.Popen(command,
423
self.CheckerStarted(command)
424
self.PropertyChanged(
425
dbus.String("checker_running"),
426
dbus.Boolean(True, variant_level=1))
427
self.checker_callback_tag = (gobject.child_watch_add
429
self.checker_callback,
431
# The checker may have completed before the gobject
432
# watch was added. Check for this.
433
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
435
gobject.source_remove(self.checker_callback_tag)
436
self.checker_callback(pid, status, command)
437
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:
438
238
logger.error(u"Failed to start subprocess: %s",
440
240
# Re-run this periodically if run by gobject.timeout_add
443
242
def stop_checker(self):
444
243
"""Force the checker process, if any, to stop."""
445
if self.checker_callback_tag:
446
gobject.source_remove(self.checker_callback_tag)
447
self.checker_callback_tag = None
448
if getattr(self, "checker", None) is None:
244
if not hasattr(self, "checker") or self.checker is None:
450
logger.debug(u"Stopping checker for %(name)s", vars(self))
452
os.kill(self.checker.pid, signal.SIGTERM)
454
#if self.checker.poll() is None:
455
# os.kill(self.checker.pid, signal.SIGKILL)
456
except OSError, error:
457
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)
459
251
self.checker = None
461
self.PropertyChanged(dbus.String(u"checker_running"),
462
dbus.Boolean(False, variant_level=1))
464
def still_valid(self):
252
def still_valid(self, now=None):
465
253
"""Has the timeout not yet passed for this client?"""
466
if not getattr(self, "enabled", False):
468
now = datetime.datetime.utcnow()
469
if self.last_checked_ok is None:
255
now = datetime.datetime.now()
256
if self.last_seen is None:
470
257
return now < (self.created + self.timeout)
472
return now < (self.last_checked_ok + self.timeout)
474
## D-Bus methods & signals
475
_interface = u"se.bsnet.fukt.Mandos.Client"
478
CheckedOK = dbus.service.method(_interface)(checked_ok)
479
CheckedOK.__name__ = "CheckedOK"
481
# CheckerCompleted - signal
482
@dbus.service.signal(_interface, signature="nxs")
483
def CheckerCompleted(self, exitcode, waitstatus, command):
487
# CheckerStarted - signal
488
@dbus.service.signal(_interface, signature="s")
489
def CheckerStarted(self, command):
493
# GetAllProperties - method
494
@dbus.service.method(_interface, out_signature="a{sv}")
495
def GetAllProperties(self):
497
return dbus.Dictionary({
499
dbus.String(self.name, variant_level=1),
500
dbus.String("fingerprint"):
501
dbus.String(self.fingerprint, variant_level=1),
503
dbus.String(self.host, variant_level=1),
504
dbus.String("created"):
505
_datetime_to_dbus(self.created, variant_level=1),
506
dbus.String("last_enabled"):
507
(_datetime_to_dbus(self.last_enabled,
509
if self.last_enabled is not None
510
else dbus.Boolean(False, variant_level=1)),
511
dbus.String("enabled"):
512
dbus.Boolean(self.enabled, variant_level=1),
513
dbus.String("last_checked_ok"):
514
(_datetime_to_dbus(self.last_checked_ok,
516
if self.last_checked_ok is not None
517
else dbus.Boolean (False, variant_level=1)),
518
dbus.String("timeout"):
519
dbus.UInt64(self.timeout_milliseconds(),
521
dbus.String("interval"):
522
dbus.UInt64(self.interval_milliseconds(),
524
dbus.String("checker"):
525
dbus.String(self.checker_command,
527
dbus.String("checker_running"):
528
dbus.Boolean(self.checker is not None,
530
dbus.String("object_path"):
531
dbus.ObjectPath(self.dbus_object_path,
535
# IsStillValid - method
536
IsStillValid = (dbus.service.method(_interface, out_signature="b")
538
IsStillValid.__name__ = "IsStillValid"
540
# PropertyChanged - signal
541
@dbus.service.signal(_interface, signature="sv")
542
def PropertyChanged(self, property, value):
546
# ReceivedSecret - signal
547
@dbus.service.signal(_interface)
548
def ReceivedSecret(self):
553
@dbus.service.signal(_interface)
558
# SetChecker - method
559
@dbus.service.method(_interface, in_signature="s")
560
def SetChecker(self, checker):
561
"D-Bus setter method"
562
self.checker_command = checker
564
self.PropertyChanged(dbus.String(u"checker"),
565
dbus.String(self.checker_command,
569
@dbus.service.method(_interface, in_signature="s")
570
def SetHost(self, host):
571
"D-Bus setter method"
574
self.PropertyChanged(dbus.String(u"host"),
575
dbus.String(self.host, variant_level=1))
577
# SetInterval - method
578
@dbus.service.method(_interface, in_signature="t")
579
def SetInterval(self, milliseconds):
580
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
582
self.PropertyChanged(dbus.String(u"interval"),
583
(dbus.UInt64(self.interval_milliseconds(),
587
@dbus.service.method(_interface, in_signature="ay",
589
def SetSecret(self, secret):
590
"D-Bus setter method"
591
self.secret = str(secret)
593
# SetTimeout - method
594
@dbus.service.method(_interface, in_signature="t")
595
def SetTimeout(self, milliseconds):
596
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
598
self.PropertyChanged(dbus.String(u"timeout"),
599
(dbus.UInt64(self.timeout_milliseconds(),
603
Enable = dbus.service.method(_interface)(enable)
604
Enable.__name__ = "Enable"
606
# StartChecker - method
607
@dbus.service.method(_interface)
608
def StartChecker(self):
613
@dbus.service.method(_interface)
618
# StopChecker - method
619
StopChecker = dbus.service.method(_interface)(stop_checker)
620
StopChecker.__name__ = "StopChecker"
259
return now < (self.last_seen + self.timeout)
625
262
def peer_certificate(session):
626
"Return the peer's OpenPGP certificate as a bytestring"
263
"Return an OpenPGP data packet string for the peer's certificate"
627
264
# If not an OpenPGP certificate...
628
if (gnutls.library.functions
629
.gnutls_certificate_type_get(session._c_object)
630
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
265
if gnutls.library.functions.gnutls_certificate_type_get\
266
(session._c_object) \
267
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
631
268
# ...do the normal thing
632
269
return session.peer_certificate
633
list_size = ctypes.c_uint(1)
634
cert_list = (gnutls.library.functions
635
.gnutls_certificate_get_peers
636
(session._c_object, ctypes.byref(list_size)))
637
if not bool(cert_list) and list_size.value != 0:
638
raise gnutls.errors.GNUTLSError("error getting peer"
270
list_size = ctypes.c_uint()
271
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
272
(session._c_object, ctypes.byref(list_size))
640
273
if list_size.value == 0:
642
275
cert = cert_list[0]
646
279
def fingerprint(openpgp):
647
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
648
# New GnuTLS "datum" with the OpenPGP public key
649
datum = (gnutls.library.types
650
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
653
ctypes.c_uint(len(openpgp))))
280
"Convert an OpenPGP data string to a hexdigit fingerprint string"
654
281
# New empty GnuTLS certificate
655
282
crt = gnutls.library.types.gnutls_openpgp_crt_t()
656
(gnutls.library.functions
657
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
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)))
658
290
# Import the OpenPGP public key into the certificate
659
(gnutls.library.functions
660
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
661
gnutls.library.constants
662
.GNUTLS_OPENPGP_FMT_RAW))
663
# Verify the self signature in the key
664
crtverify = ctypes.c_uint()
665
(gnutls.library.functions
666
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
667
if crtverify.value != 0:
668
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
669
raise gnutls.errors.CertificateSecurityError("Verify failed")
291
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
294
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
670
295
# New buffer for the fingerprint
671
buf = ctypes.create_string_buffer(20)
672
buf_len = ctypes.c_size_t()
296
buffer = ctypes.create_string_buffer(20)
297
buffer_length = ctypes.c_size_t()
673
298
# Get the fingerprint from the certificate into the buffer
674
(gnutls.library.functions
675
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
676
ctypes.byref(buf_len)))
299
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
300
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
677
301
# Deinit the certificate
678
302
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
679
303
# Convert the buffer to a Python bytestring
680
fpr = ctypes.string_at(buf, buf_len.value)
304
fpr = ctypes.string_at(buffer, buffer_length.value)
681
305
# Convert the bytestring to hexadecimal notation
682
306
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
686
class TCP_handler(SocketServer.BaseRequestHandler, object):
310
class tcp_handler(SocketServer.BaseRequestHandler, object):
687
311
"""A TCP request handler class.
688
312
Instantiated by IPv6_TCPServer for each request to handle it.
689
313
Note: This will run in its own forked process."""
691
315
def handle(self):
692
logger.info(u"TCP connection from: %s",
693
unicode(self.client_address))
694
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
695
# Open IPC pipe to parent process
696
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
697
session = (gnutls.connection
698
.ClientSession(self.request,
702
line = self.request.makefile().readline()
703
logger.debug(u"Protocol version: %r", line)
705
if int(line.strip().split()[0]) > 1:
707
except (ValueError, IndexError, RuntimeError), error:
708
logger.error(u"Unknown protocol version: %s", error)
711
# Note: gnutls.connection.X509Credentials is really a
712
# generic GnuTLS certificate credentials object so long as
713
# no X.509 keys are added to it. Therefore, we can use it
714
# here despite using OpenPGP certificates.
716
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
717
# "+AES-256-CBC", "+SHA1",
718
# "+COMP-NULL", "+CTYPE-OPENPGP",
720
# Use a fallback default, since this MUST be set.
721
priority = self.server.settings.get("priority", "NORMAL")
722
(gnutls.library.functions
723
.gnutls_priority_set_direct(session._c_object,
728
except gnutls.errors.GNUTLSError, error:
729
logger.warning(u"Handshake failed: %s", error)
730
# Do not run session.bye() here: the session is not
731
# established. Just abandon the request.
733
logger.debug(u"Handshake succeeded")
735
fpr = fingerprint(peer_certificate(session))
736
except (TypeError, gnutls.errors.GNUTLSError), error:
737
logger.warning(u"Bad certificate: %s", error)
740
logger.debug(u"Fingerprint: %s", fpr)
742
for c in self.server.clients:
743
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",
747
logger.warning(u"Client not found for fingerprint: %s",
749
ipc.write("NOTFOUND %s\n" % fpr)
752
# Have to check if client.still_valid(), since it is
753
# possible that the client timed out while establishing
754
# the GnuTLS session.
755
if not client.still_valid():
756
logger.warning(u"Client %(name)s is invalid",
758
ipc.write("INVALID %s\n" % client.name)
761
ipc.write("SENDING %s\n" % client.name)
763
while sent_size < len(client.secret):
764
sent = session.send(client.secret[sent_size:])
765
logger.debug(u"Sent: %d, remaining: %d",
766
sent, len(client.secret)
767
- (sent_size + sent))
357
logger.debug(u"Client not found for fingerprint: %s",
772
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
773
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
774
Assumes a gobject.MainLoop event loop.
776
def process_request(self, request, client_address):
777
"""This overrides and wraps the original process_request().
778
This function creates a new pipe in self.pipe
780
self.pipe = os.pipe()
781
super(ForkingMixInWithPipe,
782
self).process_request(request, client_address)
783
os.close(self.pipe[1]) # close write end
784
# Call "handle_ipc" for both data and EOF events
785
gobject.io_add_watch(self.pipe[0],
786
gobject.IO_IN | gobject.IO_HUP,
788
def handle_ipc(source, condition):
789
"""Dummy function; override as necessary"""
794
class IPv6_TCPServer(ForkingMixInWithPipe,
795
SocketServer.TCPServer, object):
796
"""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.
798
settings: Server settings
374
options: Command line options
799
375
clients: Set() of Client objects
800
enabled: Boolean; whether this server is activated yet
802
377
address_family = socket.AF_INET6
803
378
def __init__(self, *args, **kwargs):
804
if "settings" in kwargs:
805
self.settings = kwargs["settings"]
806
del kwargs["settings"]
379
if "options" in kwargs:
380
self.options = kwargs["options"]
381
del kwargs["options"]
807
382
if "clients" in kwargs:
808
383
self.clients = kwargs["clients"]
809
384
del kwargs["clients"]
810
if "use_ipv6" in kwargs:
811
if not kwargs["use_ipv6"]:
812
self.address_family = socket.AF_INET
813
del kwargs["use_ipv6"]
815
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
385
return super(type(self), self).__init__(*args, **kwargs)
816
386
def server_bind(self):
817
387
"""This overrides the normal server_bind() function
818
388
to bind to an interface if one was specified, and also NOT to
819
389
bind to an address or port if they were not specified."""
820
if self.settings["interface"]:
821
# 25 is from /usr/include/asm-i486/socket.h
822
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
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
824
395
self.socket.setsockopt(socket.SOL_SOCKET,
826
self.settings["interface"])
396
socket.SO_BINDTODEVICE,
397
self.options.interface)
827
398
except socket.error, error:
828
399
if error[0] == errno.EPERM:
829
logger.error(u"No permission to"
830
u" bind to interface %s",
831
self.settings["interface"])
400
logger.warning(u"No permission to"
401
u" bind to interface %s",
402
self.options.interface)
834
405
# Only bind(2) the socket if we really need to.
835
406
if self.server_address[0] or self.server_address[1]:
836
407
if not self.server_address[0]:
837
if self.address_family == socket.AF_INET6:
838
any_address = "::" # in6addr_any
840
any_address = socket.INADDR_ANY
841
self.server_address = (any_address,
409
self.server_address = (in6addr_any,
842
410
self.server_address[1])
843
elif not self.server_address[1]:
411
elif self.server_address[1] is None:
844
412
self.server_address = (self.server_address[0],
846
# if self.settings["interface"]:
847
# self.server_address = (self.server_address[0],
853
return super(IPv6_TCPServer, self).server_bind()
854
def server_activate(self):
856
return super(IPv6_TCPServer, self).server_activate()
859
def handle_ipc(self, source, condition, file_objects={}):
861
gobject.IO_IN: "IN", # There is data to read.
862
gobject.IO_OUT: "OUT", # Data can be written (without
864
gobject.IO_PRI: "PRI", # There is urgent data to read.
865
gobject.IO_ERR: "ERR", # Error condition.
866
gobject.IO_HUP: "HUP" # Hung up (the connection has been
867
# broken, usually for pipes and
870
conditions_string = ' | '.join(name
872
condition_names.iteritems()
874
logger.debug("Handling IPC: FD = %d, condition = %s", source,
877
# Turn the pipe file descriptor into a Python file object
878
if source not in file_objects:
879
file_objects[source] = os.fdopen(source, "r", 1)
881
# Read a line from the file object
882
cmdline = file_objects[source].readline()
883
if not cmdline: # Empty line means end of file
885
file_objects[source].close()
886
del file_objects[source]
888
# Stop calling this function
891
logger.debug("IPC command: %r\n" % cmdline)
893
# Parse and act on command
894
cmd, args = cmdline.split(None, 1)
895
if cmd == "NOTFOUND":
896
if self.settings["use_dbus"]:
898
mandos_dbus_service.ClientNotFound(args)
899
elif cmd == "INVALID":
900
if self.settings["use_dbus"]:
901
for client in self.clients:
902
if client.name == args:
906
elif cmd == "SENDING":
907
for client in self.clients:
908
if client.name == args:
910
if self.settings["use_dbus"]:
912
client.ReceivedSecret()
915
logger.error("Unknown IPC command: %r", cmdline)
917
# Keep calling this function
414
return super(type(self), self).server_bind()
921
417
def string_to_delta(interval):
922
418
"""Parse a string and return a datetime.timedelta
924
420
>>> string_to_delta('7d')
925
421
datetime.timedelta(7)
926
422
>>> string_to_delta('60s')
931
427
datetime.timedelta(1)
932
428
>>> string_to_delta(u'1w')
933
429
datetime.timedelta(7)
934
>>> string_to_delta('5m 30s')
935
datetime.timedelta(0, 330)
937
timevalue = datetime.timedelta(0)
938
for s in interval.split():
940
suffix = unicode(s[-1])
943
delta = datetime.timedelta(value)
945
delta = datetime.timedelta(0, value)
947
delta = datetime.timedelta(0, 0, 0, 0, value)
949
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
951
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
954
except (ValueError, IndexError):
432
suffix=unicode(interval[-1])
433
value=int(interval[:-1])
435
delta = datetime.timedelta(value)
437
delta = datetime.timedelta(0, value)
439
delta = datetime.timedelta(0, 0, 0, 0, value)
441
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
443
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
446
except (ValueError, IndexError):
452
"""From the Avahi server example code"""
453
global group, serviceName, serviceType, servicePort, serviceTXT, \
456
group = dbus.Interface(
457
bus.get_object( avahi.DBUS_NAME,
458
server.EntryGroupNew()),
459
avahi.DBUS_INTERFACE_ENTRY_GROUP)
460
group.connect_to_signal('StateChanged',
461
entry_group_state_changed)
462
logger.debug(u"Adding service '%s' of type '%s' ...",
463
serviceName, serviceType)
466
serviceInterface, # interface
467
avahi.PROTO_INET6, # protocol
468
dbus.UInt32(0), # flags
469
serviceName, serviceType,
471
dbus.UInt16(servicePort),
472
avahi.string_array_to_txt_array(serviceTXT))
476
def remove_service():
477
"""From the Avahi server example code"""
480
if not group is None:
960
484
def server_state_changed(state):
961
"""Derived from the Avahi example code"""
485
"""From the Avahi server example code"""
962
486
if state == avahi.SERVER_COLLISION:
963
logger.error(u"Zeroconf server name collision")
487
logger.warning(u"Server name collision")
965
489
elif state == avahi.SERVER_RUNNING:
969
493
def entry_group_state_changed(state, error):
970
"""Derived from the Avahi example code"""
971
logger.debug(u"Avahi state change: %i", state)
494
"""From the Avahi server example code"""
495
global serviceName, server, rename_count
497
logger.debug(u"state change: %i", state)
973
499
if state == avahi.ENTRY_GROUP_ESTABLISHED:
974
logger.debug(u"Zeroconf service established.")
500
logger.debug(u"Service established.")
975
501
elif state == avahi.ENTRY_GROUP_COLLISION:
976
logger.warning(u"Zeroconf service name collision.")
503
rename_count = rename_count - 1
505
name = server.GetAlternativeServiceName(name)
506
logger.warning(u"Service name collision, "
507
u"changing name to '%s' ...", name)
512
logger.error(u"No suitable service name found after %i"
513
u" retries, exiting.", n_rename)
978
515
elif state == avahi.ENTRY_GROUP_FAILURE:
979
logger.critical(u"Avahi: Error in group state changed %s",
981
raise AvahiGroupError(u"State changed: %s" % unicode(error))
516
logger.error(u"Error in group state changed %s",
983
521
def if_nametoindex(interface):
984
"""Call the C function if_nametoindex(), or equivalent"""
985
global if_nametoindex
522
"""Call the C function if_nametoindex()"""
987
if_nametoindex = (ctypes.cdll.LoadLibrary
988
(ctypes.util.find_library("c"))
524
libc = ctypes.cdll.LoadLibrary("libc.so.6")
525
return libc.if_nametoindex(interface)
990
526
except (OSError, AttributeError):
991
527
if "struct" not in sys.modules:
993
529
if "fcntl" not in sys.modules:
995
def if_nametoindex(interface):
996
"Get an interface index the hard way, i.e. using fcntl()"
997
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
998
with closing(socket.socket()) as s:
999
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1000
struct.pack("16s16x", interface))
1001
interface_index = struct.unpack("I", ifreq[16:20])[0]
1002
return interface_index
1003
return if_nametoindex(interface)
1006
def daemon(nochdir = False, noclose = False):
531
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
533
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
534
struct.pack("16s16x", interface))
536
interface_index = struct.unpack("I", ifreq[16:20])[0]
537
return interface_index
540
def daemon(nochdir, noclose):
1007
541
"""See daemon(3). Standard BSD Unix function.
1008
542
This should really exist as os.daemon, but it doesn't (yet)."""
1031
######################################################################
1032
# Parsing of options, both command line and config file
1034
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()
1035
574
parser.add_option("-i", "--interface", type="string",
1036
metavar="IF", help="Bind to interface IF")
1037
parser.add_option("-a", "--address", type="string",
1038
help="Address to listen for requests on")
1039
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,
1040
578
help="Port number to receive requests on")
1041
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,
1042
586
help="Run self-test")
1043
parser.add_option("--debug", action="store_true",
1044
help="Debug mode; run in foreground and log to"
1046
parser.add_option("--priority", type="string", help="GnuTLS"
1047
" priority string (see GnuTLS documentation)")
1048
parser.add_option("--servicename", type="string", metavar="NAME",
1049
help="Zeroconf service name")
1050
parser.add_option("--configdir", type="string",
1051
default="/etc/mandos", metavar="DIR",
1052
help="Directory to search for configuration"
1054
parser.add_option("--no-dbus", action="store_false",
1056
help="Do not provide D-Bus system bus"
1058
parser.add_option("--no-ipv6", action="store_false",
1059
dest="use_ipv6", help="Do not use IPv6")
1060
options = parser.parse_args()[0]
587
parser.add_option("--debug", action="store_true", default=False,
589
(options, args) = parser.parse_args()
1062
591
if options.check:
1064
593
doctest.testmod()
1067
# Default values for config file for server-global settings
1068
server_defaults = { "interface": "",
1073
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1074
"servicename": "Mandos",
1079
# Parse config file for server-global settings
1080
server_config = ConfigParser.SafeConfigParser(server_defaults)
1082
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1083
# Convert the SafeConfigParser object to a dict
1084
server_settings = server_config.defaults()
1085
# Use the appropriate methods on the non-string config options
1086
server_settings["debug"] = server_config.getboolean("DEFAULT",
1088
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1090
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1092
if server_settings["port"]:
1093
server_settings["port"] = server_config.getint("DEFAULT",
1097
# Override the settings from the config file with command line
1099
for option in ("interface", "address", "port", "debug",
1100
"priority", "servicename", "configdir",
1101
"use_dbus", "use_ipv6"):
1102
value = getattr(options, option)
1103
if value is not None:
1104
server_settings[option] = value
1106
# Now we have our good server settings in "server_settings"
1108
##################################################################
1111
debug = server_settings["debug"]
1112
use_dbus = server_settings["use_dbus"]
1113
use_ipv6 = server_settings["use_ipv6"]
1116
syslogger.setLevel(logging.WARNING)
1117
console.setLevel(logging.WARNING)
1119
if server_settings["servicename"] != "Mandos":
1120
syslogger.setFormatter(logging.Formatter
1121
('Mandos (%s) [%%(process)d]:'
1122
' %%(levelname)s: %%(message)s'
1123
% server_settings["servicename"]))
1125
# Parse config file with clients
1126
client_defaults = { "timeout": "1h",
1128
"checker": "fping -q -- %%(host)s",
1131
client_config = ConfigParser.SafeConfigParser(client_defaults)
1132
client_config.read(os.path.join(server_settings["configdir"],
1135
global mandos_dbus_service
1136
mandos_dbus_service = None
1139
tcp_server = IPv6_TCPServer((server_settings["address"],
1140
server_settings["port"]),
1142
settings=server_settings,
1143
clients=clients, use_ipv6=use_ipv6)
1144
pidfilename = "/var/run/mandos.pid"
1146
pidfile = open(pidfilename, "w")
1148
logger.error("Could not open file %r", pidfilename)
1151
uid = pwd.getpwnam("_mandos").pw_uid
1152
gid = pwd.getpwnam("_mandos").pw_gid
1155
uid = pwd.getpwnam("mandos").pw_uid
1156
gid = pwd.getpwnam("mandos").pw_gid
1159
uid = pwd.getpwnam("nobody").pw_uid
1160
gid = pwd.getpwnam("nogroup").pw_gid
1167
except OSError, error:
1168
if error[0] != errno.EPERM:
1171
# Enable all possible GnuTLS debugging
1173
# "Use a log level over 10 to enable all debugging options."
1175
gnutls.library.functions.gnutls_global_set_log_level(11)
1177
@gnutls.library.types.gnutls_log_func
1178
def debug_gnutls(level, string):
1179
logger.debug("GnuTLS: %s", string[:-1])
1181
(gnutls.library.functions
1182
.gnutls_global_set_log_function(debug_gnutls))
1185
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1186
service = AvahiService(name = server_settings["servicename"],
1187
servicetype = "_mandos._tcp",
1188
protocol = protocol)
1189
if server_settings["interface"]:
1190
service.interface = (if_nametoindex
1191
(server_settings["interface"]))
1196
# 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
1197
613
DBusGMainLoop(set_as_default=True )
1198
614
main_loop = gobject.MainLoop()
1199
615
bus = dbus.SystemBus()
1200
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1201
avahi.DBUS_PATH_SERVER),
1202
avahi.DBUS_INTERFACE_SERVER)
616
server = dbus.Interface(
617
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
618
avahi.DBUS_INTERFACE_SERVER )
1203
619
# End of Avahi example code
1205
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1207
clients.update(Set(Client(name = section,
1209
= dict(client_config.items(section)),
1210
use_dbus = use_dbus)
621
debug = options.debug
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\
1211
642
for section in client_config.sections()))
1213
logger.warning(u"No clients defined")
1216
# Redirect stdin so all checkers get /dev/null
1217
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1218
os.dup2(null, sys.stdin.fileno())
1222
# No console logging
1223
logger.removeHandler(console)
1224
# Close all input and output, do double fork, etc.
1228
with closing(pidfile):
1230
pidfile.write(str(pid) + "\n")
1233
logger.error(u"Could not write to file %r with PID %d",
1236
# "pidfile" was never created
1241
648
"Cleanup function; run on exit"
1243
# From the Avahi example code
650
# From the Avahi server example code
1244
651
if not group is None:
1247
654
# End of Avahi example code
1250
client = clients.pop()
1251
client.disable_hook = None
656
for client in clients:
657
client.stop_hook = None
1254
660
atexit.register(cleanup)
1257
663
signal.signal(signal.SIGINT, signal.SIG_IGN)
1258
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1259
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1262
class MandosDBusService(dbus.service.Object):
1263
"""A D-Bus proxy object"""
1265
dbus.service.Object.__init__(self, bus, "/")
1266
_interface = u"se.bsnet.fukt.Mandos"
1268
@dbus.service.signal(_interface, signature="oa{sv}")
1269
def ClientAdded(self, objpath, properties):
1273
@dbus.service.signal(_interface, signature="s")
1274
def ClientNotFound(self, fingerprint):
1278
@dbus.service.signal(_interface, signature="os")
1279
def ClientRemoved(self, objpath, name):
1283
@dbus.service.method(_interface, out_signature="ao")
1284
def GetAllClients(self):
1286
return dbus.Array(c.dbus_object_path for c in clients)
1288
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1289
def GetAllClientsWithProperties(self):
1291
return dbus.Dictionary(
1292
((c.dbus_object_path, c.GetAllProperties())
1296
@dbus.service.method(_interface, in_signature="o")
1297
def RemoveClient(self, object_path):
1300
if c.dbus_object_path == object_path:
1302
# Don't signal anything except ClientRemoved
1306
self.ClientRemoved(object_path, c.name)
1312
mandos_dbus_service = MandosDBusService()
664
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
665
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
1314
667
for client in clients:
1317
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1318
client.GetAllProperties())
1322
tcp_server.server_activate()
1324
# Find out what port we got
1325
service.port = tcp_server.socket.getsockname()[1]
1327
logger.info(u"Now listening on address %r, port %d,"
1328
" flowinfo %d, scope_id %d"
1329
% tcp_server.socket.getsockname())
1331
logger.info(u"Now listening on address %r, port %d"
1332
% tcp_server.socket.getsockname())
1334
#service.interface = tcp_server.socket.getsockname()[3]
1337
# From the Avahi example code
1338
server.connect_to_signal("StateChanged", server_state_changed)
1340
server_state_changed(server.GetState())
1341
except dbus.exceptions.DBusException, error:
1342
logger.critical(u"DBusException: %s", error)
1344
# End of Avahi example code
1346
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1347
lambda *args, **kwargs:
1348
(tcp_server.handle_request
1349
(*args[2:], **kwargs) or True))
1351
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
1353
except AvahiError, error:
1354
logger.critical(u"AvahiError: %s", error)
1356
697
except KeyboardInterrupt:
1359
logger.debug("Server received KeyboardInterrupt")
1360
logger.debug("Server exiting")
1362
if __name__ == '__main__':