1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import struct
23 import socket
24
25 from twisted.web import http, server
26 from twisted.web import resource as web_resource
27 from twisted.internet import reactor, defer
28 from twisted.python import reflect, failure
29
30 from flumotion.configure import configure
31 from flumotion.common import errors
32 from flumotion.twisted.credentials import cryptChallenge
33
34 from flumotion.common import common, log, keycards
35
36
37
38 HTTP_SERVER_NAME = 'FlumotionHTTPServer'
39 HTTP_SERVER_VERSION = configure.version
40
41 ERROR_TEMPLATE = """<!doctype html public "-//IETF//DTD HTML 2.0//EN">
42 <html>
43 <head>
44 <title>%(code)d %(error)s</title>
45 </head>
46 <body>
47 <h2>%(code)d %(error)s</h2>
48 </body>
49 </html>
50 """
51
52 HTTP_SERVER = '%s/%s' % (HTTP_SERVER_NAME, HTTP_SERVER_VERSION)
53
54
55
56
58 """
59 I am a base class for all Issuers.
60 An issuer issues keycards of a given class based on an object
61 (incoming HTTP request, ...)
62 """
63 - def issue(self, *args, **kwargs):
64 """
65 Return a keycard, or None, based on the given arguments.
66 """
67 raise NotImplementedError
68
70 """
71 I create L{flumotion.common.keycards.Keycard} based on just a
72 standard HTTP request. Useful for authenticating based on
73 server-side checks such as time, rather than client credentials.
74 """
75 - def issue(self, request):
79
81 """
82 I create L{flumotion.common.keycards.KeycardUACPP} keycards based on
83 an incoming L{twisted.protocols.http.Request} request's standard
84 HTTP authentication information.
85 """
86 - def issue(self, request):
95
97 """
98 I create L{flumotion.common.keycards.KeycardToken} keycards based on
99 an incoming L{twisted.protocols.http.Request} request's GET "token"
100 parameter.
101 """
102 - def issue(self, request):
114
115 BOUNCER_SOCKET = 'flumotion.component.bouncers.plug.BouncerPlug'
116
171
172 if tryingAgain:
173 self._keepAlive = reactor.callLater(self.KEYCARD_TRYAGAIN_INTERVAL,
174 timeout)
175 else:
176 self._keepAlive = reactor.callLater(self.KEYCARD_KEEPALIVE_INTERVAL,
177 timeout)
178
180 if self._keepAlive is not None:
181 self._keepAlive.cancel()
182 self._keepAlive = None
183
184 - def setDomain(self, domain):
185 """
186 Set a domain name on the resource, used in HTTP auth challenges and
187 on the keycard.
188
189 @type domain: string
190 """
191 self._domain = domain
192
194 self.bouncerName = bouncerName
195
197 self.requesterId = requesterId
198
199 self.issuerName = str(self.requesterId) + '-' + cryptChallenge()
200
202 self._defaultDuration = defaultDuration
203
205
206
207 if issuerClass == 'HTTPTokenIssuer':
208 self._issuer = HTTPTokenIssuer()
209 elif issuerClass == 'HTTPAuthIssuer':
210 self._issuer = HTTPAuthIssuer()
211 elif issuerClass == 'HTTPGenericIssuer':
212 self._issuer = HTTPGenericIssuer()
213 else:
214 raise ValueError, "issuerClass %s not accepted" % issuerClass
215
241
244
245 - def keepAlive(self, bouncerName, issuerName, ttl):
247
250
251
254
256
257
258 def cleanup(bouncerName, keycard):
259 def cleanupLater(res, pair):
260 self.log('failed to clean up keycard %r, will do '
261 'so later', keycard)
262 self._pendingCleanups.append(pair)
263 d = self.cleanupKeycard(bouncerName, keycard)
264 d.addErrback(cleanupLater, (bouncerName, keycard))
265 pending = self._pendingCleanups
266 self._pendingCleanups = []
267 cleanup(bouncerName, keycard)
268 for bouncerName, keycard in pending:
269 cleanup(bouncerName, keycard)
270
271
273 if self.bouncerName and self._fdToKeycard.has_key(fd):
274 keycard = self._fdToKeycard[fd]
275 del self._fdToKeycard[fd]
276 del self._idToKeycard[keycard.id]
277 self.debug('[fd %5d] asking bouncer %s to remove keycard id %s',
278 fd, self.bouncerName, keycard.id)
279 self.doCleanupKeycard(self.bouncerName, keycard)
280 if self._fdToDurationCall.has_key(fd):
281 self.debug('[fd %5d] canceling later expiration call' % fd)
282 self._fdToDurationCall[fd].cancel()
283 del self._fdToDurationCall[fd]
284
286 """
287 Expire a client due to a duration expiration.
288 """
289 self.debug('[fd %5d] duration exceeded, expiring client' % fd)
290
291
292 if self._fdToDurationCall.has_key(fd):
293 del self._fdToDurationCall[fd]
294
295 self.debug('[fd %5d] asking streamer to remove client' % fd)
296 self.clientDone(fd)
297
299 """
300 Expire a client's connection associated with the keycard Id.
301 """
302 keycard = self._idToKeycard[keycardId]
303 fd = keycard._fd
304
305 self.debug('[fd %5d] expiring client' % fd)
306
307 if self._fdToDurationCall.has_key(fd):
308 self.debug('[fd %5d] canceling later expiration call' % fd)
309 self._fdToDurationCall[fd].cancel()
310 del self._fdToDurationCall[fd]
311
312 self.debug('[fd %5d] asking streamer to remove client' % fd)
313 self.clientDone(fd)
314
315
316
323
352
357
375
379
381 """
382 Add an IP filter of the form IP/prefix-length (CIDR syntax), or just
383 a single IP address
384 """
385 definition = filter.split('/')
386 if len(definition) == 2:
387 (net, prefixlen) = definition
388 prefixlen = int(prefixlen)
389 elif len(definition) == 1:
390 net = definition[0]
391 prefixlen = 32
392 else:
393 raise errors.ConfigError(
394 "Cannot parse filter definition %s" % filter)
395
396 if prefixlen < 0 or prefixlen > 32:
397 raise errors.ConfigError("Invalid prefix length")
398
399 mask = ~((1 << (32 - prefixlen)) - 1)
400 try:
401 net = struct.unpack(">I", socket.inet_pton(socket.AF_INET, net))[0]
402 except:
403 raise errors.ConfigError("Failed to parse network address %s" % net)
404 net = net & mask
405
406 self.filters.append((net, mask))
407
409 """
410 Return true if ip is in any of the defined network(s) for this filter
411 """
412
413 realip = struct.unpack(">I", socket.inet_pton(socket.AF_INET, ip))[0]
414 for f in self.filters:
415 if (realip & f[1]) == f[0]:
416 return True
417 return False
418