130
152
u" after %i retries, exiting.",
131
153
self.rename_count)
132
154
raise AvahiServiceError(u"Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
155
self.name = self.server.GetAlternativeServiceName(self.name)
134
156
logger.info(u"Changing Zeroconf service name to %r ...",
136
158
syslogger.setFormatter(logging.Formatter
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
159
(u'Mandos (%s) [%%(process)d]:'
160
u' %%(levelname)s: %%(message)s'
141
164
self.rename_count += 1
142
165
def remove(self):
143
166
"""Derived from the Avahi example code"""
144
if group is not None:
167
if self.group is not None:
147
170
"""Derived from the Avahi example code"""
150
group = dbus.Interface(bus.get_object
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
171
if self.group is None:
172
self.group = dbus.Interface(
173
self.bus.get_object(avahi.DBUS_NAME,
174
self.server.EntryGroupNew()),
175
avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
self.group.connect_to_signal('StateChanged',
177
self.entry_group_state_changed)
156
178
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
173
def _datetime_to_dbus(dt, variant_level=0):
174
"""Convert a UTC datetime.datetime() to a D-Bus type."""
175
return dbus.String(dt.isoformat(), variant_level=variant_level)
178
class Client(dbus.service.Object):
179
self.name, self.type)
180
self.group.AddService(
183
dbus.UInt32(0), # flags
184
self.name, self.type,
185
self.domain, self.host,
186
dbus.UInt16(self.port),
187
avahi.string_array_to_txt_array(self.TXT))
189
def entry_group_state_changed(self, state, error):
190
"""Derived from the Avahi example code"""
191
logger.debug(u"Avahi state change: %i", state)
193
if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
logger.debug(u"Zeroconf service established.")
195
elif state == avahi.ENTRY_GROUP_COLLISION:
196
logger.warning(u"Zeroconf service name collision.")
198
elif state == avahi.ENTRY_GROUP_FAILURE:
199
logger.critical(u"Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
204
"""Derived from the Avahi example code"""
205
if self.group is not None:
208
def server_state_changed(self, state):
209
"""Derived from the Avahi example code"""
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
213
elif state == avahi.SERVER_RUNNING:
216
"""Derived from the Avahi example code"""
217
if self.server is None:
218
self.server = dbus.Interface(
219
self.bus.get_object(avahi.DBUS_NAME,
220
avahi.DBUS_PATH_SERVER),
221
avahi.DBUS_INTERFACE_SERVER)
222
self.server.connect_to_signal(u"StateChanged",
223
self.server_state_changed)
224
self.server_state_changed(self.server.GetState())
227
class Client(object):
179
228
"""A representation of a client host served by this server.
181
231
name: string; from the config file, used in log messages and
182
232
D-Bus identifiers
226
278
if config is None:
228
280
logger.debug(u"Creating client %r", self.name)
229
self.use_dbus = False # During __init__
230
281
# Uppercase and remove spaces from fingerprint for later
231
282
# comparison purposes with return value from the fingerprint()
233
self.fingerprint = (config["fingerprint"].upper()
284
self.fingerprint = (config[u"fingerprint"].upper()
234
285
.replace(u" ", u""))
235
286
logger.debug(u" Fingerprint: %s", self.fingerprint)
236
if "secret" in config:
237
self.secret = config["secret"].decode(u"base64")
238
elif "secfile" in config:
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
239
290
with closing(open(os.path.expanduser
240
291
(os.path.expandvars
241
(config["secfile"])))) as secfile:
292
(config[u"secfile"])))) as secfile:
242
293
self.secret = secfile.read()
244
295
raise TypeError(u"No secret or secfile for client %s"
246
self.host = config.get("host", "")
297
self.host = config.get(u"host", u"")
247
298
self.created = datetime.datetime.utcnow()
248
299
self.enabled = False
249
300
self.last_enabled = None
250
301
self.last_checked_ok = None
251
self.timeout = string_to_delta(config["timeout"])
252
self.interval = string_to_delta(config["interval"])
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
253
304
self.disable_hook = disable_hook
254
305
self.checker = None
255
306
self.checker_initiator_tag = None
256
307
self.disable_initiator_tag = None
257
308
self.checker_callback_tag = None
258
self.checker_command = config["checker"]
309
self.checker_command = config[u"checker"]
310
self.current_checker_command = None
259
311
self.last_connect = None
260
# Only now, when this client is initialized, can it show up on
262
self.use_dbus = use_dbus
264
self.dbus_object_path = (dbus.ObjectPath
266
+ self.name.replace(".", "_")))
267
dbus.service.Object.__init__(self, bus,
268
self.dbus_object_path)
270
313
def enable(self):
271
314
"""Start this client's checker and timeout hooks"""
315
if getattr(self, u"enabled", False):
272
318
self.last_enabled = datetime.datetime.utcnow()
273
319
# Schedule a new checker to be started an 'interval' from now,
274
320
# and every interval from then on.
434
465
if error.errno != errno.ESRCH: # No such process
436
467
self.checker = None
438
self.PropertyChanged(dbus.String(u"checker_running"),
439
dbus.Boolean(False, variant_level=1))
441
469
def still_valid(self):
442
470
"""Has the timeout not yet passed for this client?"""
443
if not getattr(self, "enabled", False):
471
if not getattr(self, u"enabled", False):
445
473
now = datetime.datetime.utcnow()
446
474
if self.last_checked_ok is None:
447
475
return now < (self.created + self.timeout)
449
477
return now < (self.last_checked_ok + self.timeout)
480
class ClientDBus(Client, dbus.service.Object):
481
"""A Client class using D-Bus
484
dbus_object_path: dbus.ObjectPath
485
bus: dbus.SystemBus()
487
# dbus.service.Object doesn't use super(), so we can't either.
489
def __init__(self, bus = None, *args, **kwargs):
491
Client.__init__(self, *args, **kwargs)
492
# Only now, when this client is initialized, can it show up on
494
self.dbus_object_path = (dbus.ObjectPath
496
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
498
self.dbus_object_path)
501
def _datetime_to_dbus(dt, variant_level=0):
502
"""Convert a UTC datetime.datetime() to a D-Bus type."""
503
return dbus.String(dt.isoformat(),
504
variant_level=variant_level)
507
oldstate = getattr(self, u"enabled", False)
508
r = Client.enable(self)
509
if oldstate != self.enabled:
511
self.PropertyChanged(dbus.String(u"enabled"),
512
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
519
def disable(self, signal = True):
520
oldstate = getattr(self, u"enabled", False)
521
r = Client.disable(self)
522
if signal and oldstate != self.enabled:
524
self.PropertyChanged(dbus.String(u"enabled"),
525
dbus.Boolean(False, variant_level=1))
528
def __del__(self, *args, **kwargs):
530
self.remove_from_connection()
533
if hasattr(dbus.service.Object, u"__del__"):
534
dbus.service.Object.__del__(self, *args, **kwargs)
535
Client.__del__(self, *args, **kwargs)
537
def checker_callback(self, pid, condition, command,
539
self.checker_callback_tag = None
542
self.PropertyChanged(dbus.String(u"checker_running"),
543
dbus.Boolean(False, variant_level=1))
544
if os.WIFEXITED(condition):
545
exitstatus = os.WEXITSTATUS(condition)
547
self.CheckerCompleted(dbus.Int16(exitstatus),
548
dbus.Int64(condition),
549
dbus.String(command))
552
self.CheckerCompleted(dbus.Int16(-1),
553
dbus.Int64(condition),
554
dbus.String(command))
556
return Client.checker_callback(self, pid, condition, command,
559
def checked_ok(self, *args, **kwargs):
560
r = Client.checked_ok(self, *args, **kwargs)
562
self.PropertyChanged(
563
dbus.String(u"last_checked_ok"),
564
(self._datetime_to_dbus(self.last_checked_ok,
568
def start_checker(self, *args, **kwargs):
569
old_checker = self.checker
570
if self.checker is not None:
571
old_checker_pid = self.checker.pid
573
old_checker_pid = None
574
r = Client.start_checker(self, *args, **kwargs)
575
# Only if new checker process was started
576
if (self.checker is not None
577
and old_checker_pid != self.checker.pid):
579
self.CheckerStarted(self.current_checker_command)
580
self.PropertyChanged(
581
dbus.String(u"checker_running"),
582
dbus.Boolean(True, variant_level=1))
585
def stop_checker(self, *args, **kwargs):
586
old_checker = getattr(self, u"checker", None)
587
r = Client.stop_checker(self, *args, **kwargs)
588
if (old_checker is not None
589
and getattr(self, u"checker", None) is None):
590
self.PropertyChanged(dbus.String(u"checker_running"),
591
dbus.Boolean(False, variant_level=1))
451
594
## D-Bus methods & signals
452
595
_interface = u"se.bsnet.fukt.Mandos.Client"
454
597
# CheckedOK - method
455
CheckedOK = dbus.service.method(_interface)(checked_ok)
456
CheckedOK.__name__ = "CheckedOK"
598
@dbus.service.method(_interface)
600
return self.checked_ok()
458
602
# CheckerCompleted - signal
459
@dbus.service.signal(_interface, signature="nxs")
603
@dbus.service.signal(_interface, signature=u"nxs")
460
604
def CheckerCompleted(self, exitcode, waitstatus, command):
464
608
# CheckerStarted - signal
465
@dbus.service.signal(_interface, signature="s")
609
@dbus.service.signal(_interface, signature=u"s")
466
610
def CheckerStarted(self, command):
470
614
# GetAllProperties - method
471
@dbus.service.method(_interface, out_signature="a{sv}")
615
@dbus.service.method(_interface, out_signature=u"a{sv}")
472
616
def GetAllProperties(self):
474
618
return dbus.Dictionary({
619
dbus.String(u"name"):
476
620
dbus.String(self.name, variant_level=1),
477
dbus.String("fingerprint"):
621
dbus.String(u"fingerprint"):
478
622
dbus.String(self.fingerprint, variant_level=1),
623
dbus.String(u"host"):
480
624
dbus.String(self.host, variant_level=1),
481
dbus.String("created"):
482
_datetime_to_dbus(self.created, variant_level=1),
483
dbus.String("last_enabled"):
484
(_datetime_to_dbus(self.last_enabled,
625
dbus.String(u"created"):
626
self._datetime_to_dbus(self.created,
628
dbus.String(u"last_enabled"):
629
(self._datetime_to_dbus(self.last_enabled,
486
631
if self.last_enabled is not None
487
632
else dbus.Boolean(False, variant_level=1)),
488
dbus.String("enabled"):
633
dbus.String(u"enabled"):
489
634
dbus.Boolean(self.enabled, variant_level=1),
490
dbus.String("last_checked_ok"):
491
(_datetime_to_dbus(self.last_checked_ok,
635
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
493
638
if self.last_checked_ok is not None
494
639
else dbus.Boolean (False, variant_level=1)),
495
dbus.String("timeout"):
640
dbus.String(u"timeout"):
496
641
dbus.UInt64(self.timeout_milliseconds(),
497
642
variant_level=1),
498
dbus.String("interval"):
643
dbus.String(u"interval"):
499
644
dbus.UInt64(self.interval_milliseconds(),
500
645
variant_level=1),
501
dbus.String("checker"):
646
dbus.String(u"checker"):
502
647
dbus.String(self.checker_command,
503
648
variant_level=1),
504
dbus.String("checker_running"):
649
dbus.String(u"checker_running"):
505
650
dbus.Boolean(self.checker is not None,
506
651
variant_level=1),
507
dbus.String("object_path"):
652
dbus.String(u"object_path"):
508
653
dbus.ObjectPath(self.dbus_object_path,
512
657
# IsStillValid - method
513
IsStillValid = (dbus.service.method(_interface, out_signature="b")
515
IsStillValid.__name__ = "IsStillValid"
658
@dbus.service.method(_interface, out_signature=u"b")
659
def IsStillValid(self):
660
return self.still_valid()
517
662
# PropertyChanged - signal
518
@dbus.service.signal(_interface, signature="sv")
663
@dbus.service.signal(_interface, signature=u"sv")
519
664
def PropertyChanged(self, property, value):
668
# ReceivedSecret - signal
669
@dbus.service.signal(_interface)
670
def ReceivedSecret(self):
675
@dbus.service.signal(_interface)
523
680
# SetChecker - method
524
@dbus.service.method(_interface, in_signature="s")
681
@dbus.service.method(_interface, in_signature=u"s")
525
682
def SetChecker(self, checker):
526
683
"D-Bus setter method"
527
684
self.checker_command = checker
583
742
# StopChecker - method
584
StopChecker = dbus.service.method(_interface)(stop_checker)
585
StopChecker.__name__ = "StopChecker"
743
@dbus.service.method(_interface)
744
def StopChecker(self):
590
def peer_certificate(session):
591
"Return the peer's OpenPGP certificate as a bytestring"
592
# If not an OpenPGP certificate...
593
if (gnutls.library.functions
594
.gnutls_certificate_type_get(session._c_object)
595
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
596
# ...do the normal thing
597
return session.peer_certificate
598
list_size = ctypes.c_uint(1)
599
cert_list = (gnutls.library.functions
600
.gnutls_certificate_get_peers
601
(session._c_object, ctypes.byref(list_size)))
602
if not bool(cert_list) and list_size.value != 0:
603
raise gnutls.errors.GNUTLSError("error getting peer"
605
if list_size.value == 0:
608
return ctypes.string_at(cert.data, cert.size)
611
def fingerprint(openpgp):
612
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
613
# New GnuTLS "datum" with the OpenPGP public key
614
datum = (gnutls.library.types
615
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
618
ctypes.c_uint(len(openpgp))))
619
# New empty GnuTLS certificate
620
crt = gnutls.library.types.gnutls_openpgp_crt_t()
621
(gnutls.library.functions
622
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
623
# Import the OpenPGP public key into the certificate
624
(gnutls.library.functions
625
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
626
gnutls.library.constants
627
.GNUTLS_OPENPGP_FMT_RAW))
628
# Verify the self signature in the key
629
crtverify = ctypes.c_uint()
630
(gnutls.library.functions
631
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
632
if crtverify.value != 0:
633
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
634
raise gnutls.errors.CertificateSecurityError("Verify failed")
635
# New buffer for the fingerprint
636
buf = ctypes.create_string_buffer(20)
637
buf_len = ctypes.c_size_t()
638
# Get the fingerprint from the certificate into the buffer
639
(gnutls.library.functions
640
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
641
ctypes.byref(buf_len)))
642
# Deinit the certificate
643
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
644
# Convert the buffer to a Python bytestring
645
fpr = ctypes.string_at(buf, buf_len.value)
646
# Convert the bytestring to hexadecimal notation
647
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
651
class TCP_handler(SocketServer.BaseRequestHandler, object):
652
"""A TCP request handler class.
653
Instantiated by IPv6_TCPServer for each request to handle it.
750
class ClientHandler(socketserver.BaseRequestHandler, object):
751
"""A class to handle client connections.
753
Instantiated once for each connection to handle it.
654
754
Note: This will run in its own forked process."""
656
756
def handle(self):
657
757
logger.info(u"TCP connection from: %s",
658
758
unicode(self.client_address))
659
logger.debug(u"Pipe: %d", self.server.pipe[1])
759
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
660
760
# Open IPC pipe to parent process
661
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
761
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
662
762
session = (gnutls.connection
663
763
.ClientSession(self.request,
664
764
gnutls.connection
733
830
- (sent_size + sent))
734
831
sent_size += sent
738
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
739
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
740
Assumes a gobject.MainLoop event loop.
835
def peer_certificate(session):
836
"Return the peer's OpenPGP certificate as a bytestring"
837
# If not an OpenPGP certificate...
838
if (gnutls.library.functions
839
.gnutls_certificate_type_get(session._c_object)
840
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
841
# ...do the normal thing
842
return session.peer_certificate
843
list_size = ctypes.c_uint(1)
844
cert_list = (gnutls.library.functions
845
.gnutls_certificate_get_peers
846
(session._c_object, ctypes.byref(list_size)))
847
if not bool(cert_list) and list_size.value != 0:
848
raise gnutls.errors.GNUTLSError(u"error getting peer"
850
if list_size.value == 0:
853
return ctypes.string_at(cert.data, cert.size)
856
def fingerprint(openpgp):
857
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
858
# New GnuTLS "datum" with the OpenPGP public key
859
datum = (gnutls.library.types
860
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
863
ctypes.c_uint(len(openpgp))))
864
# New empty GnuTLS certificate
865
crt = gnutls.library.types.gnutls_openpgp_crt_t()
866
(gnutls.library.functions
867
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
868
# Import the OpenPGP public key into the certificate
869
(gnutls.library.functions
870
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
871
gnutls.library.constants
872
.GNUTLS_OPENPGP_FMT_RAW))
873
# Verify the self signature in the key
874
crtverify = ctypes.c_uint()
875
(gnutls.library.functions
876
.gnutls_openpgp_crt_verify_self(crt, 0,
877
ctypes.byref(crtverify)))
878
if crtverify.value != 0:
879
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
880
raise (gnutls.errors.CertificateSecurityError
882
# New buffer for the fingerprint
883
buf = ctypes.create_string_buffer(20)
884
buf_len = ctypes.c_size_t()
885
# Get the fingerprint from the certificate into the buffer
886
(gnutls.library.functions
887
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
888
ctypes.byref(buf_len)))
889
# Deinit the certificate
890
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
891
# Convert the buffer to a Python bytestring
892
fpr = ctypes.string_at(buf, buf_len.value)
893
# Convert the bytestring to hexadecimal notation
894
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
898
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
899
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
742
900
def process_request(self, request, client_address):
743
"""This overrides and wraps the original process_request().
901
"""Overrides and wraps the original process_request().
744
903
This function creates a new pipe in self.pipe
746
905
self.pipe = os.pipe()
747
906
super(ForkingMixInWithPipe,
748
907
self).process_request(request, client_address)
749
908
os.close(self.pipe[1]) # close write end
750
# Call "handle_ipc" for both data and EOF events
751
gobject.io_add_watch(self.pipe[0],
752
gobject.IO_IN | gobject.IO_HUP,
754
def handle_ipc(source, condition):
909
self.add_pipe(self.pipe[0])
910
def add_pipe(self, pipe):
755
911
"""Dummy function; override as necessary"""
760
915
class IPv6_TCPServer(ForkingMixInWithPipe,
761
SocketServer.TCPServer, object):
762
"""IPv6 TCP server. Accepts 'None' as address and/or port.
916
socketserver.TCPServer, object):
917
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
764
settings: Server settings
765
clients: Set() of Client objects
766
920
enabled: Boolean; whether this server is activated yet
921
interface: None or a network interface name (string)
922
use_ipv6: Boolean; to use IPv6 or not
768
address_family = socket.AF_INET6
769
def __init__(self, *args, **kwargs):
770
if "settings" in kwargs:
771
self.settings = kwargs["settings"]
772
del kwargs["settings"]
773
if "clients" in kwargs:
774
self.clients = kwargs["clients"]
775
del kwargs["clients"]
777
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
924
def __init__(self, server_address, RequestHandlerClass,
925
interface=None, use_ipv6=True):
926
self.interface = interface
928
self.address_family = socket.AF_INET6
929
socketserver.TCPServer.__init__(self, server_address,
778
931
def server_bind(self):
779
932
"""This overrides the normal server_bind() function
780
933
to bind to an interface if one was specified, and also NOT to
781
934
bind to an address or port if they were not specified."""
782
if self.settings["interface"]:
783
# 25 is from /usr/include/asm-i486/socket.h
784
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
786
self.socket.setsockopt(socket.SOL_SOCKET,
788
self.settings["interface"])
789
except socket.error, error:
790
if error[0] == errno.EPERM:
791
logger.error(u"No permission to"
792
u" bind to interface %s",
793
self.settings["interface"])
935
if self.interface is not None:
936
if SO_BINDTODEVICE is None:
937
logger.error(u"SO_BINDTODEVICE does not exist;"
938
u" cannot bind to interface %s",
942
self.socket.setsockopt(socket.SOL_SOCKET,
946
except socket.error, error:
947
if error[0] == errno.EPERM:
948
logger.error(u"No permission to"
949
u" bind to interface %s",
951
elif error[0] == errno.ENOPROTOOPT:
952
logger.error(u"SO_BINDTODEVICE not available;"
953
u" cannot bind to interface %s",
796
957
# Only bind(2) the socket if we really need to.
797
958
if self.server_address[0] or self.server_address[1]:
798
959
if not self.server_address[0]:
800
self.server_address = (in6addr_any,
960
if self.address_family == socket.AF_INET6:
961
any_address = u"::" # in6addr_any
963
any_address = socket.INADDR_ANY
964
self.server_address = (any_address,
801
965
self.server_address[1])
802
966
elif not self.server_address[1]:
803
967
self.server_address = (self.server_address[0],
805
# if self.settings["interface"]:
806
970
# self.server_address = (self.server_address[0],
812
return super(IPv6_TCPServer, self).server_bind()
975
return socketserver.TCPServer.server_bind(self)
978
class MandosServer(IPv6_TCPServer):
982
clients: set of Client objects
983
gnutls_priority GnuTLS priority string
984
use_dbus: Boolean; to emit D-Bus signals or not
985
clients: set of Client objects
986
gnutls_priority GnuTLS priority string
987
use_dbus: Boolean; to emit D-Bus signals or not
989
Assumes a gobject.MainLoop event loop.
991
def __init__(self, server_address, RequestHandlerClass,
992
interface=None, use_ipv6=True, clients=None,
993
gnutls_priority=None, use_dbus=True):
995
self.clients = clients
996
if self.clients is None:
998
self.use_dbus = use_dbus
999
self.gnutls_priority = gnutls_priority
1000
IPv6_TCPServer.__init__(self, server_address,
1001
RequestHandlerClass,
1002
interface = interface,
1003
use_ipv6 = use_ipv6)
813
1004
def server_activate(self):
814
1005
if self.enabled:
815
return super(IPv6_TCPServer, self).server_activate()
1006
return socketserver.TCPServer.server_activate(self)
816
1007
def enable(self):
817
1008
self.enabled = True
1009
def add_pipe(self, pipe):
1010
# Call "handle_ipc" for both data and EOF events
1011
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
818
1013
def handle_ipc(self, source, condition, file_objects={}):
819
logger.debug("Handling IPC: %r : %r", source, condition)
1015
gobject.IO_IN: u"IN", # There is data to read.
1016
gobject.IO_OUT: u"OUT", # Data can be written (without
1018
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1019
gobject.IO_ERR: u"ERR", # Error condition.
1020
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1021
# broken, usually for pipes and
1024
conditions_string = ' | '.join(name
1026
condition_names.iteritems()
1027
if cond & condition)
1028
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
821
# Turn a file descriptor into a Python file object
1031
# Turn the pipe file descriptor into a Python file object
822
1032
if source not in file_objects:
823
file_objects[source] = os.fdopen(source, "r", 1)
1033
file_objects[source] = os.fdopen(source, u"r", 1)
825
1035
# Read a line from the file object
826
1036
cmdline = file_objects[source].readline()
827
1037
if not cmdline: # Empty line means end of file
828
1038
# close the IPC pipe
829
logger.debug("Closing: %r", source)
830
1039
file_objects[source].close()
831
1040
del file_objects[source]
833
1042
# Stop calling this function
836
logger.debug("IPC command: %r\n" % cmdline)
1045
logger.debug(u"IPC command: %r", cmdline)
838
1047
# Parse and act on command
839
cmd, args = cmdline.split(None, 1)
840
if cmd == "NOTFOUND":
842
elif cmd == "INVALID":
844
elif cmd == "SENDING":
1048
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1050
if cmd == u"NOTFOUND":
1051
logger.warning(u"Client not found for fingerprint: %s",
1055
mandos_dbus_service.ClientNotFound(args)
1056
elif cmd == u"INVALID":
1057
for client in self.clients:
1058
if client.name == args:
1059
logger.warning(u"Client %s is invalid", args)
1065
logger.error(u"Unknown client %s is invalid", args)
1066
elif cmd == u"SENDING":
1067
for client in self.clients:
1068
if client.name == args:
1069
logger.info(u"Sending secret to %s", client.name)
1073
client.ReceivedSecret()
1076
logger.error(u"Sending secret to unknown client %s",
847
logger.error("Unknown IPC command: %r", cmdline)
1079
logger.error(u"Unknown IPC command: %r", cmdline)
849
1081
# Keep calling this function
889
1121
return timevalue
892
def server_state_changed(state):
893
"""Derived from the Avahi example code"""
894
if state == avahi.SERVER_COLLISION:
895
logger.error(u"Zeroconf server name collision")
897
elif state == avahi.SERVER_RUNNING:
901
def entry_group_state_changed(state, error):
902
"""Derived from the Avahi example code"""
903
logger.debug(u"Avahi state change: %i", state)
905
if state == avahi.ENTRY_GROUP_ESTABLISHED:
906
logger.debug(u"Zeroconf service established.")
907
elif state == avahi.ENTRY_GROUP_COLLISION:
908
logger.warning(u"Zeroconf service name collision.")
910
elif state == avahi.ENTRY_GROUP_FAILURE:
911
logger.critical(u"Avahi: Error in group state changed %s",
913
raise AvahiGroupError(u"State changed: %s" % unicode(error))
915
1124
def if_nametoindex(interface):
916
"""Call the C function if_nametoindex(), or equivalent"""
1125
"""Call the C function if_nametoindex(), or equivalent
1127
Note: This function cannot accept a unicode string."""
917
1128
global if_nametoindex
919
1130
if_nametoindex = (ctypes.cdll.LoadLibrary
920
(ctypes.util.find_library("c"))
1131
(ctypes.util.find_library(u"c"))
921
1132
.if_nametoindex)
922
1133
except (OSError, AttributeError):
923
if "struct" not in sys.modules:
925
if "fcntl" not in sys.modules:
1134
logger.warning(u"Doing if_nametoindex the hard way")
927
1135
def if_nametoindex(interface):
928
1136
"Get an interface index the hard way, i.e. using fcntl()"
929
1137
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
930
1138
with closing(socket.socket()) as s:
931
1139
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
932
struct.pack("16s16x", interface))
933
interface_index = struct.unpack("I", ifreq[16:20])[0]
1140
struct.pack(str(u"16s16x"),
1142
interface_index = struct.unpack(str(u"I"),
934
1144
return interface_index
935
1145
return if_nametoindex(interface)
938
1148
def daemon(nochdir = False, noclose = False):
939
1149
"""See daemon(3). Standard BSD Unix function.
940
1151
This should really exist as os.daemon, but it doesn't (yet)."""
1174
######################################################################
1175
# Parsing of options, both command line and config file
962
1177
parser = optparse.OptionParser(version = "%%prog %s" % version)
963
parser.add_option("-i", "--interface", type="string",
964
metavar="IF", help="Bind to interface IF")
965
parser.add_option("-a", "--address", type="string",
966
help="Address to listen for requests on")
967
parser.add_option("-p", "--port", type="int",
968
help="Port number to receive requests on")
969
parser.add_option("--check", action="store_true",
970
help="Run self-test")
971
parser.add_option("--debug", action="store_true",
972
help="Debug mode; run in foreground and log to"
974
parser.add_option("--priority", type="string", help="GnuTLS"
975
" priority string (see GnuTLS documentation)")
976
parser.add_option("--servicename", type="string", metavar="NAME",
977
help="Zeroconf service name")
978
parser.add_option("--configdir", type="string",
979
default="/etc/mandos", metavar="DIR",
980
help="Directory to search for configuration"
982
parser.add_option("--no-dbus", action="store_false",
984
help="Do not provide D-Bus system bus"
1178
parser.add_option("-i", u"--interface", type=u"string",
1179
metavar="IF", help=u"Bind to interface IF")
1180
parser.add_option("-a", u"--address", type=u"string",
1181
help=u"Address to listen for requests on")
1182
parser.add_option("-p", u"--port", type=u"int",
1183
help=u"Port number to receive requests on")
1184
parser.add_option("--check", action=u"store_true",
1185
help=u"Run self-test")
1186
parser.add_option("--debug", action=u"store_true",
1187
help=u"Debug mode; run in foreground and log to"
1189
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1190
u" priority string (see GnuTLS documentation)")
1191
parser.add_option("--servicename", type=u"string",
1192
metavar=u"NAME", help=u"Zeroconf service name")
1193
parser.add_option("--configdir", type=u"string",
1194
default=u"/etc/mandos", metavar=u"DIR",
1195
help=u"Directory to search for configuration"
1197
parser.add_option("--no-dbus", action=u"store_false",
1198
dest=u"use_dbus", help=u"Do not provide D-Bus"
1199
u" system bus interface")
1200
parser.add_option("--no-ipv6", action=u"store_false",
1201
dest=u"use_ipv6", help=u"Do not use IPv6")
986
1202
options = parser.parse_args()[0]
988
1204
if options.check:
993
1209
# Default values for config file for server-global settings
994
server_defaults = { "interface": "",
999
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1000
"servicename": "Mandos",
1210
server_defaults = { u"interface": u"",
1215
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1216
u"servicename": u"Mandos",
1217
u"use_dbus": u"True",
1218
u"use_ipv6": u"True",
1004
1221
# Parse config file for server-global settings
1005
server_config = ConfigParser.SafeConfigParser(server_defaults)
1222
server_config = configparser.SafeConfigParser(server_defaults)
1006
1223
del server_defaults
1007
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1224
server_config.read(os.path.join(options.configdir,
1008
1226
# Convert the SafeConfigParser object to a dict
1009
1227
server_settings = server_config.defaults()
1010
1228
# Use the appropriate methods on the non-string config options
1011
server_settings["debug"] = server_config.getboolean("DEFAULT",
1013
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1229
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1230
server_settings[option] = server_config.getboolean(u"DEFAULT",
1015
1232
if server_settings["port"]:
1016
server_settings["port"] = server_config.getint("DEFAULT",
1233
server_settings["port"] = server_config.getint(u"DEFAULT",
1018
1235
del server_config
1020
1237
# Override the settings from the config file with command line
1021
1238
# options, if set.
1022
for option in ("interface", "address", "port", "debug",
1023
"priority", "servicename", "configdir",
1239
for option in (u"interface", u"address", u"port", u"debug",
1240
u"priority", u"servicename", u"configdir",
1241
u"use_dbus", u"use_ipv6"):
1025
1242
value = getattr(options, option)
1026
1243
if value is not None:
1027
1244
server_settings[option] = value
1246
# Force all strings to be unicode
1247
for option in server_settings.keys():
1248
if type(server_settings[option]) is str:
1249
server_settings[option] = unicode(server_settings[option])
1029
1250
# Now we have our good server settings in "server_settings"
1252
##################################################################
1031
1254
# For convenience
1032
debug = server_settings["debug"]
1033
use_dbus = server_settings["use_dbus"]
1255
debug = server_settings[u"debug"]
1256
use_dbus = server_settings[u"use_dbus"]
1257
use_ipv6 = server_settings[u"use_ipv6"]
1036
1260
syslogger.setLevel(logging.WARNING)
1037
1261
console.setLevel(logging.WARNING)
1039
if server_settings["servicename"] != "Mandos":
1263
if server_settings[u"servicename"] != u"Mandos":
1040
1264
syslogger.setFormatter(logging.Formatter
1041
('Mandos (%s): %%(levelname)s:'
1043
% server_settings["servicename"]))
1265
(u'Mandos (%s) [%%(process)d]:'
1266
u' %%(levelname)s: %%(message)s'
1267
% server_settings[u"servicename"]))
1045
1269
# Parse config file with clients
1046
client_defaults = { "timeout": "1h",
1048
"checker": "fping -q -- %%(host)s",
1270
client_defaults = { u"timeout": u"1h",
1272
u"checker": u"fping -q -- %%(host)s",
1051
client_config = ConfigParser.SafeConfigParser(client_defaults)
1052
client_config.read(os.path.join(server_settings["configdir"],
1056
tcp_server = IPv6_TCPServer((server_settings["address"],
1057
server_settings["port"]),
1059
settings=server_settings,
1061
pidfilename = "/var/run/mandos.pid"
1063
pidfile = open(pidfilename, "w")
1064
except IOError, error:
1065
logger.error("Could not open file %r", pidfilename)
1068
uid = pwd.getpwnam("_mandos").pw_uid
1069
gid = pwd.getpwnam("_mandos").pw_gid
1275
client_config = configparser.SafeConfigParser(client_defaults)
1276
client_config.read(os.path.join(server_settings[u"configdir"],
1279
global mandos_dbus_service
1280
mandos_dbus_service = None
1282
tcp_server = MandosServer((server_settings[u"address"],
1283
server_settings[u"port"]),
1285
interface=server_settings[u"interface"],
1288
server_settings[u"priority"],
1290
pidfilename = u"/var/run/mandos.pid"
1292
pidfile = open(pidfilename, u"w")
1294
logger.error(u"Could not open file %r", pidfilename)
1297
uid = pwd.getpwnam(u"_mandos").pw_uid
1298
gid = pwd.getpwnam(u"_mandos").pw_gid
1070
1299
except KeyError:
1072
uid = pwd.getpwnam("mandos").pw_uid
1073
gid = pwd.getpwnam("mandos").pw_gid
1301
uid = pwd.getpwnam(u"mandos").pw_uid
1302
gid = pwd.getpwnam(u"mandos").pw_gid
1074
1303
except KeyError:
1076
uid = pwd.getpwnam("nobody").pw_uid
1077
gid = pwd.getpwnam("nogroup").pw_gid
1305
uid = pwd.getpwnam(u"nobody").pw_uid
1306
gid = pwd.getpwnam(u"nobody").pw_gid
1078
1307
except KeyError:
1084
1313
except OSError, error:
1085
1314
if error[0] != errno.EPERM:
1089
service = AvahiService(name = server_settings["servicename"],
1090
servicetype = "_mandos._tcp", )
1091
if server_settings["interface"]:
1092
service.interface = (if_nametoindex
1093
(server_settings["interface"]))
1317
# Enable all possible GnuTLS debugging
1319
# "Use a log level over 10 to enable all debugging options."
1321
gnutls.library.functions.gnutls_global_set_log_level(11)
1323
@gnutls.library.types.gnutls_log_func
1324
def debug_gnutls(level, string):
1325
logger.debug(u"GnuTLS: %s", string[:-1])
1327
(gnutls.library.functions
1328
.gnutls_global_set_log_function(debug_gnutls))
1095
1330
global main_loop
1098
1331
# From the Avahi example code
1099
1332
DBusGMainLoop(set_as_default=True )
1100
1333
main_loop = gobject.MainLoop()
1101
1334
bus = dbus.SystemBus()
1102
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1103
avahi.DBUS_PATH_SERVER),
1104
avahi.DBUS_INTERFACE_SERVER)
1105
1335
# End of Avahi example code
1107
1337
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1338
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1339
service = AvahiService(name = server_settings[u"servicename"],
1340
servicetype = u"_mandos._tcp",
1341
protocol = protocol, bus = bus)
1342
if server_settings["interface"]:
1343
service.interface = (if_nametoindex
1344
(str(server_settings[u"interface"])))
1109
clients.update(Set(Client(name = section,
1111
= dict(client_config.items(section)),
1112
use_dbus = use_dbus)
1113
for section in client_config.sections()))
1346
client_class = Client
1348
client_class = functools.partial(ClientDBus, bus = bus)
1349
tcp_server.clients.update(set(
1350
client_class(name = section,
1351
config= dict(client_config.items(section)))
1352
for section in client_config.sections()))
1353
if not tcp_server.clients:
1115
1354
logger.warning(u"No clients defined")
1161
1395
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1164
class MandosServer(dbus.service.Object):
1398
class MandosDBusService(dbus.service.Object):
1165
1399
"""A D-Bus proxy object"""
1166
1400
def __init__(self):
1167
dbus.service.Object.__init__(self, bus, "/")
1401
dbus.service.Object.__init__(self, bus, u"/")
1168
1402
_interface = u"se.bsnet.fukt.Mandos"
1170
@dbus.service.signal(_interface, signature="oa{sv}")
1404
@dbus.service.signal(_interface, signature=u"oa{sv}")
1171
1405
def ClientAdded(self, objpath, properties):
1175
@dbus.service.signal(_interface, signature="os")
1409
@dbus.service.signal(_interface, signature=u"s")
1410
def ClientNotFound(self, fingerprint):
1414
@dbus.service.signal(_interface, signature=u"os")
1176
1415
def ClientRemoved(self, objpath, name):
1180
@dbus.service.method(_interface, out_signature="ao")
1419
@dbus.service.method(_interface, out_signature=u"ao")
1181
1420
def GetAllClients(self):
1183
return dbus.Array(c.dbus_object_path for c in clients)
1422
return dbus.Array(c.dbus_object_path
1423
for c in tcp_server.clients)
1185
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1425
@dbus.service.method(_interface,
1426
out_signature=u"a{oa{sv}}")
1186
1427
def GetAllClientsWithProperties(self):
1188
1429
return dbus.Dictionary(
1189
1430
((c.dbus_object_path, c.GetAllProperties())
1431
for c in tcp_server.clients),
1432
signature=u"oa{sv}")
1193
@dbus.service.method(_interface, in_signature="o")
1434
@dbus.service.method(_interface, in_signature=u"o")
1194
1435
def RemoveClient(self, object_path):
1437
for c in tcp_server.clients:
1197
1438
if c.dbus_object_path == object_path:
1439
tcp_server.clients.remove(c)
1440
c.remove_from_connection()
1199
1441
# Don't signal anything except ClientRemoved
1442
c.disable(signal=False)
1202
1443
# Emit D-Bus signal
1203
1444
self.ClientRemoved(object_path, c.name)