1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import os
21 import base64
22 import xmlrpclib
23 import re
24
25
26 from spacewalk.common.rhnConfig import CFG
27 from spacewalk.common.rhnLog import log_debug, log_error
28 from spacewalk.common.rhnApache import rhnApache
29 from spacewalk.common.rhnTB import Traceback
30 from spacewalk.common.rhnException import rhnFault, rhnException
31 from spacewalk.common import rhnFlags, apache
32 from spacewalk.common.rhnLib import setHeaderValue
33 from spacewalk.common import byterange
34
35 from rhn import rpclib, connections
36 from rhn.UserDictCase import UserDictCase
37 from rhnConstants import HEADER_ACTUAL_URI, HEADER_EFFECTIVE_URI, \
38 HEADER_CHECKSUM, SCHEME_HTTP, SCHEME_HTTPS, URI_PREFIX_KS, \
39 URI_PREFIX_KS_CHECKSUM, COMPONENT_BROKER, COMPONENT_REDIRECT
40
41
42 from proxy.rhnProxyAuth import get_proxy_auth
46 """
47 Are we a 'proxy.broker' or a 'proxy.redirect'.
48
49 Checks to see if the last visited Spacewalk Proxy was itself. If so, we
50 are a 'proxy.redirect'. If not, then we must be a 'proxy.broker'.
51 """
52
53
54 if not req.headers_in.has_key('X-RHN-Proxy-Auth'):
55
56 return COMPONENT_BROKER
57
58
59 proxy_auth = req.headers_in['X-RHN-Proxy-Auth']
60 last_auth = proxy_auth.split(',')[-1]
61 last_visited = last_auth.split(':')[0]
62 proxy_server_id = get_proxy_auth().getProxyServerId()
63
64 try:
65 log_debug(4, "last_visited", last_visited, "; proxy server id",
66 proxy_server_id)
67
68 except:
69
70
71 pass
72 if last_visited == proxy_server_id:
73
74 return COMPONENT_REDIRECT
75
76 return COMPONENT_BROKER
77
80
81 """ Main apache entry point for the proxy. """
82 _lang_catalog = "proxy"
83
88
90 self._component = component
91
92 @staticmethod
104
120
157
187
188 @staticmethod
206
208 """ Sends a HEAD request to the satellite for the purpose of obtaining
209 the checksum for the requested resource. A (status, checksum)
210 tuple is returned. If status is not apache.OK, checksum will be
211 None. If status is OK, and a checksum is not returned, the old
212 BZ 158236 behavior will be used.
213 """
214 scheme = SCHEME_HTTP
215 if req.server.port == 443:
216 scheme = SCHEME_HTTPS
217 log_debug(6, "Using scheme: %s" % scheme)
218
219
220
221
222
223
224
225 host = "127.0.0.1"
226 port = req.connection.local_addr[1]
227
228 connection = self._createConnection(host, port, scheme)
229 if not connection:
230
231
232
233
234 log_error('HEAD req - Could not create connection to %s://%s:%s'
235 % (scheme, host, str(port)))
236 return (apache.OK, None)
237
238
239
240
241 pingURL = "%s://%s:%s%s" % (scheme, host, str(port), req.uri)
242 log_debug(6, "Ping URI: %s" % pingURL)
243
244 hdrs = UserDictCase()
245 for k in req.headers_in.keys():
246 if k.lower() != 'range':
247 hdrs[k] = re.sub(r'\n(?![ \t])|\r(?![ \t\n])', '', str(req.headers_in[k]))
248
249 log_debug(9, "Using existing headers_in", hdrs)
250 connection.request("HEAD", pingURL, None, hdrs)
251 log_debug(6, "Connection made, awaiting response.")
252
253
254
255 response = connection.getresponse()
256 log_debug(6, "Received response status: %s" % response.status)
257 connection.close()
258
259 if (response.status != apache.HTTP_OK) and (response.status != apache.HTTP_PARTIAL_CONTENT):
260
261
262 log_debug(1, "HEAD req - Received error code in reponse: %s"
263 % (str(response.status)))
264 return (response.status, None)
265
266
267
268 responseHdrs = response.msg
269 if not responseHdrs:
270
271
272
273 log_error("HEAD response - No HTTP headers!")
274 return (apache.OK, None)
275
276 if not responseHdrs.has_key(HEADER_CHECKSUM):
277
278
279
280
281
282 log_debug(1, "HEAD response - No X-RHN-Checksum field provided!")
283 return (apache.OK, None)
284
285 checksum = responseHdrs[HEADER_CHECKSUM]
286
287 return (apache.OK, checksum)
288
289 @staticmethod
291 """
292 This routine computes a new cacheable URI based on the old URI and the
293 checksum. For example, if the checksum is 1234ABCD and the oldURI was:
294
295 /ty/AljAmCEt/RedHat/base/comps.xml
296
297 Then, the new URI will be:
298
299 /ty-cksm/1234ABCD/RedHat/base/comps.xml
300
301 If for some reason the new URI could not be generated, return None.
302 """
303
304 newURI = URI_PREFIX_KS_CHECKSUM + checksum
305
306
307
308 uriParts = oldURI.split('/')
309 numParts = 0
310 for part in uriParts:
311 if len(part) is not 0:
312 numParts += 1
313 if numParts > 2:
314 newURI += "/" + part
315
316
317
318 if numParts <= 2:
319 newURI = None
320
321 return newURI
322
323 @staticmethod
337
339 """ Main handler to handle all requests pumped through this server. """
340
341 ret = rhnApache.handler(self, req)
342 if ret != apache.OK:
343 return ret
344
345 log_debug(4, "METHOD", req.method)
346 log_debug(4, "PATH_INFO", req.path_info)
347 log_debug(4, "URI (full path info)", req.uri)
348 log_debug(4, "Component", self._component)
349
350 if self._component == COMPONENT_BROKER:
351 from broker import rhnBroker
352 handlerObj = rhnBroker.BrokerHandler(req)
353 else:
354
355 from redirect import rhnRedirect
356 handlerObj = rhnRedirect.RedirectHandler(req)
357
358 try:
359 ret = handlerObj.handler()
360 except rhnFault, e:
361 return self.response(req, e)
362
363 if rhnFlags.test("NeedEncoding"):
364 return self.response(req, ret)
365
366
367 if not isinstance(ret, type(1)):
368 raise rhnException("Invalid status code type %s" % type(ret))
369 log_debug(1, "Leaving with status code %s" % ret)
370 return ret
371
372 @staticmethod
374 """ convert a response to the right type for passing back to
375 rpclib.xmlrpclib.dumps
376 """
377 if isinstance(response, xmlrpclib.Fault):
378 return response
379 return (response,)
380
381 @staticmethod
383 """ send a file out """
384 log_debug(3, response.name)
385
386 if rhnFlags.test("Content-Type"):
387 req.content_type = rhnFlags.get("Content-Type")
388 else:
389
390 req.content_type = "application/octet-stream"
391
392
393 if response.length == 0:
394 response.file_obj.seek(0, 2)
395 file_size = response.file_obj.tell()
396 response.file_obj.seek(0, 0)
397 else:
398 file_size = response.length
399
400 success_response = apache.OK
401 response_size = file_size
402
403
404 if req.headers_in.has_key("Range"):
405 try:
406 range_start, range_end = \
407 byterange.parse_byteranges(req.headers_in["Range"],
408 file_size)
409 response_size = range_end - range_start
410 req.headers_out["Content-Range"] = \
411 byterange.get_content_range(range_start, range_end, file_size)
412 req.headers_out["Accept-Ranges"] = "bytes"
413
414 response.file_obj.seek(range_start)
415
416
417
418 req.status = apache.HTTP_PARTIAL_CONTENT
419 success_response = apache.HTTP_PARTIAL_CONTENT
420
421
422 except byterange.InvalidByteRangeException:
423 pass
424 except byterange.UnsatisfyableByteRangeException:
425 pass
426
427 req.headers_out["Content-Length"] = str(response_size)
428
429
430
431
432
433 if response.name:
434 req.headers_out["X-Package-FileName"] = response.name
435
436 xrepcon = req.headers_in.has_key("X-Replace-Content-Active") \
437 and rhnFlags.test("Download-Accelerator-Path")
438 if xrepcon:
439 fpath = rhnFlags.get("Download-Accelerator-Path")
440 log_debug(1, "Serving file %s" % fpath)
441 req.headers_out["X-Replace-Content"] = fpath
442
443 byte_rate = rhnFlags.get("QOS-Max-Bandwidth")
444 if byte_rate:
445 req.headers_out["X-Replace-Content-Throttle"] = str(byte_rate)
446
447
448 req.send_http_header()
449
450 if req.headers_in.has_key("Range"):
451
452 read = 0
453 while read < response_size:
454
455 buf = response.read(CFG.BUFFER_SIZE)
456 if not buf:
457 break
458 try:
459 req.write(buf)
460 read = read + CFG.BUFFER_SIZE
461 except IOError:
462 if xrepcon:
463
464
465 break
466 return apache.HTTP_BAD_REQUEST
467 response.close()
468 else:
469 if 'wsgi.file_wrapper' in req.headers_in:
470 req.output = req.headers_in['wsgi.file_wrapper'](response, CFG.BUFFER_SIZE)
471 else:
472 req.output = iter(lambda: response.read(CFG.BUFFER_SIZE), '')
473 return success_response
474
476 """ send the response (common code) """
477
478
479 log_debug(5, "Response type", type(response))
480
481 needs_xmlrpc_encoding = rhnFlags.test("NeedEncoding")
482 compress_response = rhnFlags.test("compress_response")
483
484
485 if isinstance(response, rpclib.transports.File):
486 if not hasattr(response.file_obj, 'fileno') and compress_response:
487
488
489 response = response.file_obj.read()
490 needs_xmlrpc_encoding = 0
491 else:
492
493 return self.response_file(req, response)
494
495 is_fault = 0
496 if isinstance(response, rhnFault):
497 if req.method == 'GET':
498 return self._response_fault_get(req, response.getxml())
499
500 response = response.getxml()
501 is_fault = 1
502
503 compress_response = 0
504
505 needs_xmlrpc_encoding = 1
506
507 output = rpclib.transports.Output()
508
509 if not is_fault:
510
511 output.set_transport_flags(
512 transfer=rpclib.transports.lookupTransfer(self.input.transfer),
513 encoding=rpclib.transports.lookupEncoding(self.input.encoding))
514
515 if compress_response:
516
517 log_debug(4, "Compression on for client version", self.clientVersion)
518 if self.clientVersion > 0:
519 output.set_transport_flags(output.TRANSFER_BINARY,
520 output.ENCODE_ZLIB)
521 else:
522 output.set_transport_flags(output.TRANSFER_BASE64,
523 output.ENCODE_ZLIB)
524
525
526 output.headers.update(rhnFlags.get('outputTransportOptions').dict())
527
528 if needs_xmlrpc_encoding:
529
530 response = self.normalize(response)
531 try:
532 response = rpclib.xmlrpclib.dumps(response, methodresponse=1)
533 except TypeError, e:
534 log_debug(-1, "Error \"%s\" encoding response = %s" % (e, response))
535 Traceback("apacheHandler.response", req,
536 extra="Error \"%s\" encoding response = %s" % (e, response),
537 severity="notification")
538 return apache.HTTP_INTERNAL_SERVER_ERROR
539 except Exception:
540
541 Traceback("apacheHandler.response", req,
542 severity="unhandled")
543 return apache.HTTP_INTERNAL_SERVER_ERROR
544
545
546 output.process(response)
547
548 for k, v in output.headers.items():
549 if k.lower() == 'content-type':
550
551 req.content_type = v
552 else:
553 setHeaderValue(req.headers_out, k, v)
554
555 if CFG.DEBUG == 4:
556
557 log_debug(4, "The response: %s[...SNIP (for sanity) SNIP...]%s" %
558 (response[:100], response[-100:]))
559 elif CFG.DEBUG >= 5:
560
561 log_debug(5, "The response: %s" % response)
562
563
564 req.send_http_header()
565 try:
566
567
568
569 req.write(output.data)
570 except IOError:
571
572
573 return apache.HTTP_BAD_REQUEST
574 del output
575 return apache.OK
576
577 @staticmethod
588
590 """ Clean up stuff before we close down the session when we are
591 called from apacheServer.Cleanup()
592 """
593
594 log_debug(1)
595 self.input = None
596
597 while 1:
598 pid = status = -1
599 try:
600 (pid, status) = os.waitpid(-1, 0)
601 except OSError:
602 break
603 else:
604 log_error("Reaped child process %d with status %d" % (pid, status))
605 ret = rhnApache.cleanupHandler(self, req)
606 return ret
607
608
609