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())
227
class Client(object):
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)
182
class Client(dbus.service.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
278
231
if config is None:
280
233
logger.debug(u"Creating client %r", self.name)
234
self.use_dbus = False # During __init__
281
235
# Uppercase and remove spaces from fingerprint for later
282
236
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
238
self.fingerprint = (config["fingerprint"].upper()
285
239
.replace(u" ", u""))
286
240
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:
241
if "secret" in config:
242
self.secret = config["secret"].decode(u"base64")
243
elif "secfile" in config:
290
244
with closing(open(os.path.expanduser
291
245
(os.path.expandvars
292
(config[u"secfile"])))) as secfile:
246
(config["secfile"])))) as secfile:
293
247
self.secret = secfile.read()
295
249
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
251
self.host = config.get("host", "")
298
252
self.created = datetime.datetime.utcnow()
299
253
self.enabled = False
300
254
self.last_enabled = None
301
255
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
256
self.timeout = string_to_delta(config["timeout"])
257
self.interval = string_to_delta(config["interval"])
304
258
self.disable_hook = disable_hook
305
259
self.checker = None
306
260
self.checker_initiator_tag = None
307
261
self.disable_initiator_tag = None
308
262
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
263
self.checker_command = config["checker"]
310
264
self.current_checker_command = None
311
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)
313
276
def enable(self):
314
277
"""Start this client's checker and timeout hooks"""
315
if getattr(self, u"enabled", False):
318
278
self.last_enabled = datetime.datetime.utcnow()
319
279
# Schedule a new checker to be started an 'interval' from now,
320
280
# and every interval from then on.
465
457
if error.errno != errno.ESRCH: # No such process
467
459
self.checker = None
461
self.PropertyChanged(dbus.String(u"checker_running"),
462
dbus.Boolean(False, variant_level=1))
469
464
def still_valid(self):
470
465
"""Has the timeout not yet passed for this client?"""
471
if not getattr(self, u"enabled", False):
466
if not getattr(self, "enabled", False):
473
468
now = datetime.datetime.utcnow()
474
469
if self.last_checked_ok is None:
475
470
return now < (self.created + self.timeout)
477
472
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))
594
474
## D-Bus methods & signals
595
475
_interface = u"se.bsnet.fukt.Mandos.Client"
597
477
# CheckedOK - method
598
@dbus.service.method(_interface)
600
return self.checked_ok()
478
CheckedOK = dbus.service.method(_interface)(checked_ok)
479
CheckedOK.__name__ = "CheckedOK"
602
481
# CheckerCompleted - signal
603
@dbus.service.signal(_interface, signature=u"nxs")
482
@dbus.service.signal(_interface, signature="nxs")
604
483
def CheckerCompleted(self, exitcode, waitstatus, command):
608
487
# CheckerStarted - signal
609
@dbus.service.signal(_interface, signature=u"s")
488
@dbus.service.signal(_interface, signature="s")
610
489
def CheckerStarted(self, command):
614
493
# GetAllProperties - method
615
@dbus.service.method(_interface, out_signature=u"a{sv}")
494
@dbus.service.method(_interface, out_signature="a{sv}")
616
495
def GetAllProperties(self):
618
497
return dbus.Dictionary({
619
dbus.String(u"name"):
620
499
dbus.String(self.name, variant_level=1),
621
dbus.String(u"fingerprint"):
500
dbus.String("fingerprint"):
622
501
dbus.String(self.fingerprint, variant_level=1),
623
dbus.String(u"host"):
624
503
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,
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,
631
509
if self.last_enabled is not None
632
510
else dbus.Boolean(False, variant_level=1)),
633
dbus.String(u"enabled"):
511
dbus.String("enabled"):
634
512
dbus.Boolean(self.enabled, variant_level=1),
635
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
513
dbus.String("last_checked_ok"):
514
(_datetime_to_dbus(self.last_checked_ok,
638
516
if self.last_checked_ok is not None
639
517
else dbus.Boolean (False, variant_level=1)),
640
dbus.String(u"timeout"):
518
dbus.String("timeout"):
641
519
dbus.UInt64(self.timeout_milliseconds(),
642
520
variant_level=1),
643
dbus.String(u"interval"):
521
dbus.String("interval"):
644
522
dbus.UInt64(self.interval_milliseconds(),
645
523
variant_level=1),
646
dbus.String(u"checker"):
524
dbus.String("checker"):
647
525
dbus.String(self.checker_command,
648
526
variant_level=1),
649
dbus.String(u"checker_running"):
527
dbus.String("checker_running"):
650
528
dbus.Boolean(self.checker is not None,
651
529
variant_level=1),
652
dbus.String(u"object_path"):
530
dbus.String("object_path"):
653
531
dbus.ObjectPath(self.dbus_object_path,
657
535
# IsStillValid - method
658
@dbus.service.method(_interface, out_signature=u"b")
659
def IsStillValid(self):
660
return self.still_valid()
536
IsStillValid = (dbus.service.method(_interface, out_signature="b")
538
IsStillValid.__name__ = "IsStillValid"
662
540
# PropertyChanged - signal
663
@dbus.service.signal(_interface, signature=u"sv")
541
@dbus.service.signal(_interface, signature="sv")
664
542
def PropertyChanged(self, property, value):
742
618
# StopChecker - method
743
@dbus.service.method(_interface)
744
def StopChecker(self):
619
StopChecker = dbus.service.method(_interface)(stop_checker)
620
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.
625
def peer_certificate(session):
626
"Return the peer's OpenPGP certificate as a bytestring"
627
# 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):
631
# ...do the normal thing
632
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"
640
if list_size.value == 0:
643
return ctypes.string_at(cert.data, cert.size)
646
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))))
654
# New empty GnuTLS certificate
655
crt = gnutls.library.types.gnutls_openpgp_crt_t()
656
(gnutls.library.functions
657
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
658
# 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")
670
# New buffer for the fingerprint
671
buf = ctypes.create_string_buffer(20)
672
buf_len = ctypes.c_size_t()
673
# 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)))
677
# Deinit the certificate
678
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
679
# Convert the buffer to a Python bytestring
680
fpr = ctypes.string_at(buf, buf_len.value)
681
# Convert the bytestring to hexadecimal notation
682
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
686
class TCP_handler(SocketServer.BaseRequestHandler, object):
687
"""A TCP request handler class.
688
Instantiated by IPv6_TCPServer for each request to handle it.
754
689
Note: This will run in its own forked process."""
756
691
def handle(self):
830
767
- (sent_size + sent))
831
768
sent_size += sent
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."""
772
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
773
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
774
Assumes a gobject.MainLoop event loop.
900
776
def process_request(self, request, client_address):
901
"""Overrides and wraps the original process_request().
777
"""This overrides and wraps the original process_request().
903
778
This function creates a new pipe in self.pipe
905
780
self.pipe = os.pipe()
906
781
super(ForkingMixInWithPipe,
907
782
self).process_request(request, client_address)
908
783
os.close(self.pipe[1]) # close write end
909
self.add_pipe(self.pipe[0])
910
def add_pipe(self, pipe):
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):
911
789
"""Dummy function; override as necessary"""
915
794
class IPv6_TCPServer(ForkingMixInWithPipe,
916
socketserver.TCPServer, object):
795
SocketServer.TCPServer, object):
917
796
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
798
settings: Server settings
799
clients: Set() of Client objects
920
800
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
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,
802
address_family = socket.AF_INET6
803
def __init__(self, *args, **kwargs):
804
if "settings" in kwargs:
805
self.settings = kwargs["settings"]
806
del kwargs["settings"]
807
if "clients" in kwargs:
808
self.clients = kwargs["clients"]
809
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)
931
816
def server_bind(self):
932
817
"""This overrides the normal server_bind() function
933
818
to bind to an interface if one was specified, and also NOT to
934
819
bind to an address or port if they were not specified."""
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",
820
if self.settings["interface"]:
821
# 25 is from /usr/include/asm-i486/socket.h
822
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
824
self.socket.setsockopt(socket.SOL_SOCKET,
826
self.settings["interface"])
827
except socket.error, error:
828
if error[0] == errno.EPERM:
829
logger.error(u"No permission to"
830
u" bind to interface %s",
831
self.settings["interface"])
957
834
# Only bind(2) the socket if we really need to.
958
835
if self.server_address[0] or self.server_address[1]:
959
836
if not self.server_address[0]:
960
837
if self.address_family == socket.AF_INET6:
961
any_address = u"::" # in6addr_any
838
any_address = "::" # in6addr_any
963
840
any_address = socket.INADDR_ANY
964
841
self.server_address = (any_address,
966
843
elif not self.server_address[1]:
967
844
self.server_address = (self.server_address[0],
846
# if self.settings["interface"]:
970
847
# self.server_address = (self.server_address[0],
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)
853
return super(IPv6_TCPServer, self).server_bind()
1004
854
def server_activate(self):
1005
855
if self.enabled:
1006
return socketserver.TCPServer.server_activate(self)
856
return super(IPv6_TCPServer, self).server_activate()
1007
857
def enable(self):
1008
858
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,
1013
859
def handle_ipc(self, source, condition, file_objects={}):
1014
860
condition_names = {
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
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
1024
870
conditions_string = ' | '.join(name
1025
871
for cond, name in
1026
872
condition_names.iteritems()
1027
873
if cond & condition)
1028
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
874
logger.debug("Handling IPC: FD = %d, condition = %s", source,
1029
875
conditions_string)
1031
877
# Turn the pipe file descriptor into a Python file object
1032
878
if source not in file_objects:
1033
file_objects[source] = os.fdopen(source, u"r", 1)
879
file_objects[source] = os.fdopen(source, "r", 1)
1035
881
# Read a line from the file object
1036
882
cmdline = file_objects[source].readline()
1121
957
return timevalue
960
def server_state_changed(state):
961
"""Derived from the Avahi example code"""
962
if state == avahi.SERVER_COLLISION:
963
logger.error(u"Zeroconf server name collision")
965
elif state == avahi.SERVER_RUNNING:
969
def entry_group_state_changed(state, error):
970
"""Derived from the Avahi example code"""
971
logger.debug(u"Avahi state change: %i", state)
973
if state == avahi.ENTRY_GROUP_ESTABLISHED:
974
logger.debug(u"Zeroconf service established.")
975
elif state == avahi.ENTRY_GROUP_COLLISION:
976
logger.warning(u"Zeroconf service name collision.")
978
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))
1124
983
def if_nametoindex(interface):
1125
"""Call the C function if_nametoindex(), or equivalent
1127
Note: This function cannot accept a unicode string."""
984
"""Call the C function if_nametoindex(), or equivalent"""
1128
985
global if_nametoindex
1130
987
if_nametoindex = (ctypes.cdll.LoadLibrary
1131
(ctypes.util.find_library(u"c"))
988
(ctypes.util.find_library("c"))
1132
989
.if_nametoindex)
1133
990
except (OSError, AttributeError):
1134
logger.warning(u"Doing if_nametoindex the hard way")
991
if "struct" not in sys.modules:
993
if "fcntl" not in sys.modules:
1135
995
def if_nametoindex(interface):
1136
996
"Get an interface index the hard way, i.e. using fcntl()"
1137
997
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1138
998
with closing(socket.socket()) as s:
1139
999
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1140
struct.pack(str(u"16s16x"),
1142
interface_index = struct.unpack(str(u"I"),
1000
struct.pack("16s16x", interface))
1001
interface_index = struct.unpack("I", ifreq[16:20])[0]
1144
1002
return interface_index
1145
1003
return if_nametoindex(interface)
1148
1006
def daemon(nochdir = False, noclose = False):
1149
1007
"""See daemon(3). Standard BSD Unix function.
1151
1008
This should really exist as os.daemon, but it doesn't (yet)."""
1155
1012
if not nochdir:
1159
1016
if not noclose:
1175
1032
# Parsing of options, both command line and config file
1177
1034
parser = optparse.OptionParser(version = "%%prog %s" % version)
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")
1035
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",
1040
help="Port number to receive requests on")
1041
parser.add_option("--check", action="store_true",
1042
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")
1202
1060
options = parser.parse_args()[0]
1204
1062
if options.check:
1209
1067
# Default values for config file for server-global settings
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",
1068
server_defaults = { "interface": "",
1073
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1074
"servicename": "Mandos",
1221
1079
# Parse config file for server-global settings
1222
server_config = configparser.SafeConfigParser(server_defaults)
1080
server_config = ConfigParser.SafeConfigParser(server_defaults)
1223
1081
del server_defaults
1224
server_config.read(os.path.join(options.configdir,
1082
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1226
1083
# Convert the SafeConfigParser object to a dict
1227
1084
server_settings = server_config.defaults()
1228
1085
# Use the appropriate methods on the non-string config options
1229
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1230
server_settings[option] = server_config.getboolean(u"DEFAULT",
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",
1232
1092
if server_settings["port"]:
1233
server_settings["port"] = server_config.getint(u"DEFAULT",
1093
server_settings["port"] = server_config.getint("DEFAULT",
1235
1095
del server_config
1237
1097
# Override the settings from the config file with command line
1238
1098
# options, if set.
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"):
1099
for option in ("interface", "address", "port", "debug",
1100
"priority", "servicename", "configdir",
1101
"use_dbus", "use_ipv6"):
1242
1102
value = getattr(options, option)
1243
1103
if value is not None:
1244
1104
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])
1250
1106
# Now we have our good server settings in "server_settings"
1252
1108
##################################################################
1254
1110
# For convenience
1255
debug = server_settings[u"debug"]
1256
use_dbus = server_settings[u"use_dbus"]
1257
use_ipv6 = server_settings[u"use_ipv6"]
1111
debug = server_settings["debug"]
1112
use_dbus = server_settings["use_dbus"]
1113
use_ipv6 = server_settings["use_ipv6"]
1260
1116
syslogger.setLevel(logging.WARNING)
1261
1117
console.setLevel(logging.WARNING)
1263
if server_settings[u"servicename"] != u"Mandos":
1119
if server_settings["servicename"] != "Mandos":
1264
1120
syslogger.setFormatter(logging.Formatter
1265
(u'Mandos (%s) [%%(process)d]:'
1266
u' %%(levelname)s: %%(message)s'
1267
% server_settings[u"servicename"]))
1121
('Mandos (%s) [%%(process)d]:'
1122
' %%(levelname)s: %%(message)s'
1123
% server_settings["servicename"]))
1269
1125
# Parse config file with clients
1270
client_defaults = { u"timeout": u"1h",
1272
u"checker": u"fping -q -- %%(host)s",
1126
client_defaults = { "timeout": "1h",
1128
"checker": "fping -q -- %%(host)s",
1275
client_config = configparser.SafeConfigParser(client_defaults)
1276
client_config.read(os.path.join(server_settings[u"configdir"],
1131
client_config = ConfigParser.SafeConfigParser(client_defaults)
1132
client_config.read(os.path.join(server_settings["configdir"],
1279
1135
global mandos_dbus_service
1280
1136
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"
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"
1292
pidfile = open(pidfilename, u"w")
1146
pidfile = open(pidfilename, "w")
1293
1147
except IOError:
1294
logger.error(u"Could not open file %r", pidfilename)
1148
logger.error("Could not open file %r", pidfilename)
1297
uid = pwd.getpwnam(u"_mandos").pw_uid
1298
gid = pwd.getpwnam(u"_mandos").pw_gid
1151
uid = pwd.getpwnam("_mandos").pw_uid
1152
gid = pwd.getpwnam("_mandos").pw_gid
1299
1153
except KeyError:
1301
uid = pwd.getpwnam(u"mandos").pw_uid
1302
gid = pwd.getpwnam(u"mandos").pw_gid
1155
uid = pwd.getpwnam("mandos").pw_uid
1156
gid = pwd.getpwnam("mandos").pw_gid
1303
1157
except KeyError:
1305
uid = pwd.getpwnam(u"nobody").pw_uid
1306
gid = pwd.getpwnam(u"nobody").pw_gid
1159
uid = pwd.getpwnam("nobody").pw_uid
1160
gid = pwd.getpwnam("nogroup").pw_gid
1307
1161
except KeyError:
1323
1177
@gnutls.library.types.gnutls_log_func
1324
1178
def debug_gnutls(level, string):
1325
logger.debug(u"GnuTLS: %s", string[:-1])
1179
logger.debug("GnuTLS: %s", string[:-1])
1327
1181
(gnutls.library.functions
1328
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"]))
1330
1193
global main_loop
1331
1196
# From the Avahi example code
1332
1197
DBusGMainLoop(set_as_default=True )
1333
1198
main_loop = gobject.MainLoop()
1334
1199
bus = dbus.SystemBus()
1200
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1201
avahi.DBUS_PATH_SERVER),
1202
avahi.DBUS_INTERFACE_SERVER)
1335
1203
# End of Avahi example code
1337
1205
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"])))
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:
1207
clients.update(Set(Client(name = section,
1209
= dict(client_config.items(section)),
1210
use_dbus = use_dbus)
1211
for section in client_config.sections()))
1354
1213
logger.warning(u"No clients defined")
1398
1262
class MandosDBusService(dbus.service.Object):
1399
1263
"""A D-Bus proxy object"""
1400
1264
def __init__(self):
1401
dbus.service.Object.__init__(self, bus, u"/")
1265
dbus.service.Object.__init__(self, bus, "/")
1402
1266
_interface = u"se.bsnet.fukt.Mandos"
1404
@dbus.service.signal(_interface, signature=u"oa{sv}")
1268
@dbus.service.signal(_interface, signature="oa{sv}")
1405
1269
def ClientAdded(self, objpath, properties):
1409
@dbus.service.signal(_interface, signature=u"s")
1273
@dbus.service.signal(_interface, signature="s")
1410
1274
def ClientNotFound(self, fingerprint):
1414
@dbus.service.signal(_interface, signature=u"os")
1278
@dbus.service.signal(_interface, signature="os")
1415
1279
def ClientRemoved(self, objpath, name):
1419
@dbus.service.method(_interface, out_signature=u"ao")
1283
@dbus.service.method(_interface, out_signature="ao")
1420
1284
def GetAllClients(self):
1422
return dbus.Array(c.dbus_object_path
1423
for c in tcp_server.clients)
1286
return dbus.Array(c.dbus_object_path for c in clients)
1425
@dbus.service.method(_interface,
1426
out_signature=u"a{oa{sv}}")
1288
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1427
1289
def GetAllClientsWithProperties(self):
1429
1291
return dbus.Dictionary(
1430
1292
((c.dbus_object_path, c.GetAllProperties())
1431
for c in tcp_server.clients),
1432
signature=u"oa{sv}")
1434
@dbus.service.method(_interface, in_signature=u"o")
1296
@dbus.service.method(_interface, in_signature="o")
1435
1297
def RemoveClient(self, object_path):
1437
for c in tcp_server.clients:
1438
1300
if c.dbus_object_path == object_path:
1439
tcp_server.clients.remove(c)
1440
c.remove_from_connection()
1441
1302
# Don't signal anything except ClientRemoved
1442
c.disable(signal=False)
1443
1305
# Emit D-Bus signal
1444
1306
self.ClientRemoved(object_path, c.name)