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