Resend POST requests for Kerberos in WinHTTP#2529
Resend POST requests for Kerberos in WinHTTP#2529chescock wants to merge 3 commits intolibgit2:masterfrom
Conversation
…ROR_WINHTTP_RESEND_REQUEST. We first send a zero-length request to get a 401 without writing the body twice.
|
This is terrifying. @ethomson? |
|
Hmm, that's very interesting. So yes, you're very much right that we should be re-posting the form data if we received a 401 on the original POST. This was sort of sneaky because we assume that we would never be in a situation where this mattered: the assumption is that a If I had to guess: Apache is tearing down the connection between the I'd love to see a fiddler capture, if you don't mind. It's possible that we're not sending keep-alive headers correctly for some reason, or that we're tearing down the connection... (Less likely is that it's not treating a kept-alive session as authenticated, which would be a bug in their implementation.) In any case, this is a very valid bug that I regret. I think that the really proper support would be to send a This should be the standards-compliant way to accomplish this same thing you're doing, but I'm not sure how to coerce winhttp into doing this offhand. (I'm actually pretty shocked that winhttp didn't do this for us.) |
|
I took a Fiddler trace and stripped out the hostnames and tickets and things. Should I just paste it here as a comment? I get It does seem like mod_auth_kerb treats the request as authenticated instead of the connection. I haven’t found any hard documentation, but I’ve found a few references that make me think that's expected:
at http://kerberos.996246.n3.nabble.com/resend-spnego-token-td21160.html, and
at http://code.google.com/p/serf/issues/detail?id=89. Web browsers seem to send the I can start looking into the Thanks! |
That works. You can also create a Gist if you feel the paste is too large. https://gist.github.com/ |
|
|
We deal with this in git-core, too. Ideally you would send an The relevant code in git-core is in |
|
Also see git/git@c80d96c for |
|
If I'm reading the code correctly, the only change I need to make to match the behavior of
The other difference is that I'm currently doing this for all requests instead of just large ones. I think WinHttp will resend the request body automatically if we pass the data in |
…ead of calling WinHttpWriteData so that WinHttp can resend the request if needed.
|
@ethomson Does this change look reasonable with the updates? Is there anything else you think I should fix? |
There was a problem hiding this comment.
This line looks off. Are you trying to do HTTP chunking by hand? Isn't WinHTTP supposed to do that? Do we even want chunking for this? We should be able to simply tell WinHTTP to send the flush packet since we know what length we want.
There was a problem hiding this comment.
Yes, that code is doing HTTP chunking by hand. There is similar code already in write_chunk and winhttp_stream_read. It seems that the way you do chunking in WinHTTP is by adding the header manually and then writing the chunk boundaries manually.
The documentation for WinHttpSendRequest says that “dwTotalLength must not change between calls to WinHttpSendRequest for the same request”, and we need to use the same request handle in order to preserve the authentication information. When using chunked encoding, dwTotalLength is set to WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH and so we’re able to send requests of different lengths, but I don’t see a way to use normal encoding if we’re trying to send an empty request followed by a real request on the same request handle.
|
Sorry! I have been slammed with other things this week. So the WinHTTP transport layer is not the only place where this code makes sense. Now that we have stateful authentication in the regular HTTP transport, we'll want to do the same sort of dance there. I wonder if it makes sense (or is possible) to move this up a level so that both transport layers can benefit from this logic for dealing with deficient servers. |
|
It should definitely be possible to do this in the smart protocol layer. We already need to perform special handling if the transport is stateless/rpc in some places, so we should be able to send a flush packet before we start the push proper to make sure the authentication happens early. |
|
Sorry for the delay in responding! I was out on vacation last week and I’m still catching up. I’ll start looking into how I could implement this in the higher layer. I’m a little more nervous about making a larger change, though, since I’m new to the codebase and extremely rusty at C. I’m not entirely convinced that this is actually a general concern as opposed to just handling quirks in the WinHTTP API. In the upload-pack case we can just give WinHTTP the entire request body and it will resend it as needed, but other transports might need a flush packet there as well. Most web browsers appear to resend the Authorization header on subsequent requests so that they don’t get 401 responses every time, and a transport that did that wouldn’t need to send the flush packet first. |
|
Seems this effort got stuck a little bit? It would be great to have this solved in the next version that will be published, as the Kerberos authentication simply doesn't work with Apache and mod_auth_kerb. I assume there mp workaround that can be applied on Apache configuration to get this working? |
|
Closed with #5286 |
I'm trying to use LibGit2Sharp to connect to an Apache server that uses mod_auth_kerb for authentication, and I'm getting the error "Failed to receive response: The request must be resent". It seems that the way WinHttp handles Kerberos authentication involves sending the request twice, which works fine for GET requests that have no body but fails for POST requests because it doesn’t store a copy of the request body to resend.
The workaround I’m using here is to send an initial empty POST when using Negotiate, which should trigger the 401 early and allow the actual request to succeed without having to write the body twice. I think this may be unnecessary in some cases, but I don’t see a way to tell whether it’s required and the extra empty POST appears to be harmless if it succeeds.
I’m fairly new to both Kerberos and WinHttp, so I apologize in advance if I’ve done something completely crazy and I welcome suggestions for other solutions to my problem.