1253893Speter/* Copyright 2009 Justin Erenkrantz and Greg Stein
2253893Speter *
3253893Speter * Licensed under the Apache License, Version 2.0 (the "License");
4253893Speter * you may not use this file except in compliance with the License.
5253893Speter * You may obtain a copy of the License at
6253893Speter *
7253893Speter *     http://www.apache.org/licenses/LICENSE-2.0
8253893Speter *
9253893Speter * Unless required by applicable law or agreed to in writing, software
10253893Speter * distributed under the License is distributed on an "AS IS" BASIS,
11253893Speter * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12253893Speter * See the License for the specific language governing permissions and
13253893Speter * limitations under the License.
14253893Speter */
15253893Speter
16253893Speter#include "auth_spnego.h"
17253893Speter
18253893Speter#ifdef SERF_HAVE_SPNEGO
19253893Speter
20253893Speter/** These functions implement SPNEGO-based Kerberos and NTLM authentication,
21253893Speter *  using either GSS-API (RFC 2743) or SSPI on Windows.
22253893Speter *  The HTTP message exchange is documented in RFC 4559.
23253893Speter **/
24253893Speter
25253893Speter#include <serf.h>
26253893Speter#include <serf_private.h>
27253893Speter#include <auth/auth.h>
28253893Speter
29253893Speter#include <apr.h>
30253893Speter#include <apr_base64.h>
31253893Speter#include <apr_strings.h>
32253893Speter
33253893Speter/** TODO:
34253893Speter ** - send session key directly on new connections where we already know
35253893Speter **   the server requires Kerberos authn.
36253893Speter ** - Add a way for serf to give detailed error information back to the
37253893Speter **   application.
38253893Speter **/
39253893Speter
40253893Speter/* Authentication over HTTP using Kerberos
41253893Speter *
42253893Speter * Kerberos involves three servers:
43253893Speter * - Authentication Server (AS): verifies users during login
44253893Speter * - Ticket-Granting Server (TGS): issues proof of identity tickets
45253893Speter * - HTTP server (S)
46253893Speter *
47253893Speter * Steps:
48253893Speter * 0. User logs in to the AS and receives a TGS ticket. On workstations
49253893Speter * where the login program doesn't support Kerberos, the user can use
50253893Speter * 'kinit'.
51253893Speter *
52253893Speter * 1. C  --> S:    GET
53253893Speter *
54253893Speter *    C <--  S:    401 Authentication Required
55253893Speter *                 WWW-Authenticate: Negotiate
56253893Speter *
57253893Speter * -> app contacts the TGS to request a session key for the HTTP service
58253893Speter *    @ target host. The returned session key is encrypted with the HTTP
59253893Speter *    service's secret key, so we can safely send it to the server.
60253893Speter *
61253893Speter * 2. C  --> S:    GET
62253893Speter *                 Authorization: Negotiate <Base64 encoded session key>
63253893Speter *                 gss_api_ctx->state = gss_api_auth_in_progress;
64253893Speter *
65253893Speter *    C <--  S:    200 OK
66253893Speter *                 WWW-Authenticate: Negotiate <Base64 encoded server
67253893Speter *                                              authentication data>
68253893Speter *
69253893Speter * -> The server returned an (optional) key to proof itself to us. We check this
70253893Speter *    key with the TGS again. If it checks out, we can return the response
71253893Speter *    body to the application.
72253893Speter *
73253893Speter * Note: It's possible that the server returns 401 again in step 2, if the
74253893Speter *       Kerberos context isn't complete yet. This means there is 3rd step
75253893Speter *       where we'll send a request with an Authorization header to the
76253893Speter *       server. Some (simple) tests with mod_auth_kerb and MIT Kerberos 5 show
77253893Speter *       this never happens.
78253893Speter *
79253893Speter * Depending on the type of HTTP server, this handshake is required for either
80253893Speter * every new connection, or for every new request! For more info see the next
81253893Speter * comment on authn_persistence_state_t.
82253893Speter *
83253893Speter * Note: Step 1 of the handshake will only happen on the first connection, once
84253893Speter * we know the server requires Kerberos authentication, the initial requests
85253893Speter * on the other connections will include a session key, so we start at
86253893Speter * step 2 in the handshake.
87253893Speter * ### TODO: Not implemented yet!
88253893Speter */
89253893Speter
90253893Speter/* Current state of the authentication of the current request. */
91253893Spetertypedef enum {
92253893Speter    gss_api_auth_not_started,
93253893Speter    gss_api_auth_in_progress,
94253893Speter    gss_api_auth_completed,
95253893Speter} gss_api_auth_state;
96253893Speter
97253893Speter/**
98253893Speter   authn_persistence_state_t: state that indicates if we are talking with a
99253893Speter   server that requires authentication only of the first request (stateful),
100253893Speter   or of each request (stateless).
101253893Speter
102253893Speter   INIT: Begin state. Authenticating the first request on this connection.
103253893Speter   UNDECIDED: we haven't identified the server yet, assume STATEFUL for now.
104253893Speter     Pipeline mode disabled, requests are sent only after the response off the
105253893Speter     previous request arrived.
106253893Speter   STATELESS: we know the server requires authentication for each request.
107253893Speter     On all new requests add the Authorization header with an initial SPNEGO
108253893Speter     token (created per request).
109253893Speter     To keep things simple, keep the connection in one by one mode.
110253893Speter     (otherwise we'd have to keep a queue of gssapi context objects to match
111253893Speter      the Negotiate header of the response with the session initiated by the
112253893Speter      mathing request).
113253893Speter     This state is an final state.
114253893Speter   STATEFUL: alright, we have authenticated the connection and for the server
115253893Speter     that is enough. Don't add an Authorization header to new requests.
116253893Speter     Serf will switch to pipelined mode.
117253893Speter     This state is not a final state, although in practical scenario's it will
118253893Speter     be. When we receive a 40x response from the server switch to STATELESS
119253893Speter     mode.
120253893Speter
121253893Speter   We start in state init for the first request until it is authenticated.
122253893Speter
123253893Speter   The rest of the state machine starts with the arrival of the response to the
124253893Speter   second request, and then goes on with each response:
125253893Speter
126253893Speter      --------
127253893Speter      | INIT |     C --> S:    GET request in response to 40x of the server
128253893Speter      --------                 add [Proxy]-Authorization header
129253893Speter          |
130253893Speter          |
131253893Speter    ------------
132253893Speter    | UNDECIDED|   C --> S:    GET request, assume stateful,
133253893Speter    ------------               no [Proxy]-Authorization header
134253893Speter          |
135253893Speter          |
136253893Speter          |------------------------------------------------
137253893Speter          |                                               |
138253893Speter          | C <-- S: 40x Authentication                   | C <-- S: 200 OK
139253893Speter          |          Required                             |
140253893Speter          |                                               |
141253893Speter          v                                               v
142253893Speter      -------------                               ------------
143253893Speter    ->| STATELESS |<------------------------------| STATEFUL |<--
144253893Speter    | -------------       C <-- S: 40x            ------------  |
145253893Speter  * |    |                Authentication                  |     | 200 OK
146253893Speter    |    /                Required                        |     |
147253893Speter    -----                                                 -----/
148253893Speter
149253893Speter **/
150253893Spetertypedef enum {
151253893Speter    pstate_init,
152253893Speter    pstate_undecided,
153253893Speter    pstate_stateless,
154253893Speter    pstate_stateful,
155253893Speter} authn_persistence_state_t;
156253893Speter
157253893Speter
158253893Speter/* HTTP Service name, used to get the session key.  */
159253893Speter#define KRB_HTTP_SERVICE "HTTP"
160253893Speter
161253893Speter/* Stores the context information related to Kerberos authentication. */
162253893Spetertypedef struct
163253893Speter{
164253893Speter    apr_pool_t *pool;
165253893Speter
166253893Speter    /* GSSAPI context */
167253893Speter    serf__spnego_context_t *gss_ctx;
168253893Speter
169253893Speter    /* Current state of the authentication cycle. */
170253893Speter    gss_api_auth_state state;
171253893Speter
172253893Speter    /* Current persistence state. */
173253893Speter    authn_persistence_state_t pstate;
174253893Speter
175253893Speter    const char *header;
176253893Speter    const char *value;
177253893Speter} gss_authn_info_t;
178253893Speter
179253893Speter/* On the initial 401 response of the server, request a session key from
180253893Speter   the Kerberos KDC to pass to the server, proving that we are who we
181253893Speter   claim to be. The session key can only be used with the HTTP service
182253893Speter   on the target host. */
183253893Speterstatic apr_status_t
184253893Spetergss_api_get_credentials(char *token, apr_size_t token_len,
185253893Speter                        const char *hostname,
186253893Speter                        const char **buf, apr_size_t *buf_len,
187253893Speter                        gss_authn_info_t *gss_info)
188253893Speter{
189253893Speter    serf__spnego_buffer_t input_buf;
190253893Speter    serf__spnego_buffer_t output_buf;
191253893Speter    apr_status_t status = APR_SUCCESS;
192253893Speter
193253893Speter    /* If the server sent us a token, pass it to gss_init_sec_token for
194253893Speter       validation. */
195253893Speter    if (token) {
196253893Speter        input_buf.value = token;
197253893Speter        input_buf.length = token_len;
198253893Speter    } else {
199253893Speter        input_buf.value = 0;
200253893Speter        input_buf.length = 0;
201253893Speter    }
202253893Speter
203253893Speter    /* Establish a security context to the server. */
204253893Speter    status = serf__spnego_init_sec_context(
205253893Speter         gss_info->gss_ctx,
206253893Speter         KRB_HTTP_SERVICE, hostname,
207253893Speter         &input_buf,
208253893Speter         &output_buf,
209253893Speter         gss_info->pool,
210253893Speter         gss_info->pool
211253893Speter        );
212253893Speter
213253893Speter    switch(status) {
214253893Speter    case APR_SUCCESS:
215253893Speter        gss_info->state = gss_api_auth_completed;
216253893Speter        break;
217253893Speter    case APR_EAGAIN:
218253893Speter        gss_info->state = gss_api_auth_in_progress;
219253893Speter        status = APR_SUCCESS;
220253893Speter        break;
221253893Speter    default:
222253893Speter        return status;
223253893Speter    }
224253893Speter
225253893Speter    /* Return the session key to our caller. */
226253893Speter    *buf = output_buf.value;
227253893Speter    *buf_len = output_buf.length;
228253893Speter
229253893Speter    return status;
230253893Speter}
231253893Speter
232253893Speter/* do_auth is invoked in two situations:
233253893Speter   - when a response from a server is received that contains an authn header
234253893Speter     (either from a 40x or 2xx response)
235253893Speter   - when a request is prepared on a connection with stateless authentication.
236253893Speter
237253893Speter   Read the header sent by the server (if any), invoke the gssapi authn
238253893Speter   code and use the resulting Server Ticket on the next request to the
239253893Speter   server. */
240253893Speterstatic apr_status_t
241253893Speterdo_auth(peer_t peer,
242253893Speter        int code,
243253893Speter        gss_authn_info_t *gss_info,
244253893Speter        serf_connection_t *conn,
245253893Speter        const char *auth_hdr,
246253893Speter        apr_pool_t *pool)
247253893Speter{
248253893Speter    serf_context_t *ctx = conn->ctx;
249253893Speter    serf__authn_info_t *authn_info;
250253893Speter    const char *tmp = NULL;
251253893Speter    char *token = NULL;
252253893Speter    apr_size_t tmp_len = 0, token_len = 0;
253253893Speter    apr_status_t status;
254253893Speter
255253893Speter    if (peer == HOST) {
256253893Speter        authn_info = serf__get_authn_info_for_server(conn);
257253893Speter    } else {
258253893Speter        authn_info = &ctx->proxy_authn_info;
259253893Speter    }
260253893Speter
261253893Speter    /* Is this a response from a host/proxy? auth_hdr should always be set. */
262253893Speter    if (code && auth_hdr) {
263253893Speter        const char *space = NULL;
264253893Speter        /* The server will return a token as attribute to the Negotiate key.
265253893Speter           Negotiate YGwGCSqGSIb3EgECAgIAb10wW6ADAgEFoQMCAQ+iTzBNoAMCARCiRgREa6
266253893Speter           mouMBAMFqKVdTGtfpZNXKzyw4Yo1paphJdIA3VOgncaoIlXxZLnkHiIHS2v65pVvrp
267253893Speter           bRIyjF8xve9HxpnNIucCY9c=
268253893Speter
269253893Speter           Read this base64 value, decode it and validate it so we're sure the
270253893Speter           server is who we expect it to be. */
271253893Speter        space = strchr(auth_hdr, ' ');
272253893Speter
273253893Speter        if (space) {
274253893Speter            token = apr_palloc(pool, apr_base64_decode_len(space + 1));
275253893Speter            token_len = apr_base64_decode(token, space + 1);
276253893Speter        }
277253893Speter    } else {
278253893Speter        /* This is a new request, not a retry in response to a 40x of the
279253893Speter           host/proxy.
280253893Speter           Only add the Authorization header if we know the server requires
281253893Speter           per-request authentication (stateless). */
282253893Speter        if (gss_info->pstate != pstate_stateless)
283253893Speter            return APR_SUCCESS;
284253893Speter    }
285253893Speter
286253893Speter    switch(gss_info->pstate) {
287253893Speter        case pstate_init:
288253893Speter            /* Nothing to do here */
289253893Speter            break;
290253893Speter        case pstate_undecided: /* Fall through */
291253893Speter        case pstate_stateful:
292253893Speter            {
293253893Speter                /* Switch to stateless mode, from now on handle authentication
294253893Speter                   of each request with a new gss context. This is easiest to
295253893Speter                   manage when sending requests one by one. */
296253893Speter                serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
297253893Speter                              "Server requires per-request SPNEGO authn, "
298253893Speter                              "switching to stateless mode.\n");
299253893Speter
300253893Speter                gss_info->pstate = pstate_stateless;
301253893Speter                serf_connection_set_max_outstanding_requests(conn, 1);
302253893Speter                break;
303253893Speter            }
304253893Speter        case pstate_stateless:
305253893Speter            /* Nothing to do here */
306253893Speter            break;
307253893Speter    }
308253893Speter
309253893Speter    /* If the server didn't provide us with a token, start with a new initial
310253893Speter       step in the SPNEGO authentication. */
311253893Speter    if (!token) {
312253893Speter        serf__spnego_reset_sec_context(gss_info->gss_ctx);
313253893Speter        gss_info->state = gss_api_auth_not_started;
314253893Speter    }
315253893Speter
316253893Speter    if (peer == HOST) {
317253893Speter        status = gss_api_get_credentials(token, token_len,
318253893Speter                                         conn->host_info.hostname,
319253893Speter                                         &tmp, &tmp_len,
320253893Speter                                         gss_info);
321253893Speter    } else {
322253893Speter        char *proxy_host;
323253893Speter        apr_getnameinfo(&proxy_host, conn->ctx->proxy_address, 0);
324253893Speter        status = gss_api_get_credentials(token, token_len, proxy_host,
325253893Speter                                         &tmp, &tmp_len,
326253893Speter                                         gss_info);
327253893Speter    }
328253893Speter    if (status)
329253893Speter        return status;
330253893Speter
331253893Speter    /* On the next request, add an Authorization header. */
332253893Speter    if (tmp_len) {
333253893Speter        serf__encode_auth_header(&gss_info->value, authn_info->scheme->name,
334253893Speter                                 tmp,
335253893Speter                                 tmp_len,
336253893Speter                                 pool);
337253893Speter        gss_info->header = (peer == HOST) ?
338253893Speter            "Authorization" : "Proxy-Authorization";
339253893Speter    }
340253893Speter
341253893Speter    return APR_SUCCESS;
342253893Speter}
343253893Speter
344253893Speterapr_status_t
345253893Speterserf__init_spnego(int code,
346253893Speter                  serf_context_t *ctx,
347253893Speter                  apr_pool_t *pool)
348253893Speter{
349253893Speter    return APR_SUCCESS;
350253893Speter}
351253893Speter
352253893Speter/* A new connection is created to a server that's known to use
353253893Speter   Kerberos. */
354253893Speterapr_status_t
355253893Speterserf__init_spnego_connection(const serf__authn_scheme_t *scheme,
356253893Speter                             int code,
357253893Speter                             serf_connection_t *conn,
358253893Speter                             apr_pool_t *pool)
359253893Speter{
360253893Speter    gss_authn_info_t *gss_info;
361253893Speter    apr_status_t status;
362253893Speter
363253893Speter    gss_info = apr_pcalloc(conn->pool, sizeof(*gss_info));
364253893Speter    gss_info->pool = conn->pool;
365253893Speter    gss_info->state = gss_api_auth_not_started;
366253893Speter    gss_info->pstate = pstate_init;
367253893Speter    status = serf__spnego_create_sec_context(&gss_info->gss_ctx, scheme,
368253893Speter                                             gss_info->pool, pool);
369253893Speter
370253893Speter    if (status) {
371253893Speter        return status;
372253893Speter    }
373253893Speter
374253893Speter    if (code == 401) {
375253893Speter        conn->authn_baton = gss_info;
376253893Speter    } else {
377253893Speter        conn->proxy_authn_baton = gss_info;
378253893Speter    }
379253893Speter
380253893Speter    /* Make serf send the initial requests one by one */
381253893Speter    serf_connection_set_max_outstanding_requests(conn, 1);
382253893Speter
383253893Speter    serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
384253893Speter                  "Initialized Kerberos context for this connection.\n");
385253893Speter
386253893Speter    return APR_SUCCESS;
387253893Speter}
388253893Speter
389253893Speter/* A 40x response was received, handle the authentication. */
390253893Speterapr_status_t
391253893Speterserf__handle_spnego_auth(int code,
392253893Speter                         serf_request_t *request,
393253893Speter                         serf_bucket_t *response,
394253893Speter                         const char *auth_hdr,
395253893Speter                         const char *auth_attr,
396253893Speter                         void *baton,
397253893Speter                         apr_pool_t *pool)
398253893Speter{
399253893Speter    serf_connection_t *conn = request->conn;
400253893Speter    gss_authn_info_t *gss_info = (code == 401) ? conn->authn_baton :
401253893Speter        conn->proxy_authn_baton;
402253893Speter
403253893Speter    return do_auth(code == 401 ? HOST : PROXY,
404253893Speter                   code,
405253893Speter                   gss_info,
406253893Speter                   request->conn,
407253893Speter                   auth_hdr,
408253893Speter                   pool);
409253893Speter}
410253893Speter
411253893Speter/* Setup the authn headers on this request message. */
412253893Speterapr_status_t
413253893Speterserf__setup_request_spnego_auth(peer_t peer,
414253893Speter                                int code,
415253893Speter                                serf_connection_t *conn,
416253893Speter                                serf_request_t *request,
417253893Speter                                const char *method,
418253893Speter                                const char *uri,
419253893Speter                                serf_bucket_t *hdrs_bkt)
420253893Speter{
421253893Speter    gss_authn_info_t *gss_info = (peer == HOST) ? conn->authn_baton :
422253893Speter        conn->proxy_authn_baton;
423253893Speter
424253893Speter    /* If we have an ongoing authentication handshake, the handler of the
425253893Speter       previous response will have created the authn headers for this request
426253893Speter       already. */
427253893Speter    if (gss_info && gss_info->header && gss_info->value) {
428253893Speter        serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
429253893Speter                      "Set Negotiate authn header on retried request.\n");
430253893Speter
431253893Speter        serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
432253893Speter                                 gss_info->value);
433253893Speter
434253893Speter        /* We should send each token only once. */
435253893Speter        gss_info->header = NULL;
436253893Speter        gss_info->value = NULL;
437253893Speter
438253893Speter        return APR_SUCCESS;
439253893Speter    }
440253893Speter
441253893Speter    switch (gss_info->pstate) {
442253893Speter        case pstate_init:
443253893Speter            /* We shouldn't normally arrive here, do nothing. */
444253893Speter            break;
445253893Speter        case pstate_undecided: /* fall through */
446253893Speter            serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
447253893Speter                          "Assume for now that the server supports persistent "
448253893Speter                          "SPNEGO authentication.\n");
449253893Speter            /* Nothing to do here. */
450253893Speter            break;
451253893Speter        case pstate_stateful:
452253893Speter            serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
453253893Speter                          "SPNEGO on this connection is persistent, "
454253893Speter                          "don't set authn header on next request.\n");
455253893Speter            /* Nothing to do here. */
456253893Speter            break;
457253893Speter        case pstate_stateless:
458253893Speter            {
459253893Speter                apr_status_t status;
460253893Speter
461253893Speter                /* Authentication on this connection is known to be stateless.
462253893Speter                   Add an initial Negotiate token for the server, to bypass the
463253893Speter                   40x response we know we'll otherwise receive.
464253893Speter                  (RFC 4559 section 4.2) */
465253893Speter                serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
466253893Speter                              "Add initial Negotiate header to request.\n");
467253893Speter
468253893Speter                status = do_auth(peer,
469253893Speter                                 code,
470253893Speter                                 gss_info,
471253893Speter                                 conn,
472253893Speter                                 0l,    /* no response authn header */
473253893Speter                                 conn->pool);
474253893Speter                if (status)
475253893Speter                    return status;
476253893Speter
477253893Speter                serf_bucket_headers_setn(hdrs_bkt, gss_info->header,
478253893Speter                                         gss_info->value);
479253893Speter                /* We should send each token only once. */
480253893Speter                gss_info->header = NULL;
481253893Speter                gss_info->value = NULL;
482253893Speter                break;
483253893Speter            }
484253893Speter    }
485253893Speter
486253893Speter    return APR_SUCCESS;
487253893Speter}
488253893Speter
489253893Speter/* Function is called when 2xx responses are received. Normally we don't
490253893Speter * have to do anything, except for the first response after the
491253893Speter * authentication handshake. This specific response includes authentication
492253893Speter * data which should be validated by the client (mutual authentication).
493253893Speter */
494253893Speterapr_status_t
495253893Speterserf__validate_response_spnego_auth(peer_t peer,
496253893Speter                                    int code,
497253893Speter                                    serf_connection_t *conn,
498253893Speter                                    serf_request_t *request,
499253893Speter                                    serf_bucket_t *response,
500253893Speter                                    apr_pool_t *pool)
501253893Speter{
502253893Speter    gss_authn_info_t *gss_info;
503253893Speter    const char *auth_hdr_name;
504253893Speter
505253893Speter    /* TODO: currently this function is only called when a response includes
506253893Speter       an Authenticate header. This header is optional. If the server does
507253893Speter       not provide this header on the first 2xx response, we will not promote
508253893Speter       the connection from undecided to stateful. This won't break anything,
509253893Speter       but means we stay in non-pipelining mode. */
510253893Speter    serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
511253893Speter                  "Validate Negotiate response header.\n");
512253893Speter
513253893Speter    if (peer == HOST) {
514253893Speter        gss_info = conn->authn_baton;
515253893Speter        auth_hdr_name = "WWW-Authenticate";
516253893Speter    } else {
517253893Speter        gss_info = conn->proxy_authn_baton;
518253893Speter        auth_hdr_name = "Proxy-Authenticate";
519253893Speter    }
520253893Speter
521253893Speter    if (gss_info->state != gss_api_auth_completed) {
522253893Speter        serf_bucket_t *hdrs;
523253893Speter        const char *auth_hdr_val;
524253893Speter        apr_status_t status;
525253893Speter
526253893Speter        hdrs = serf_bucket_response_get_headers(response);
527253893Speter        auth_hdr_val = serf_bucket_headers_get(hdrs, auth_hdr_name);
528253893Speter
529253893Speter        status = do_auth(peer, code, gss_info, conn, auth_hdr_val, pool);
530253893Speter        if (status)
531253893Speter            return status;
532253893Speter    }
533253893Speter
534253893Speter    if (gss_info->state == gss_api_auth_completed) {
535253893Speter        switch(gss_info->pstate) {
536253893Speter            case pstate_init:
537253893Speter                /* Authentication of the first request is done. */
538253893Speter                gss_info->pstate = pstate_undecided;
539253893Speter                break;
540253893Speter            case pstate_undecided:
541253893Speter                /* The server didn't request for authentication even though
542253893Speter                   we didn't add an Authorization header to previous
543253893Speter                   request. That means it supports persistent authentication. */
544253893Speter                gss_info->pstate = pstate_stateful;
545253893Speter                serf_connection_set_max_outstanding_requests(conn, 0);
546253893Speter                break;
547253893Speter            default:
548253893Speter                /* Nothing to do here. */
549253893Speter                break;
550253893Speter        }
551253893Speter    }
552253893Speter
553253893Speter    return APR_SUCCESS;
554253893Speter}
555253893Speter
556253893Speter#endif /* SERF_HAVE_SPNEGO */
557