152
133
u" after %i retries, exiting.",
153
134
self.rename_count)
154
135
raise AvahiServiceError(u"Too many renames")
155
self.name = self.server.GetAlternativeServiceName(self.name)
136
self.name = server.GetAlternativeServiceName(self.name)
156
137
logger.info(u"Changing Zeroconf service name to %r ...",
158
139
syslogger.setFormatter(logging.Formatter
159
(u'Mandos (%s) [%%(process)d]:'
160
u' %%(levelname)s: %%(message)s'
140
('Mandos (%s) [%%(process)d]:'
141
' %%(levelname)s: %%(message)s'
164
145
self.rename_count += 1
165
146
def remove(self):
166
147
"""Derived from the Avahi example code"""
167
if self.group is not None:
148
if group is not None:
170
151
"""Derived from the Avahi example code"""
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)
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)
178
160
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
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())
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
174
# 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)
227
182
class Client(object):
228
183
"""A representation of a client host served by this server.
231
185
name: string; from the config file, used in log messages and
232
186
D-Bus identifiers
281
231
# Uppercase and remove spaces from fingerprint for later
282
232
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
234
self.fingerprint = (config["fingerprint"].upper()
285
235
.replace(u" ", u""))
286
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
237
if "secret" in config:
238
self.secret = config["secret"].decode(u"base64")
239
elif "secfile" in config:
290
240
with closing(open(os.path.expanduser
291
241
(os.path.expandvars
292
(config[u"secfile"])))) as secfile:
242
(config["secfile"])))) as secfile:
293
243
self.secret = secfile.read()
295
245
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
247
self.host = config.get("host", "")
298
248
self.created = datetime.datetime.utcnow()
299
249
self.enabled = False
300
250
self.last_enabled = None
301
251
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
252
self.timeout = string_to_delta(config["timeout"])
253
self.interval = string_to_delta(config["interval"])
304
254
self.disable_hook = disable_hook
305
255
self.checker = None
306
256
self.checker_initiator_tag = None
307
257
self.disable_initiator_tag = None
308
258
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
259
self.checker_command = config["checker"]
310
260
self.current_checker_command = None
311
261
self.last_connect = None
313
263
def enable(self):
314
264
"""Start this client's checker and timeout hooks"""
315
if getattr(self, u"enabled", False):
318
265
self.last_enabled = datetime.datetime.utcnow()
319
266
# Schedule a new checker to be started an 'interval' from now,
320
267
# and every interval from then on.
480
422
class ClientDBus(Client, dbus.service.Object):
481
423
"""A Client class using D-Bus
484
dbus_object_path: dbus.ObjectPath
485
bus: dbus.SystemBus()
425
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
487
427
# dbus.service.Object doesn't use super(), so we can't either.
489
def __init__(self, bus = None, *args, **kwargs):
429
def __init__(self, *args, **kwargs):
491
430
Client.__init__(self, *args, **kwargs)
492
431
# Only now, when this client is initialized, can it show up on
494
433
self.dbus_object_path = (dbus.ObjectPath
496
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
435
+ self.name.replace(".", "_")))
436
dbus.service.Object.__init__(self, bus,
498
437
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)
506
438
def enable(self):
507
oldstate = getattr(self, u"enabled", False)
439
oldstate = getattr(self, "enabled", False)
508
440
r = Client.enable(self)
509
441
if oldstate != self.enabled:
510
442
# Emit D-Bus signals
511
443
self.PropertyChanged(dbus.String(u"enabled"),
512
444
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
445
self.PropertyChanged(dbus.String(u"last_enabled"),
446
(_datetime_to_dbus(self.last_enabled,
519
450
def disable(self, signal = True):
520
oldstate = getattr(self, u"enabled", False)
451
oldstate = getattr(self, "enabled", False)
521
452
r = Client.disable(self)
522
453
if signal and oldstate != self.enabled:
523
454
# Emit D-Bus signal
573
503
old_checker_pid = None
574
504
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):
505
# Only emit D-Bus signal if new checker process was started
506
if ((self.checker is not None)
507
and not (old_checker is not None
508
and old_checker_pid == self.checker.pid)):
579
509
self.CheckerStarted(self.current_checker_command)
580
510
self.PropertyChanged(
581
dbus.String(u"checker_running"),
511
dbus.String("checker_running"),
582
512
dbus.Boolean(True, variant_level=1))
585
515
def stop_checker(self, *args, **kwargs):
586
old_checker = getattr(self, u"checker", None)
516
old_checker = getattr(self, "checker", None)
587
517
r = Client.stop_checker(self, *args, **kwargs)
588
518
if (old_checker is not None
589
and getattr(self, u"checker", None) is None):
519
and getattr(self, "checker", None) is None):
590
520
self.PropertyChanged(dbus.String(u"checker_running"),
591
521
dbus.Boolean(False, variant_level=1))
595
525
_interface = u"se.bsnet.fukt.Mandos.Client"
597
527
# CheckedOK - method
598
@dbus.service.method(_interface)
600
return self.checked_ok()
528
CheckedOK = dbus.service.method(_interface)(checked_ok)
529
CheckedOK.__name__ = "CheckedOK"
602
531
# CheckerCompleted - signal
603
@dbus.service.signal(_interface, signature=u"nxs")
532
@dbus.service.signal(_interface, signature="nxs")
604
533
def CheckerCompleted(self, exitcode, waitstatus, command):
608
537
# CheckerStarted - signal
609
@dbus.service.signal(_interface, signature=u"s")
538
@dbus.service.signal(_interface, signature="s")
610
539
def CheckerStarted(self, command):
614
543
# GetAllProperties - method
615
@dbus.service.method(_interface, out_signature=u"a{sv}")
544
@dbus.service.method(_interface, out_signature="a{sv}")
616
545
def GetAllProperties(self):
618
547
return dbus.Dictionary({
619
dbus.String(u"name"):
620
549
dbus.String(self.name, variant_level=1),
621
dbus.String(u"fingerprint"):
550
dbus.String("fingerprint"):
622
551
dbus.String(self.fingerprint, variant_level=1),
623
dbus.String(u"host"):
624
553
dbus.String(self.host, variant_level=1),
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,
554
dbus.String("created"):
555
_datetime_to_dbus(self.created, variant_level=1),
556
dbus.String("last_enabled"):
557
(_datetime_to_dbus(self.last_enabled,
631
559
if self.last_enabled is not None
632
560
else dbus.Boolean(False, variant_level=1)),
633
dbus.String(u"enabled"):
561
dbus.String("enabled"):
634
562
dbus.Boolean(self.enabled, variant_level=1),
635
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
563
dbus.String("last_checked_ok"):
564
(_datetime_to_dbus(self.last_checked_ok,
638
566
if self.last_checked_ok is not None
639
567
else dbus.Boolean (False, variant_level=1)),
640
dbus.String(u"timeout"):
568
dbus.String("timeout"):
641
569
dbus.UInt64(self.timeout_milliseconds(),
642
570
variant_level=1),
643
dbus.String(u"interval"):
571
dbus.String("interval"):
644
572
dbus.UInt64(self.interval_milliseconds(),
645
573
variant_level=1),
646
dbus.String(u"checker"):
574
dbus.String("checker"):
647
575
dbus.String(self.checker_command,
648
576
variant_level=1),
649
dbus.String(u"checker_running"):
577
dbus.String("checker_running"):
650
578
dbus.Boolean(self.checker is not None,
651
579
variant_level=1),
652
dbus.String(u"object_path"):
580
dbus.String("object_path"):
653
581
dbus.ObjectPath(self.dbus_object_path,
657
585
# IsStillValid - method
658
@dbus.service.method(_interface, out_signature=u"b")
586
@dbus.service.method(_interface, out_signature="b")
659
587
def IsStillValid(self):
660
588
return self.still_valid()
662
590
# PropertyChanged - signal
663
@dbus.service.signal(_interface, signature=u"sv")
591
@dbus.service.signal(_interface, signature="sv")
664
592
def PropertyChanged(self, property, value):
742
668
# StopChecker - method
743
@dbus.service.method(_interface)
744
def StopChecker(self):
669
StopChecker = dbus.service.method(_interface)(stop_checker)
670
StopChecker.__name__ = "StopChecker"
750
class ClientHandler(socketserver.BaseRequestHandler, object):
751
"""A class to handle client connections.
753
Instantiated once for each connection to handle it.
675
def peer_certificate(session):
676
"Return the peer's OpenPGP certificate as a bytestring"
677
# If not an OpenPGP certificate...
678
if (gnutls.library.functions
679
.gnutls_certificate_type_get(session._c_object)
680
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
681
# ...do the normal thing
682
return session.peer_certificate
683
list_size = ctypes.c_uint(1)
684
cert_list = (gnutls.library.functions
685
.gnutls_certificate_get_peers
686
(session._c_object, ctypes.byref(list_size)))
687
if not bool(cert_list) and list_size.value != 0:
688
raise gnutls.errors.GNUTLSError("error getting peer"
690
if list_size.value == 0:
693
return ctypes.string_at(cert.data, cert.size)
696
def fingerprint(openpgp):
697
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
698
# New GnuTLS "datum" with the OpenPGP public key
699
datum = (gnutls.library.types
700
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
703
ctypes.c_uint(len(openpgp))))
704
# New empty GnuTLS certificate
705
crt = gnutls.library.types.gnutls_openpgp_crt_t()
706
(gnutls.library.functions
707
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
708
# Import the OpenPGP public key into the certificate
709
(gnutls.library.functions
710
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
711
gnutls.library.constants
712
.GNUTLS_OPENPGP_FMT_RAW))
713
# Verify the self signature in the key
714
crtverify = ctypes.c_uint()
715
(gnutls.library.functions
716
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
717
if crtverify.value != 0:
718
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
719
raise gnutls.errors.CertificateSecurityError("Verify failed")
720
# New buffer for the fingerprint
721
buf = ctypes.create_string_buffer(20)
722
buf_len = ctypes.c_size_t()
723
# Get the fingerprint from the certificate into the buffer
724
(gnutls.library.functions
725
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
726
ctypes.byref(buf_len)))
727
# Deinit the certificate
728
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
729
# Convert the buffer to a Python bytestring
730
fpr = ctypes.string_at(buf, buf_len.value)
731
# Convert the bytestring to hexadecimal notation
732
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
736
class TCP_handler(SocketServer.BaseRequestHandler, object):
737
"""A TCP request handler class.
738
Instantiated by IPv6_TCPServer for each request to handle it.
754
739
Note: This will run in its own forked process."""
756
741
def handle(self):
831
817
- (sent_size + sent))
832
818
sent_size += sent
836
def peer_certificate(session):
837
"Return the peer's OpenPGP certificate as a bytestring"
838
# If not an OpenPGP certificate...
839
if (gnutls.library.functions
840
.gnutls_certificate_type_get(session._c_object)
841
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
842
# ...do the normal thing
843
return session.peer_certificate
844
list_size = ctypes.c_uint(1)
845
cert_list = (gnutls.library.functions
846
.gnutls_certificate_get_peers
847
(session._c_object, ctypes.byref(list_size)))
848
if not bool(cert_list) and list_size.value != 0:
849
raise gnutls.errors.GNUTLSError(u"error getting peer"
851
if list_size.value == 0:
854
return ctypes.string_at(cert.data, cert.size)
857
def fingerprint(openpgp):
858
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
859
# New GnuTLS "datum" with the OpenPGP public key
860
datum = (gnutls.library.types
861
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
864
ctypes.c_uint(len(openpgp))))
865
# New empty GnuTLS certificate
866
crt = gnutls.library.types.gnutls_openpgp_crt_t()
867
(gnutls.library.functions
868
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
869
# Import the OpenPGP public key into the certificate
870
(gnutls.library.functions
871
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
872
gnutls.library.constants
873
.GNUTLS_OPENPGP_FMT_RAW))
874
# Verify the self signature in the key
875
crtverify = ctypes.c_uint()
876
(gnutls.library.functions
877
.gnutls_openpgp_crt_verify_self(crt, 0,
878
ctypes.byref(crtverify)))
879
if crtverify.value != 0:
880
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
881
raise (gnutls.errors.CertificateSecurityError
883
# New buffer for the fingerprint
884
buf = ctypes.create_string_buffer(20)
885
buf_len = ctypes.c_size_t()
886
# Get the fingerprint from the certificate into the buffer
887
(gnutls.library.functions
888
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
889
ctypes.byref(buf_len)))
890
# Deinit the certificate
891
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
892
# Convert the buffer to a Python bytestring
893
fpr = ctypes.string_at(buf, buf_len.value)
894
# Convert the bytestring to hexadecimal notation
895
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
899
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
900
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
822
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
823
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
824
Assumes a gobject.MainLoop event loop.
901
826
def process_request(self, request, client_address):
902
"""Overrides and wraps the original process_request().
904
This function creates a new pipe in self.pipe
827
"""This overrides and wraps the original process_request().
828
This function creates a new pipe in self.pipe
906
830
self.pipe = os.pipe()
907
831
super(ForkingMixInWithPipe,
908
832
self).process_request(request, client_address)
909
833
os.close(self.pipe[1]) # close write end
910
self.add_pipe(self.pipe[0])
911
def add_pipe(self, pipe):
834
# Call "handle_ipc" for both data and EOF events
835
gobject.io_add_watch(self.pipe[0],
836
gobject.IO_IN | gobject.IO_HUP,
838
def handle_ipc(source, condition):
912
839
"""Dummy function; override as necessary"""
916
844
class IPv6_TCPServer(ForkingMixInWithPipe,
917
socketserver.TCPServer, object):
845
SocketServer.TCPServer, object):
918
846
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
848
settings: Server settings
849
clients: Set() of Client objects
921
850
enabled: Boolean; whether this server is activated yet
922
interface: None or a network interface name (string)
923
use_ipv6: Boolean; to use IPv6 or not
925
def __init__(self, server_address, RequestHandlerClass,
926
interface=None, use_ipv6=True):
927
self.interface = interface
929
self.address_family = socket.AF_INET6
930
socketserver.TCPServer.__init__(self, server_address,
852
address_family = socket.AF_INET6
853
def __init__(self, *args, **kwargs):
854
if "settings" in kwargs:
855
self.settings = kwargs["settings"]
856
del kwargs["settings"]
857
if "clients" in kwargs:
858
self.clients = kwargs["clients"]
859
del kwargs["clients"]
860
if "use_ipv6" in kwargs:
861
if not kwargs["use_ipv6"]:
862
self.address_family = socket.AF_INET
863
del kwargs["use_ipv6"]
865
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
932
866
def server_bind(self):
933
867
"""This overrides the normal server_bind() function
934
868
to bind to an interface if one was specified, and also NOT to
935
869
bind to an address or port if they were not specified."""
936
if self.interface is not None:
937
if SO_BINDTODEVICE is None:
938
logger.error(u"SO_BINDTODEVICE does not exist;"
939
u" cannot bind to interface %s",
943
self.socket.setsockopt(socket.SOL_SOCKET,
947
except socket.error, error:
948
if error[0] == errno.EPERM:
949
logger.error(u"No permission to"
950
u" bind to interface %s",
952
elif error[0] == errno.ENOPROTOOPT:
953
logger.error(u"SO_BINDTODEVICE not available;"
954
u" cannot bind to interface %s",
870
if self.settings["interface"]:
871
# 25 is from /usr/include/asm-i486/socket.h
872
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
874
self.socket.setsockopt(socket.SOL_SOCKET,
876
self.settings["interface"])
877
except socket.error, error:
878
if error[0] == errno.EPERM:
879
logger.error(u"No permission to"
880
u" bind to interface %s",
881
self.settings["interface"])
958
884
# Only bind(2) the socket if we really need to.
959
885
if self.server_address[0] or self.server_address[1]:
960
886
if not self.server_address[0]:
961
887
if self.address_family == socket.AF_INET6:
962
any_address = u"::" # in6addr_any
888
any_address = "::" # in6addr_any
964
890
any_address = socket.INADDR_ANY
965
891
self.server_address = (any_address,
967
893
elif not self.server_address[1]:
968
894
self.server_address = (self.server_address[0],
896
# if self.settings["interface"]:
971
897
# self.server_address = (self.server_address[0],
976
return socketserver.TCPServer.server_bind(self)
979
class MandosServer(IPv6_TCPServer):
983
clients: set of Client objects
984
gnutls_priority GnuTLS priority string
985
use_dbus: Boolean; to emit D-Bus signals or not
986
clients: set of Client objects
987
gnutls_priority GnuTLS priority string
988
use_dbus: Boolean; to emit D-Bus signals or not
990
Assumes a gobject.MainLoop event loop.
992
def __init__(self, server_address, RequestHandlerClass,
993
interface=None, use_ipv6=True, clients=None,
994
gnutls_priority=None, use_dbus=True):
996
self.clients = clients
997
if self.clients is None:
999
self.use_dbus = use_dbus
1000
self.gnutls_priority = gnutls_priority
1001
IPv6_TCPServer.__init__(self, server_address,
1002
RequestHandlerClass,
1003
interface = interface,
1004
use_ipv6 = use_ipv6)
903
return super(IPv6_TCPServer, self).server_bind()
1005
904
def server_activate(self):
1006
905
if self.enabled:
1007
return socketserver.TCPServer.server_activate(self)
906
return super(IPv6_TCPServer, self).server_activate()
1008
907
def enable(self):
1009
908
self.enabled = True
1010
def add_pipe(self, pipe):
1011
# Call "handle_ipc" for both data and EOF events
1012
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1014
909
def handle_ipc(self, source, condition, file_objects={}):
1015
910
condition_names = {
1016
gobject.IO_IN: u"IN", # There is data to read.
1017
gobject.IO_OUT: u"OUT", # Data can be written (without
1019
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1020
gobject.IO_ERR: u"ERR", # Error condition.
1021
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1022
# broken, usually for pipes and
911
gobject.IO_IN: "IN", # There is data to read.
912
gobject.IO_OUT: "OUT", # Data can be written (without
914
gobject.IO_PRI: "PRI", # There is urgent data to read.
915
gobject.IO_ERR: "ERR", # Error condition.
916
gobject.IO_HUP: "HUP" # Hung up (the connection has been
917
# broken, usually for pipes and
1025
920
conditions_string = ' | '.join(name
1026
921
for cond, name in
1027
922
condition_names.iteritems()
1028
923
if cond & condition)
1029
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
924
logger.debug("Handling IPC: FD = %d, condition = %s", source,
1030
925
conditions_string)
1032
927
# Turn the pipe file descriptor into a Python file object
1033
928
if source not in file_objects:
1034
file_objects[source] = os.fdopen(source, u"r", 1)
929
file_objects[source] = os.fdopen(source, "r", 1)
1036
931
# Read a line from the file object
1037
932
cmdline = file_objects[source].readline()
1043
938
# Stop calling this function
1046
logger.debug(u"IPC command: %r", cmdline)
941
logger.debug("IPC command: %r\n" % cmdline)
1048
943
# Parse and act on command
1049
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1051
if cmd == u"NOTFOUND":
1052
logger.warning(u"Client not found for fingerprint: %s",
944
cmd, args = cmdline.split(None, 1)
945
if cmd == "NOTFOUND":
946
if self.settings["use_dbus"]:
1055
947
# Emit D-Bus signal
1056
948
mandos_dbus_service.ClientNotFound(args)
1057
elif cmd == u"INVALID":
1058
for client in self.clients:
1059
if client.name == args:
1060
logger.warning(u"Client %s is invalid", args)
949
elif cmd == "INVALID":
950
if self.settings["use_dbus"]:
951
for client in self.clients:
952
if client.name == args:
1062
953
# Emit D-Bus signal
1063
954
client.Rejected()
1066
logger.error(u"Unknown client %s is invalid", args)
1067
elif cmd == u"SENDING":
956
elif cmd == "SENDING":
1068
957
for client in self.clients:
1069
958
if client.name == args:
1070
logger.info(u"Sending secret to %s", client.name)
1071
959
client.checked_ok()
960
if self.settings["use_dbus"]:
1073
961
# Emit D-Bus signal
1074
962
client.ReceivedSecret()
1077
logger.error(u"Sending secret to unknown client %s",
1080
logger.error(u"Unknown IPC command: %r", cmdline)
965
logger.error("Unknown IPC command: %r", cmdline)
1082
967
# Keep calling this function
1122
1007
return timevalue
1010
def server_state_changed(state):
1011
"""Derived from the Avahi example code"""
1012
if state == avahi.SERVER_COLLISION:
1013
logger.error(u"Zeroconf server name collision")
1015
elif state == avahi.SERVER_RUNNING:
1019
def entry_group_state_changed(state, error):
1020
"""Derived from the Avahi example code"""
1021
logger.debug(u"Avahi state change: %i", state)
1023
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1024
logger.debug(u"Zeroconf service established.")
1025
elif state == avahi.ENTRY_GROUP_COLLISION:
1026
logger.warning(u"Zeroconf service name collision.")
1028
elif state == avahi.ENTRY_GROUP_FAILURE:
1029
logger.critical(u"Avahi: Error in group state changed %s",
1031
raise AvahiGroupError(u"State changed: %s" % unicode(error))
1125
1033
def if_nametoindex(interface):
1126
"""Call the C function if_nametoindex(), or equivalent
1128
Note: This function cannot accept a unicode string."""
1034
"""Call the C function if_nametoindex(), or equivalent"""
1129
1035
global if_nametoindex
1131
1037
if_nametoindex = (ctypes.cdll.LoadLibrary
1132
(ctypes.util.find_library(u"c"))
1038
(ctypes.util.find_library("c"))
1133
1039
.if_nametoindex)
1134
1040
except (OSError, AttributeError):
1135
logger.warning(u"Doing if_nametoindex the hard way")
1041
if "struct" not in sys.modules:
1043
if "fcntl" not in sys.modules:
1136
1045
def if_nametoindex(interface):
1137
1046
"Get an interface index the hard way, i.e. using fcntl()"
1138
1047
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1139
1048
with closing(socket.socket()) as s:
1140
1049
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1141
struct.pack(str(u"16s16x"),
1143
interface_index = struct.unpack(str(u"I"),
1050
struct.pack("16s16x", interface))
1051
interface_index = struct.unpack("I", ifreq[16:20])[0]
1145
1052
return interface_index
1146
1053
return if_nametoindex(interface)
1149
1056
def daemon(nochdir = False, noclose = False):
1150
1057
"""See daemon(3). Standard BSD Unix function.
1152
1058
This should really exist as os.daemon, but it doesn't (yet)."""
1156
1062
if not nochdir:
1160
1066
if not noclose:
1176
1082
# Parsing of options, both command line and config file
1178
1084
parser = optparse.OptionParser(version = "%%prog %s" % version)
1179
parser.add_option("-i", u"--interface", type=u"string",
1180
metavar="IF", help=u"Bind to interface IF")
1181
parser.add_option("-a", u"--address", type=u"string",
1182
help=u"Address to listen for requests on")
1183
parser.add_option("-p", u"--port", type=u"int",
1184
help=u"Port number to receive requests on")
1185
parser.add_option("--check", action=u"store_true",
1186
help=u"Run self-test")
1187
parser.add_option("--debug", action=u"store_true",
1188
help=u"Debug mode; run in foreground and log to"
1190
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1191
u" priority string (see GnuTLS documentation)")
1192
parser.add_option("--servicename", type=u"string",
1193
metavar=u"NAME", help=u"Zeroconf service name")
1194
parser.add_option("--configdir", type=u"string",
1195
default=u"/etc/mandos", metavar=u"DIR",
1196
help=u"Directory to search for configuration"
1198
parser.add_option("--no-dbus", action=u"store_false",
1199
dest=u"use_dbus", help=u"Do not provide D-Bus"
1200
u" system bus interface")
1201
parser.add_option("--no-ipv6", action=u"store_false",
1202
dest=u"use_ipv6", help=u"Do not use IPv6")
1085
parser.add_option("-i", "--interface", type="string",
1086
metavar="IF", help="Bind to interface IF")
1087
parser.add_option("-a", "--address", type="string",
1088
help="Address to listen for requests on")
1089
parser.add_option("-p", "--port", type="int",
1090
help="Port number to receive requests on")
1091
parser.add_option("--check", action="store_true",
1092
help="Run self-test")
1093
parser.add_option("--debug", action="store_true",
1094
help="Debug mode; run in foreground and log to"
1096
parser.add_option("--priority", type="string", help="GnuTLS"
1097
" priority string (see GnuTLS documentation)")
1098
parser.add_option("--servicename", type="string", metavar="NAME",
1099
help="Zeroconf service name")
1100
parser.add_option("--configdir", type="string",
1101
default="/etc/mandos", metavar="DIR",
1102
help="Directory to search for configuration"
1104
parser.add_option("--no-dbus", action="store_false",
1106
help="Do not provide D-Bus system bus"
1108
parser.add_option("--no-ipv6", action="store_false",
1109
dest="use_ipv6", help="Do not use IPv6")
1203
1110
options = parser.parse_args()[0]
1205
1112
if options.check:
1210
1117
# Default values for config file for server-global settings
1211
server_defaults = { u"interface": u"",
1216
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1217
u"servicename": u"Mandos",
1218
u"use_dbus": u"True",
1219
u"use_ipv6": u"True",
1118
server_defaults = { "interface": "",
1123
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1124
"servicename": "Mandos",
1222
1129
# Parse config file for server-global settings
1223
server_config = configparser.SafeConfigParser(server_defaults)
1130
server_config = ConfigParser.SafeConfigParser(server_defaults)
1224
1131
del server_defaults
1225
server_config.read(os.path.join(options.configdir,
1132
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1227
1133
# Convert the SafeConfigParser object to a dict
1228
1134
server_settings = server_config.defaults()
1229
1135
# Use the appropriate methods on the non-string config options
1230
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1231
server_settings[option] = server_config.getboolean(u"DEFAULT",
1136
server_settings["debug"] = server_config.getboolean("DEFAULT",
1138
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1140
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1233
1142
if server_settings["port"]:
1234
server_settings["port"] = server_config.getint(u"DEFAULT",
1143
server_settings["port"] = server_config.getint("DEFAULT",
1236
1145
del server_config
1238
1147
# Override the settings from the config file with command line
1239
1148
# options, if set.
1240
for option in (u"interface", u"address", u"port", u"debug",
1241
u"priority", u"servicename", u"configdir",
1242
u"use_dbus", u"use_ipv6"):
1149
for option in ("interface", "address", "port", "debug",
1150
"priority", "servicename", "configdir",
1151
"use_dbus", "use_ipv6"):
1243
1152
value = getattr(options, option)
1244
1153
if value is not None:
1245
1154
server_settings[option] = value
1247
# Force all strings to be unicode
1248
for option in server_settings.keys():
1249
if type(server_settings[option]) is str:
1250
server_settings[option] = unicode(server_settings[option])
1251
1156
# Now we have our good server settings in "server_settings"
1253
1158
##################################################################
1255
1160
# For convenience
1256
debug = server_settings[u"debug"]
1257
use_dbus = server_settings[u"use_dbus"]
1258
use_ipv6 = server_settings[u"use_ipv6"]
1161
debug = server_settings["debug"]
1162
use_dbus = server_settings["use_dbus"]
1163
use_ipv6 = server_settings["use_ipv6"]
1261
1166
syslogger.setLevel(logging.WARNING)
1262
1167
console.setLevel(logging.WARNING)
1264
if server_settings[u"servicename"] != u"Mandos":
1169
if server_settings["servicename"] != "Mandos":
1265
1170
syslogger.setFormatter(logging.Formatter
1266
(u'Mandos (%s) [%%(process)d]:'
1267
u' %%(levelname)s: %%(message)s'
1268
% server_settings[u"servicename"]))
1171
('Mandos (%s) [%%(process)d]:'
1172
' %%(levelname)s: %%(message)s'
1173
% server_settings["servicename"]))
1270
1175
# Parse config file with clients
1271
client_defaults = { u"timeout": u"1h",
1273
u"checker": u"fping -q -- %%(host)s",
1176
client_defaults = { "timeout": "1h",
1178
"checker": "fping -q -- %%(host)s",
1276
client_config = configparser.SafeConfigParser(client_defaults)
1277
client_config.read(os.path.join(server_settings[u"configdir"],
1181
client_config = ConfigParser.SafeConfigParser(client_defaults)
1182
client_config.read(os.path.join(server_settings["configdir"],
1280
1185
global mandos_dbus_service
1281
1186
mandos_dbus_service = None
1283
tcp_server = MandosServer((server_settings[u"address"],
1284
server_settings[u"port"]),
1286
interface=server_settings[u"interface"],
1289
server_settings[u"priority"],
1291
pidfilename = u"/var/run/mandos.pid"
1189
tcp_server = IPv6_TCPServer((server_settings["address"],
1190
server_settings["port"]),
1192
settings=server_settings,
1193
clients=clients, use_ipv6=use_ipv6)
1194
pidfilename = "/var/run/mandos.pid"
1293
pidfile = open(pidfilename, u"w")
1196
pidfile = open(pidfilename, "w")
1294
1197
except IOError:
1295
logger.error(u"Could not open file %r", pidfilename)
1198
logger.error("Could not open file %r", pidfilename)
1298
uid = pwd.getpwnam(u"_mandos").pw_uid
1299
gid = pwd.getpwnam(u"_mandos").pw_gid
1201
uid = pwd.getpwnam("_mandos").pw_uid
1202
gid = pwd.getpwnam("_mandos").pw_gid
1300
1203
except KeyError:
1302
uid = pwd.getpwnam(u"mandos").pw_uid
1303
gid = pwd.getpwnam(u"mandos").pw_gid
1205
uid = pwd.getpwnam("mandos").pw_uid
1206
gid = pwd.getpwnam("mandos").pw_gid
1304
1207
except KeyError:
1306
uid = pwd.getpwnam(u"nobody").pw_uid
1307
gid = pwd.getpwnam(u"nobody").pw_gid
1209
uid = pwd.getpwnam("nobody").pw_uid
1210
gid = pwd.getpwnam("nogroup").pw_gid
1308
1211
except KeyError:
1324
1227
@gnutls.library.types.gnutls_log_func
1325
1228
def debug_gnutls(level, string):
1326
logger.debug(u"GnuTLS: %s", string[:-1])
1229
logger.debug("GnuTLS: %s", string[:-1])
1328
1231
(gnutls.library.functions
1329
1232
.gnutls_global_set_log_function(debug_gnutls))
1235
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1236
service = AvahiService(name = server_settings["servicename"],
1237
servicetype = "_mandos._tcp",
1238
protocol = protocol)
1239
if server_settings["interface"]:
1240
service.interface = (if_nametoindex
1241
(server_settings["interface"]))
1331
1243
global main_loop
1332
1246
# From the Avahi example code
1333
1247
DBusGMainLoop(set_as_default=True )
1334
1248
main_loop = gobject.MainLoop()
1335
1249
bus = dbus.SystemBus()
1250
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1251
avahi.DBUS_PATH_SERVER),
1252
avahi.DBUS_INTERFACE_SERVER)
1336
1253
# End of Avahi example code
1338
1255
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1339
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1340
service = AvahiService(name = server_settings[u"servicename"],
1341
servicetype = u"_mandos._tcp",
1342
protocol = protocol, bus = bus)
1343
if server_settings["interface"]:
1344
service.interface = (if_nametoindex
1345
(str(server_settings[u"interface"])))
1347
1257
client_class = Client
1349
client_class = functools.partial(ClientDBus, bus = bus)
1350
tcp_server.clients.update(set(
1259
client_class = ClientDBus
1351
1261
client_class(name = section,
1352
1262
config= dict(client_config.items(section)))
1353
1263
for section in client_config.sections()))
1354
if not tcp_server.clients:
1355
1265
logger.warning(u"No clients defined")
1399
1314
class MandosDBusService(dbus.service.Object):
1400
1315
"""A D-Bus proxy object"""
1401
1316
def __init__(self):
1402
dbus.service.Object.__init__(self, bus, u"/")
1317
dbus.service.Object.__init__(self, bus, "/")
1403
1318
_interface = u"se.bsnet.fukt.Mandos"
1405
@dbus.service.signal(_interface, signature=u"oa{sv}")
1320
@dbus.service.signal(_interface, signature="oa{sv}")
1406
1321
def ClientAdded(self, objpath, properties):
1410
@dbus.service.signal(_interface, signature=u"s")
1325
@dbus.service.signal(_interface, signature="s")
1411
1326
def ClientNotFound(self, fingerprint):
1415
@dbus.service.signal(_interface, signature=u"os")
1330
@dbus.service.signal(_interface, signature="os")
1416
1331
def ClientRemoved(self, objpath, name):
1420
@dbus.service.method(_interface, out_signature=u"ao")
1335
@dbus.service.method(_interface, out_signature="ao")
1421
1336
def GetAllClients(self):
1423
return dbus.Array(c.dbus_object_path
1424
for c in tcp_server.clients)
1338
return dbus.Array(c.dbus_object_path for c in clients)
1426
@dbus.service.method(_interface,
1427
out_signature=u"a{oa{sv}}")
1340
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1428
1341
def GetAllClientsWithProperties(self):
1430
1343
return dbus.Dictionary(
1431
1344
((c.dbus_object_path, c.GetAllProperties())
1432
for c in tcp_server.clients),
1433
signature=u"oa{sv}")
1435
@dbus.service.method(_interface, in_signature=u"o")
1348
@dbus.service.method(_interface, in_signature="o")
1436
1349
def RemoveClient(self, object_path):
1438
for c in tcp_server.clients:
1439
1352
if c.dbus_object_path == object_path:
1440
tcp_server.clients.remove(c)
1441
1354
c.remove_from_connection()
1442
1355
# Don't signal anything except ClientRemoved
1443
1356
c.disable(signal=False)