1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import socket
19 import re
20 from urlparse import urlparse, urlunparse
21
22
23 from spacewalk.common.rhnConfig import CFG
24 from spacewalk.common.rhnLog import log_debug, log_error
25 from spacewalk.common.rhnTB import Traceback
26 from spacewalk.common import rhnFlags, rhnLib, apache
27
28
29 from rhn import connections
30
31
32 from proxy.rhnShared import SharedHandler
33 from proxy import rhnConstants
34
35
36
37
38
40
41 """ Spacewalk Proxy SSL Redirect specific handler code called by rhnApache.
42
43 Workflow is:
44 Client -> Apache:Broker -> Squid -> Apache:Redirect -> Satellite
45
46 Redirect handler get all request for localhost:80 and they come
47 from Broker handler through Squid, which hadle caching.
48 Redirect module transform destination url to parent or http proxy.
49 Depend on what we have in CFG.
50 """
51
57
59 """ set connection variables
60 NOTE: self.{caChain,rhnParent,httpProxy*} are initialized
61 in SharedHandler
62 """
63
64 effectiveURI = self._getEffectiveURI()
65 effectiveURI_parts = urlparse(effectiveURI)
66 scheme = 'http'
67 if CFG.USE_SSL:
68 scheme = 'https'
69 else:
70 self.caChain = ''
71 self.rhnParentXMLRPC = urlunparse((scheme, self.rhnParent, '/XMLRPC', '', '', ''))
72 self.rhnParent = urlunparse((scheme, self.rhnParent) + effectiveURI_parts[2:])
73
74 log_debug(3, 'remapped self.rhnParent: %s' % self.rhnParent)
75 log_debug(3, 'remapped self.rhnParentXMLRPC: %s' % self.rhnParentXMLRPC)
76
104
106 """ Here, we'll override the default behavior for handling server responses
107 so that we can adequately handle 302's.
108
109 We will follow redirects unless it is redirect to (re)login page. In which
110 case we change protocol to https and return redirect to user.
111 """
112
113
114
115
116 if status == apache.HTTP_MOVED_TEMPORARILY or \
117 status == apache.HTTP_MOVED_PERMANENTLY:
118
119 log_debug(1, "Received redirect response: ", status)
120
121
122 headers = self.responseContext.getHeaders()
123 if headers is not None:
124 for headerKey in headers.keys():
125 if headerKey == 'location':
126 location = self._get_header(headerKey)
127 relogin = re.compile(r'https?://.*(/rhn/(Re)?Login.do\?.*)')
128 m = relogin.match(location[0])
129 if m:
130
131 proxy_auth = self.req.headers_in['X-RHN-Proxy-Auth']
132 last_auth = proxy_auth.split(',')[-1]
133 server_name = last_auth.split(':')[-1]
134 log_debug(1, "Redirecting to SSL version of login page")
135 rhnLib.setHeaderValue(self.req.headers_out, 'Location',
136 "https://%s%s" % (server_name, m.group(1)))
137 return apache.HTTP_MOVED_PERMANENTLY
138
139 redirectStatus = self.__redirectToNextLocation()
140
141
142
143
144
145
146
147
148
149 while redirectStatus == apache.HTTP_MOVED_PERMANENTLY or \
150 redirectStatus == apache.HTTP_MOVED_TEMPORARILY:
151
152
153
154
155
156 log_debug(1, "Redirected again! Code=", redirectStatus)
157 redirectStatus = self.__redirectToNextLocation(True)
158
159 if (redirectStatus != apache.HTTP_OK) and (redirectStatus != apache.HTTP_PARTIAL_CONTENT):
160
161
162
163
164 log_debug(1, "Redirection failed; retries exhausted. "
165 "Failing over. Code=",
166 redirectStatus)
167 redirectStatus = self.__redirectFailover()
168
169 return SharedHandler._handleServerResponse(self, redirectStatus)
170
171 else:
172
173 return SharedHandler._handleServerResponse(self, status)
174 return None
175
177 """ This function will perform a redirection to the next location, as
178 specified in the last response's "Location" header. This function will
179 return an actual HTTP response status code. If successful, it will
180 return apache.HTTP_OK, not apache.OK. If unsuccessful, this function
181 will retry a configurable number of times, as defined in
182 CFG.NETWORK_RETRIES. The following codes define "success".
183
184 HTTP_OK
185 HTTP_PARTIAL_CONTENT
186 HTTP_MOVED_TEMPORARILY
187 HTTP_MOVED_PERMANENTLY
188
189 Upon successful completion of this function, the responseContext
190 should be populated with the response.
191
192 Arguments:
193
194 loopProtection - If True, this function will insert a special
195 header into the new request that tells the RHN
196 server not to issue another redirect to us, in case
197 that's where we end up being redirected.
198
199 Return:
200
201 This function may return any valid HTTP_* response code. See
202 __redirectToNextLocationNoRetry for more info.
203 """
204 retriesLeft = CFG.NETWORK_RETRIES
205
206
207
208
209
210
211
212
213
214 redirectStatus = self.__redirectToNextLocationNoRetry(loopProtection)
215 while redirectStatus != apache.HTTP_OK and redirectStatus != apache.HTTP_PARTIAL_CONTENT and \
216 redirectStatus != apache.HTTP_MOVED_PERMANENTLY and \
217 redirectStatus != apache.HTTP_MOVED_TEMPORARILY and retriesLeft > 0:
218
219 retriesLeft = retriesLeft - 1
220 log_debug(1, "Redirection failed; trying again. "
221 "Retries left=",
222 retriesLeft,
223 "Code=",
224 redirectStatus)
225
226
227
228
229 self.responseContext.remove()
230
231
232 redirectStatus = \
233 self.__redirectToNextLocationNoRetry(loopProtection)
234
235 return redirectStatus
236
238 """ This function will perform a redirection to the next location, as
239 specified in the last response's "Location" header. This function will
240 return an actual HTTP response status code. If successful, it will
241 return apache.HTTP_OK, not apache.OK. If unsuccessful, this function
242 will simply return; no retries will be performed. The following error
243 codes can be returned:
244
245 HTTP_OK,HTTP_PARTIAL_CONTENT - Redirect successful.
246 HTTP_MOVED_TEMPORARILY - Redirect was redirected again by 3rd party.
247 HTTP_MOVED_PERMANENTLY - Redirect was redirected again by 3rd party.
248 HTTP_INTERNAL_SERVER_ERROR - Error extracting redirect information
249 HTTP_SERVICE_UNAVAILABLE - Could not connect to 3rd party server,
250 connection was reset, or a read error
251 occurred during communication.
252 HTTP_* - Any other HTTP status code may also be
253 returned.
254
255 Upon successful completion of this function, a new responseContext
256 will be created and pushed onto the stack.
257 """
258
259
260
261
262
263 redirectLocation = self._get_header(rhnConstants.HEADER_LOCATION)
264
265
266
267 self.responseContext.add()
268
269
270
271
272 if not redirectLocation:
273 log_error(" No redirect location specified!")
274 Traceback(mail=0)
275 return apache.HTTP_INTERNAL_SERVER_ERROR
276
277
278
279
280 redirectLocation = redirectLocation[0]
281 log_debug(1, " Redirecting to: ", redirectLocation)
282
283
284
285
286 _scheme, host, port, uri = self._parse_url(redirectLocation)
287
288
289 if redirectLocation.find('?') > -1:
290 uri += redirectLocation[redirectLocation.index('?'):]
291
292
293
294
295 params = {
296 'host': host,
297 'port': port,
298 }
299 if CFG.has_key('timeout'):
300 params['timeout'] = CFG.TIMEOUT
301 if CFG.USE_SSL:
302 log_debug(1, " Redirecting with SSL. Cert= ", self.caChain)
303 params['trusted_certs'] = [self.caChain]
304 connection = connections.HTTPSConnection(**params)
305 else:
306 log_debug(1, " Redirecting withOUT SSL.")
307 connection = connections.HTTPConnection(**params)
308
309
310 self.responseContext.setConnection(connection)
311
312
313
314 log_debug(4, "Attempting to connect to 3rd party server...")
315 try:
316 connection.connect()
317 except socket.error, e:
318 log_error("Error opening redirect connection", redirectLocation, e)
319 Traceback(mail=0)
320 return apache.HTTP_SERVICE_UNAVAILABLE
321 log_debug(4, "Connected to 3rd party server:",
322 connection.sock.getpeername())
323
324
325
326 response = None
327 try:
328
329
330
331 log_debug(4, "Making request: ", self.req.method, uri)
332 connection.putrequest(self.req.method, uri)
333
334
335
336 if loopProtection:
337 connection.putheader(rhnConstants.HEADER_RHN_REDIRECT, '0')
338
339 log_debug(4, " Adding original URL header: ", self.rhnParent)
340 connection.putheader(rhnConstants.HEADER_RHN_ORIG_LOC,
341 self.rhnParent)
342
343
344
345
346 for hdr in self.req.headers_in.keys():
347 if hdr.lower().startswith("x-rhn"):
348 connection.putheader(hdr, self.req.headers_in[hdr])
349 log_debug(4, "Passing request header: ",
350 hdr,
351 self.req.headers_in[hdr])
352
353 connection.endheaders()
354
355 response = connection.getresponse()
356 except IOError, ioe:
357
358 log_error("Redirect connection reset by peer.",
359 redirectLocation,
360 ioe)
361 Traceback(mail=0)
362
363
364
365 return apache.HTTP_SERVICE_UNAVAILABLE
366
367 except socket.error, se:
368
369 log_error("Redirect request failed.", redirectLocation, se)
370 Traceback(mail=0)
371
372
373
374 return apache.HTTP_SERVICE_UNAVAILABLE
375
376
377
378
379 self.responseContext.setBodyFd(response)
380 self.responseContext.setHeaders(response.msg)
381
382 log_debug(4, "Response headers: ",
383 self.responseContext.getHeaders().items())
384 log_debug(4, "Got redirect response. Status=", response.status)
385
386
387
388 return response.status
389
391 """ This routine resends the original request back to the satellite/hosted
392 system if a redirect to a 3rd party failed. To prevent redirection loops
393 from occurring, an "X-RHN-Redirect: 0" header is passed along with the
394 request. This function will return apache.HTTP_OK if everything
395 succeeded, otherwise it will return an appropriate HTTP error code.
396 """
397
398
399
400
401 headers = rhnFlags.get('outputTransportOptions')
402 headers[rhnConstants.HEADER_RHN_REDIRECT] = '0'
403
404 log_debug(4, "Added X-RHN-Redirect header to outputTransportOptions:",
405 headers)
406
407
408
409 self.responseContext.clear()
410 self._connectToParent()
411
412
413
414
415
416
417 status = self._serverCommo()
418
419
420
421 if status == apache.OK:
422 status = apache.HTTP_OK
423
424 return status
425