72
27
self.password = open(passfile).readall()
74
raise RuntimeError(u"No Password or Passfile for client %s"
29
print "No Password or Passfile in client config file"
30
# raise RuntimeError XXX
31
self.password = "gazonk"
76
32
self.fqdn = fqdn # string
77
33
self.created = datetime.datetime.now()
34
self.last_seen = None # datetime.datetime()
79
35
if timeout is None:
80
36
timeout = options.timeout
81
self.timeout = timeout
82
self.timeout_milliseconds = ((self.timeout.days
83
* 24 * 60 * 60 * 1000)
84
+ (self.timeout.seconds * 1000)
85
+ (self.timeout.microseconds
37
self.timeout = timeout # datetime.timedelta()
88
39
interval = options.interval
90
interval = string_to_delta(interval)
91
self.interval = interval
92
self.interval_milliseconds = ((self.interval.days
93
* 24 * 60 * 60 * 1000)
94
+ (self.interval.seconds * 1000)
95
+ (self.interval.microseconds
97
self.stop_hook = stop_hook
99
self.checker_initiator_tag = None
100
self.stop_initiator_tag = None
101
self.checker_callback_tag = None
103
"""Start this clients checker and timeout hooks"""
104
# Schedule a new checker to be started an 'interval' from now,
105
# and every interval from then on.
106
self.checker_initiator_tag = gobject.\
107
timeout_add(self.interval_milliseconds,
109
# Also start a new checker *right now*.
111
# Schedule a stop() when 'timeout' has passed
112
self.stop_initiator_tag = gobject.\
113
timeout_add(self.timeout_milliseconds,
117
The possibility that this client might be restarted is left
118
open, but not currently used."""
119
# print "Stopping client", self.name
121
if self.stop_initiator_tag:
122
gobject.source_remove(self.stop_initiator_tag)
123
self.stop_initiator_tag = None
124
if self.checker_initiator_tag:
125
gobject.source_remove(self.checker_initiator_tag)
126
self.checker_initiator_tag = None
130
# Do not run this again if called by a gobject.timeout_add
133
# Some code duplication here and in stop()
134
if hasattr(self, "stop_initiator_tag") \
135
and self.stop_initiator_tag:
136
gobject.source_remove(self.stop_initiator_tag)
137
self.stop_initiator_tag = None
138
if hasattr(self, "checker_initiator_tag") \
139
and self.checker_initiator_tag:
140
gobject.source_remove(self.checker_initiator_tag)
141
self.checker_initiator_tag = None
143
def checker_callback(self, pid, condition):
144
"""The checker has completed, so take appropriate actions."""
145
now = datetime.datetime.now()
146
if os.WIFEXITED(condition) \
147
and (os.WEXITSTATUS(condition) == 0):
148
#print "Checker for %(name)s succeeded" % vars(self)
40
self.interval = interval # datetime.timedelta()
41
self.next_check = datetime.datetime.now() # datetime.datetime()
42
self.checker = None # or a subprocess.Popen()
43
def check_action(self, now=None):
44
"""The checker said something and might have completed.
45
Check if is has, and take appropriate actions."""
46
if self.checker.poll() is None:
47
# False alarm, no result yet
51
now = datetime.datetime.now()
52
if self.checker.returncode == 0:
149
53
self.last_seen = now
150
gobject.source_remove(self.stop_initiator_tag)
151
self.stop_initiator_tag = gobject.\
152
timeout_add(self.timeout_milliseconds,
155
# if not os.WIFEXITED(condition):
156
# print "Checker for %(name)s crashed?" % vars(self)
158
# print "Checker for %(name)s failed" % vars(self)
160
self.checker_callback_tag = None
54
while self.next_check <= now:
55
self.next_check += self.interval
56
handle_request = check_action
161
57
def start_checker(self):
162
"""Start a new checker subprocess if one is not running.
163
If a checker already exists, leave it running and do
165
if self.checker is None:
166
#print "Starting checker for", self.name
168
self.checker = subprocess.\
169
Popen("sleep 1; fping -q -- %s"
170
% re.escape(self.fqdn),
171
stdout=subprocess.PIPE,
172
close_fds=True, shell=True,
174
self.checker_callback_tag = gobject.\
175
child_watch_add(self.checker.pid,
178
except subprocess.OSError, error:
179
sys.stderr.write(u"Failed to start subprocess: %s\n"
181
# Re-run this periodically if run by gobject.timeout_add
60
self.checker = subprocess.Popen("sleep 1; fping -q -- %s"
61
% re.escape(self.fqdn),
62
stdout=subprocess.PIPE,
65
except subprocess.OSError, e:
66
print "Failed to start subprocess:", e
183
67
def stop_checker(self):
184
"""Force the checker process, if any, to stop."""
185
if not hasattr(self, "checker") or self.checker is None:
68
if self.checker is None:
187
gobject.source_remove(self.checker_callback_tag)
188
self.checker_callback_tag = None
189
70
os.kill(self.checker.pid, signal.SIGTERM)
190
71
if self.checker.poll() is None:
191
72
os.kill(self.checker.pid, signal.SIGKILL)
192
73
self.checker = None
74
__del__ = stop_checker
76
if self.checker is None:
78
return self.checker.stdout.fileno()
80
"""The time when something must be done about this client"""
81
return min(self.last_seen + self.timeout, self.next_check)
193
82
def still_valid(self, now=None):
194
"""Has the timeout not yet passed for this client?"""
196
now = datetime.datetime.now()
197
if self.last_seen is None:
198
return now < (self.created + self.timeout)
200
return now < (self.last_seen + self.timeout)
83
"""Has this client's timeout not passed?"""
85
now = datetime.datetime.now()
86
return now < (self.last_seen + timeout)
87
def it_is_time_to_check(self, now=None):
89
now = datetime.datetime.now()
90
return self.next_check <= now
93
class server_metaclass(type):
94
"Common behavior for the UDP and TCP server classes"
95
def __new__(cls, name, bases, attrs):
96
attrs["address_family"] = socket.AF_INET6
97
attrs["allow_reuse_address"] = True
98
def server_bind(self):
99
if self.options.interface:
100
if not hasattr(socket, "SO_BINDTODEVICE"):
101
# From /usr/include/asm-i486/socket.h
102
socket.SO_BINDTODEVICE = 25
104
self.socket.setsockopt(socket.SOL_SOCKET,
105
socket.SO_BINDTODEVICE,
106
self.options.interface)
107
except socket.error, error:
108
if error[0] == errno.EPERM:
109
print "Warning: No permission to bind to interface", \
110
self.options.interface
113
return super(type(self), self).server_bind()
114
attrs["server_bind"] = server_bind
115
def init(self, *args, **kwargs):
116
if "options" in kwargs:
117
self.options = kwargs["options"]
118
del kwargs["options"]
119
if "clients" in kwargs:
120
self.clients = kwargs["clients"]
121
del kwargs["clients"]
122
if "credentials" in kwargs:
123
self.credentials = kwargs["credentials"]
124
del kwargs["credentials"]
125
return super(type(self), self).__init__(*args, **kwargs)
126
attrs["__init__"] = init
127
return type.__new__(cls, name, bases, attrs)
130
class udp_handler(SocketServer.DatagramRequestHandler, object):
132
self.wfile.write("Polo")
133
print "UDP request answered"
136
class IPv6_UDPServer(SocketServer.UDPServer, object):
137
__metaclass__ = server_metaclass
138
def verify_request(self, request, client_address):
139
print "UDP request came"
140
return request[0] == "Marco"
203
143
class tcp_handler(SocketServer.BaseRequestHandler, object):
204
"""A TCP request handler class.
205
Instantiated by IPv6_TCPServer for each request to handle it.
206
Note: This will run in its own forked process."""
207
144
def handle(self):
208
#print u"TCP request came"
209
#print u"Request:", self.request
210
#print u"Client Address:", self.client_address
211
#print u"Server:", self.server
145
print "TCP request came"
146
print "Request:", self.request
147
print "Client Address:", self.client_address
148
print "Server:", self.server
212
149
session = gnutls.connection.ServerSession(self.request,
217
except gnutls.errors.GNUTLSError, error:
218
#sys.stderr.write(u"Handshake failed: %s\n" % error)
219
# Do not run session.bye() here: the session is not
220
# established. Just abandon the request.
222
#if session.peer_certificate:
223
# print "DN:", session.peer_certificate.subject
150
self.server.credentials)
152
if session.peer_certificate:
153
print "DN:", session.peer_certificate.subject
225
155
session.verify_peer()
226
156
except gnutls.errors.CertificateError, error:
227
#sys.stderr.write(u"Verify failed: %s\n" % error)
157
print "Verify failed", error
232
if c.dn == session.peer_certificate.subject:
235
# Have to check if client.still_valid(), since it is possible
236
# that the client timed out while establishing the GnuTLS
238
if client and client.still_valid():
239
session.send(client.password)
242
# sys.stderr.write(u"Client %(name)s is invalid\n"
245
# sys.stderr.write(u"Client not found for DN: %s\n"
246
# % session.peer_certificate.subject)
247
#session.send("gazonk")
161
session.send(dict((client.dn, client.password)
162
for client in self.server.clients)
163
[session.peer_certificate.subject])
165
session.send("gazonk")
252
170
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
253
"""IPv6 TCP server. Accepts 'None' as address and/or port.
255
options: Command line options
256
clients: Set() of Client objects
257
credentials: GnuTLS X.509 credentials
259
address_family = socket.AF_INET6
260
def __init__(self, *args, **kwargs):
261
if "options" in kwargs:
262
self.options = kwargs["options"]
263
del kwargs["options"]
264
if "clients" in kwargs:
265
self.clients = kwargs["clients"]
266
del kwargs["clients"]
267
if "credentials" in kwargs:
268
self.credentials = kwargs["credentials"]
269
del kwargs["credentials"]
270
return super(type(self), self).__init__(*args, **kwargs)
271
def server_bind(self):
272
"""This overrides the normal server_bind() function
273
to bind to an interface if one was specified, and also NOT to
274
bind to an address or port if they were not specified."""
275
if self.options.interface:
276
if not hasattr(socket, "SO_BINDTODEVICE"):
277
# From /usr/include/asm-i486/socket.h
278
socket.SO_BINDTODEVICE = 25
280
self.socket.setsockopt(socket.SOL_SOCKET,
281
socket.SO_BINDTODEVICE,
282
self.options.interface)
283
except socket.error, error:
284
if error[0] == errno.EPERM:
285
sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
286
% self.options.interface)
289
# Only bind(2) the socket if we really need to.
290
if self.server_address[0] or self.server_address[1]:
291
if not self.server_address[0]:
293
self.server_address = (in6addr_any,
294
self.server_address[1])
295
elif self.server_address[1] is None:
296
self.server_address = (self.server_address[0],
298
return super(type(self), self).server_bind()
171
__metaclass__ = server_metaclass
172
request_queue_size = 1024
301
177
def string_to_delta(interval):
302
178
"""Parse a string and return a datetime.timedelta
336
"""From the Avahi server example code"""
337
global group, serviceName, serviceType, servicePort, serviceTXT, \
340
group = dbus.Interface(
341
bus.get_object( avahi.DBUS_NAME,
342
server.EntryGroupNew()),
343
avahi.DBUS_INTERFACE_ENTRY_GROUP)
344
group.connect_to_signal('StateChanged',
345
entry_group_state_changed)
347
# print "Adding service '%s' of type '%s' ..." % (serviceName,
351
serviceInterface, # interface
352
avahi.PROTO_INET6, # protocol
353
dbus.UInt32(0), # flags
354
serviceName, serviceType,
356
dbus.UInt16(servicePort),
357
avahi.string_array_to_txt_array(serviceTXT))
361
def remove_service():
362
"""From the Avahi server example code"""
365
if not group is None:
369
def server_state_changed(state):
370
"""From the Avahi server example code"""
371
if state == avahi.SERVER_COLLISION:
372
print "WARNING: Server name collision"
374
elif state == avahi.SERVER_RUNNING:
378
def entry_group_state_changed(state, error):
379
"""From the Avahi server example code"""
380
global serviceName, server, rename_count
382
# print "state change: %i" % state
384
if state == avahi.ENTRY_GROUP_ESTABLISHED:
386
# print "Service established."
387
elif state == avahi.ENTRY_GROUP_COLLISION:
389
rename_count = rename_count - 1
391
name = server.GetAlternativeServiceName(name)
392
print "WARNING: Service name collision, changing name to '%s' ..." % name
397
print "ERROR: No suitable service name found after %i retries, exiting." % n_rename
399
elif state == avahi.ENTRY_GROUP_FAILURE:
400
print "Error in group state changed", error
405
def if_nametoindex(interface):
406
"""Call the C function if_nametoindex()"""
408
if "ctypes" not in sys.modules:
410
libc = ctypes.cdll.LoadLibrary("libc.so.6")
411
return libc.if_nametoindex(interface)
412
except (ImportError, OSError, AttributeError):
413
if "struct" not in sys.modules:
415
if "fcntl" not in sys.modules:
417
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
419
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
420
struct.pack("16s16x", interface))
422
interface_index = struct.unpack("I", ifreq[16:20])[0]
423
return interface_index
426
if __name__ == '__main__':
427
212
parser = OptionParser()
428
213
parser.add_option("-i", "--interface", type="string",
429
default=None, metavar="IF",
430
help="Bind to interface IF")
214
default="eth0", metavar="IF",
215
help="Interface to bind to")
431
216
parser.add_option("--cert", type="string", default="cert.pem",
433
help="Public key certificate PEM file to use")
218
help="Public key certificate to use")
434
219
parser.add_option("--key", type="string", default="key.pem",
436
help="Private key PEM file to use")
221
help="Private key to use")
437
222
parser.add_option("--ca", type="string", default="ca.pem",
439
help="Certificate Authority certificate PEM file to use")
224
help="Certificate Authority certificate to use")
440
225
parser.add_option("--crl", type="string", default="crl.pem",
442
help="Certificate Revokation List PEM file to use")
443
parser.add_option("-p", "--port", type="int", default=None,
227
help="Certificate Revokation List to use")
228
parser.add_option("-p", "--port", type="int", default=49001,
444
229
help="Port number to receive requests on")
445
parser.add_option("--timeout", type="string", # Parsed later
230
parser.add_option("--dh", type="int", metavar="BITS",
231
help="DH group to use")
232
parser.add_option("-t", "--timeout", type="string", # Parsed later
447
234
help="Amount of downtime allowed for clients")
448
235
parser.add_option("--interval", type="string", # Parsed later
476
264
# Parse config file
478
client_config = ConfigParser.SafeConfigParser(defaults)
479
#client_config.readfp(open("secrets.conf"), "secrets.conf")
480
client_config.read("mandos-clients.conf")
482
# From the Avahi server example code
483
DBusGMainLoop(set_as_default=True )
484
main_loop = gobject.MainLoop()
485
bus = dbus.SystemBus()
486
server = dbus.Interface(
487
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
488
avahi.DBUS_INTERFACE_SERVER )
489
# End of Avahi example code
492
def remove_from_clients(client):
493
clients.remove(client)
495
print "No clients left, exiting"
498
clients.update(Set(Client(name=section, options=options,
499
stop_hook = remove_from_clients,
500
**(dict(client_config\
502
for section in client_config.sections()))
503
for client in clients:
506
tcp_server = IPv6_TCPServer((None, options.port),
266
client_config_object = ConfigParser.SafeConfigParser(defaults)
267
client_config_object.read("mandos-clients.conf")
268
clients = [Client(name=section, options=options,
269
**(dict(client_config_object.items(section))))
270
for section in client_config_object.sections()]
272
udp_server = IPv6_UDPServer((in6addr_any, options.port),
276
tcp_server = IPv6_TCPServer((in6addr_any, options.port),
510
280
credentials=cred)
511
# Find out what random port we got
512
servicePort = tcp_server.socket.getsockname()[1]
513
#sys.stderr.write("Now listening on port %d\n" % servicePort)
515
if options.interface is not None:
516
serviceInterface = if_nametoindex(options.interface)
518
# From the Avahi server example code
519
server.connect_to_signal("StateChanged", server_state_changed)
520
server_state_changed(server.GetState())
521
# End of Avahi example code
523
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
524
lambda *args, **kwargs:
525
tcp_server.handle_request(*args[2:],
529
except KeyboardInterrupt:
284
input, out, err = select.select((udp_server,
291
except KeyboardInterrupt:
534
# From the Avahi server example code
535
if not group is None:
537
# End of Avahi example code
539
295
for client in clients:
540
client.stop_hook = None
296
client.stop_checker()
299
if __name__ == "__main__":