Add missing (?) library
[moonshot-firefox.git] / nsHttpMoonshot.cpp
1 /* The contents of this file are subject to the Mozilla Public License Version
2  * 1.1 (the "License"); you may not use this file except in compliance with
3  * the License. You may obtain a copy of the License at
4  * http://www.mozilla.org/MPL/
5  *
6  * Software distributed under the License is distributed on an "AS IS" basis,
7  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
8  * for the specific language governing rights and limitations under the
9  * License.
10  *
11  * The Original Code is the Negotiateauth
12  *
13  * The Initial Developer of the Original Code is Daniel Kouril.
14  * Portions created by the Initial Developer are Copyright (C) 2003
15  * the Initial Developer. All Rights Reserved.
16  *
17  * Contributor(s):
18  *   Daniel Kouril <kouril@ics.muni.cz> (original author)
19  *   Wyllys Ingersoll <wyllys.ingersoll@sun.com>
20  *   Christopher Nebergall <cneberg@sandia.gov>
21  */
22
23 //
24 // GSSAPI Authentication Support Module
25 //
26 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
27 // (formerly draft-brezak-spnego-http-04.txt)
28 //
29 // Also described here:
30 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
31 //
32 //
33
34 /* this #define must run before prlog.h is included */
35 #define FORCE_PR_LOG 1
36
37 #include <stdlib.h>
38 #include "nsCOMPtr.h"
39 #include "nsIHttpChannel.h"
40 #include "nsIServiceManager.h"
41 #include "nsISupportsPrimitives.h"
42 #include "nsIURI.h"
43 #include "plbase64.h"
44 #include "plstr.h"
45 #include "prprf.h"
46 #include "prlog.h"
47 #include "prmem.h"
48 #include "nsISupportsUtils.h"
49
50 /* XXX, just for debugging */
51 #ifdef MOZILLA_INTERNAL_API
52 #include "nsString.h"
53 #else
54 #include "nsStringAPI.h"
55 #endif
56
57 /* HACK: */
58 #include <ctype.h>
59
60
61 #include "nsMoonshotSessionState.h"
62 #include "nsHttpMoonshot.h"
63
64 /* #define HAVE_GSS_C_NT_HOSTBASED_SERVICE 1 */
65 #include <gssapi.h>
66 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE 
67 #ifndef HEIMDAL
68  #include <gssapi/gssapi_generic.h> 
69 #endif
70 #endif
71
72 static gss_OID_desc gss_krb5_mech_oid_desc =
73 {9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
74
75 static gss_OID_desc gss_spnego_mech_oid_desc = 
76 {6, (void *)"\x2b\x06\x01\x05\x05\x02"};
77
78 // in order to do logging, the following environment variables need to be set:
79 // 
80 //      set NSPR_LOG_MODULES=negotiate:4
81 //      set NSPR_LOG_FILE=negotiate.log
82
83 #if defined(PR_LOGGING)
84
85     PRLogModuleInfo *gHttpLog = nsnull;
86     static PRLogModuleInfo* gNegotiateLog = nsnull;
87
88 #endif
89
90  #define LOG4(args) PR_LOG(gNegotiateLog, 4, args)
91  #define LOG(args) LOG4(args)
92
93 static void
94 parse_oid(char *mechanism, gss_OID * oid)
95 {
96     char   *mechstr = 0;
97     gss_buffer_desc tok;
98     OM_uint32 maj_stat, min_stat;
99     size_t i, mechlen = strlen(mechanism);
100
101     if (isdigit((int) mechanism[0])) {
102         mechstr = (char *)malloc(mechlen + 5);
103         if (!mechstr) {
104             fprintf(stderr, "Couldn't allocate mechanism scratch!\n");
105             return;
106         }
107         mechstr[0] = '{';
108         mechstr[1] = ' ';
109         for (i = 0; i < mechlen; i++)
110             mechstr[i + 2] = (mechanism[i] == '.') ? ' ' : mechanism[i];
111         mechstr[mechlen + 2] = ' ';
112         mechstr[mechlen + 3] = ' ';
113         mechstr[mechlen + 4] = '\0';
114         tok.value = mechstr;
115     } else
116         tok.value = mechanism;
117     tok.length = strlen((const char *)tok.value);
118     maj_stat = gss_str_to_oid(&min_stat, &tok, oid);
119     if (maj_stat != GSS_S_COMPLETE) {
120         //display_status("str_to_oid", maj_stat, min_stat);
121         return;
122     }
123     if (mechstr)
124         free(mechstr);
125 }
126
127 gss_OID
128 GetOID()
129 {
130     gss_OID mech_oid;
131
132     parse_oid("{1 3 6 1 4 1 5322 22 1 18}", &mech_oid);
133     return mech_oid;
134 }
135
136 nsHttpMoonshot::nsHttpMoonshot()
137 {
138    NS_INIT_ISUPPORTS();
139
140 #if defined(PR_LOGGING)
141    if (!gNegotiateLog)
142       gNegotiateLog = PR_NewLogModule("moonshot");
143 #endif /* PR_LOGGING */
144
145 }
146
147 nsHttpMoonshot::~nsHttpMoonshot()
148 {
149 }
150
151 NS_IMETHODIMP
152 nsHttpMoonshot::GetAuthFlags(PRUint32 *flags)
153 {
154   *flags = REQUEST_BASED; 
155   return NS_OK;
156 }
157
158 NS_IMETHODIMP
159 nsHttpMoonshot::ChallengeReceived(nsIHttpChannel *httpChannel,
160                                    const char *challenge,
161                                    PRBool isProxyAuth,
162                                    nsISupports **sessionState,
163                                    nsISupports **continuationState,
164                                    PRBool *identityInvalid)
165 {
166     nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
167
168     //
169     // Use this opportunity to instantiate the session object
170     // that gets used later when we generate the credentials.
171     //
172     if (!session) {
173         session = new nsMoonshotSessionState();
174         if (!session)
175                 return(NS_ERROR_OUT_OF_MEMORY);
176         NS_ADDREF(*sessionState = session);
177         *identityInvalid = PR_TRUE;
178         LOG(("nsHttpMoonshot::A new session context established\n"));
179     } else {
180         LOG(("nsHttpMoonshot::Still using context from previous request\n"));
181         *identityInvalid = PR_FALSE;
182     }
183
184     return NS_OK;
185 }
186
187 #if 0
188 NS_IMPL_ISUPPORTS2(nsHttpMoonshot, nsIHttpAuthenticator,
189                                     nsIHttpAuthenticator_1_9_2)
190 #else
191 NS_IMPL_ISUPPORTS1(nsHttpMoonshot, nsIHttpAuthenticator)
192 #endif
193
194 //
195 // Generate proper GSSAPI error messages from the major and
196 // minor status codes.
197 //
198 void
199 nsHttpMoonshot::LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, char *prefix)
200 {
201    OM_uint32 new_stat;
202    OM_uint32 msg_ctx = 0;
203    gss_buffer_desc status1_string;
204    gss_buffer_desc status2_string;
205    OM_uint32 ret;
206    nsCAutoString error(prefix);
207
208    error += ": ";
209    do {
210       ret = gss_display_status (&new_stat,
211                                maj_stat,
212                                GSS_C_GSS_CODE,
213                                GSS_C_NULL_OID,
214                                &msg_ctx,
215                                &status1_string);
216       error += (char *)status1_string.value;
217       error += "\n";
218       ret = gss_display_status (&new_stat,
219                                min_stat,
220                                GSS_C_MECH_CODE,
221                                GSS_C_NULL_OID,
222                                &msg_ctx,
223                                &status2_string);
224       error += (char *)status2_string.value;
225       error += "\n";
226
227    } while (!GSS_ERROR(ret) && msg_ctx != 0);
228
229    // LOG(("%s", ToNewCString(error)));
230    LOG(("%s\n", error.get()));
231 }
232
233 //
234 // GenerateCredentials
235 //
236 // This routine is responsible for creating the correct authentication
237 // blob to pass to the server that requested "Negotiate" authentication.
238 //
239 NS_IMETHODIMP
240 nsHttpMoonshot::GenerateCredentials(nsIHttpChannel *httpChannel,
241                                      const char *challenge,
242                                      PRBool isProxyAuth,
243                                      const PRUnichar *domain,
244                                      const PRUnichar *user,
245                                      const PRUnichar *password,
246                                      nsISupports **sessionState,
247                                      nsISupports **continuationState,
248                                      char **creds)
249 {
250     LOG(("nsHttpMoonshot::GenerateCredentials [challenge=%s]\n", challenge));
251
252     PRUint32 unused;
253     return GenerateCredentials_1_9_2(httpChannel,
254                                      challenge,
255                                      isProxyAuth,
256                                      domain,
257                                      user,
258                                      password,
259                                      sessionState,
260                                      continuationState,
261                                      &unused,
262                                      creds);
263 }
264
265 NS_IMETHODIMP
266 nsHttpMoonshot::GenerateCredentials_1_9_2(nsIHttpChannel *httpChannel,
267                                                const char *challenge,
268                                                PRBool isProxyAuth,
269                                                const PRUnichar *domain,
270                                                const PRUnichar *username,
271                                                const PRUnichar *password,
272                                                nsISupports **sessionState,
273                                                nsISupports **continuationState,
274                                                PRUint32 *flags,
275                                                char **creds)
276 {
277    OM_uint32 major_status, minor_status;
278    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
279    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
280    gss_buffer_t  in_token_ptr = GSS_C_NO_BUFFER;
281    gss_name_t server;
282    nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
283    gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
284
285
286    nsCOMPtr<nsIURI> uri;
287    nsresult rv;
288    nsCAutoString service;
289    
290    LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
291
292    NS_ENSURE_ARG_POINTER(creds);
293
294    PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
295                 strlen(NEGOTIATE_AUTH));
296    NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
297
298    rv = httpChannel->GetURI(getter_AddRefs(uri));
299    if (NS_FAILED(rv)) return rv;
300
301    rv = uri->GetAsciiHost(service);
302    if (NS_FAILED(rv)) return rv;
303    
304    LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n", 
305        service.get()));
306
307   // TEST
308 //   LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
309
310    //
311    // The correct service name for IIS servers is "HTTP/f.q.d.n", so
312    // construct the proper service name for passing to "gss_import_name".
313    //
314    // TODO: Possibly make this a configurable service name for use
315    // with non-standard servers that use stuff like "khttp/f.q.d.n" 
316    // instead.
317    //
318 /* DK:   service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
319 #if 0
320    service.Insert("HTTP@", 0);
321 #else
322    service.Insert("host@", 0);
323 #endif
324
325    input_token.value = (void *)service.get();
326    input_token.length = service.Length() + 1;
327
328    major_status = gss_import_name(&minor_status,
329                                  &input_token,
330 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
331                                  GSS_C_NT_HOSTBASED_SERVICE,
332 #else
333                                  gss_nt_service_name,
334 #endif
335                                  &server);
336    input_token.value = NULL;
337    input_token.length = 0;
338    if (GSS_ERROR(major_status)) {
339       LogGssError(major_status, minor_status, "gss_import_name() failed");
340       return NS_ERROR_FAILURE;
341    }
342
343    //
344    // If the "Negotiate:" header had some data associated with it,
345    // that data should be used as the input to this call.  This may
346    // be a continuation of an earlier call because GSSAPI authentication
347    // often takes multiple round-trips to complete depending on the
348    // context flags given.  We want to use MUTUAL_AUTHENTICATION which
349    // generally *does* require multiple round-trips.  Don't assume
350    // auth can be completed in just 1 call.
351    //
352    unsigned int len = strlen(challenge);
353
354    if (len > strlen(NEGOTIATE_AUTH)) {
355         challenge += strlen(NEGOTIATE_AUTH);
356         while (*challenge == ' ') challenge++;
357         len = strlen(challenge);
358
359
360         if(len && (0 == (len & 3)) )
361         {
362          if( (char)'=' == challenge[len-1] )
363          {
364              if( (char)'=' == challenge[len-2] )
365              {
366                  len -= 2;
367              }
368              else
369              {
370                  len -= 1;
371              }
372          }
373      }
374
375
376         input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
377 //        input_token.length = (len * 3)/4;
378         input_token.value = malloc(input_token.length + 1);
379         if (!input_token.value)
380                 return (NS_ERROR_OUT_OF_MEMORY);
381
382         //
383         // Decode the response that followed the "Negotiate" token
384         //
385         if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
386                 free(input_token.value);
387                 return(NS_ERROR_UNEXPECTED);
388         }
389         in_token_ptr = &input_token;
390         LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
391    } else {
392         //
393         // Starting over, clear out any existing context and don't
394         // use an input token.
395         //
396         // TEST
397 /*      if (session->context_state == 2) {
398           *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
399           if (!(*creds)) {
400               return NS_ERROR_OUT_OF_MEMORY;
401           }
402
403           sprintf(*creds, "%s", NEGOTIATE_AUTH);
404           return NS_OK;
405         } else { */
406           session->Reset();
407           in_token_ptr = GSS_C_NO_BUFFER;
408         //}
409    }
410
411    /* HACK */
412    {
413         OM_uint32 maj_stat, min_stat;
414         gss_buffer_desc tmp_token;
415         gss_name_t gss_username = GSS_C_NO_NAME;
416         gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
417
418         tmp_token.value = (void *) "steve@local";
419         tmp_token.length = strlen((const char *)tmp_token.value);
420         maj_stat = gss_import_name(&min_stat, &tmp_token,
421                                    GSS_C_NT_USER_NAME,
422                                    &gss_username);
423
424         if (GSS_ERROR(maj_stat)) {
425             LogGssError(maj_stat, min_stat, "gss_import_name() failed");
426             session->Reset();
427             return NS_ERROR_FAILURE;
428         }
429
430         mechs.elements = GetOID();
431         mechs.count = 1;
432         mechsp = &mechs;
433
434         tmp_token.value = (void *)"testing";
435         tmp_token.length = strlen((const char*)tmp_token.value);
436         maj_stat = gss_acquire_cred_with_password(&min_stat,
437                                                   gss_username, &tmp_token, 0,
438                                                   mechsp, GSS_C_INITIATE,
439                                                   &cred, NULL, NULL);
440         if (GSS_ERROR(maj_stat)) {
441             LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
442             session->Reset();
443             return NS_ERROR_FAILURE;
444         }
445    }
446
447    major_status = gss_init_sec_context(&minor_status,
448                                     cred,
449                                     &session->gss_ctx,
450                                     server,
451                                     GetOID(),
452                                     GSS_C_MUTUAL_FLAG,
453                                     /* GSS_C_INDEFINITE */ 0,
454                                     GSS_C_NO_CHANNEL_BINDINGS,
455                                     in_token_ptr,
456                                     nsnull,
457                                     &output_token,
458                                     nsnull,
459                                     nsnull);
460
461    if (GSS_ERROR(major_status)) {
462       LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
463       (void) gss_release_name(&minor_status, &server);
464       gss_release_cred(&minor_status, &cred);
465       session->Reset();
466       if (input_token.length > 0 && input_token.value != NULL)
467               (void) gss_release_buffer(&minor_status, &input_token);
468       return NS_ERROR_FAILURE;
469    }
470
471    if (major_status == GSS_S_COMPLETE) {
472         //
473         // We are done with this authentication, reset the context. 
474         //
475         // TEST
476         // session->Reset();
477         session->gss_state = GSS_CTX_ESTABLISHED;
478         LOG(("GSS Auth done"));
479    } else if (major_status == GSS_S_CONTINUE_NEEDED) {
480         //
481         // We could create a continuation state, but its not
482         // really necessary.
483         //
484         // The important thing is that we do NOT reset the
485         // session context here because it will be needed on the
486         // next call.
487         //
488         // TEST
489         session->gss_state = GSS_CTX_IN_PROGRESS;
490         LOG(("GSS Auth continuing"));
491    } 
492
493    // We don't need the input token data anymore.
494    if (input_token.length > 0 && input_token.value != NULL)
495         (void) gss_release_buffer(&minor_status, &input_token);
496
497    if (output_token.length == 0) {
498       LOG(("No GSS output token to send, exiting"));
499       (void) gss_release_name(&minor_status, &server);
500       gss_release_cred(&minor_status, &cred);
501       return NS_ERROR_FAILURE;
502    }
503
504    //
505    // The token output from the gss_init_sec_context call is
506    // encoded and used as the Authentication response for the
507    // server.
508    //
509    char *encoded_token = PL_Base64Encode((char *)output_token.value,
510                                         output_token.length,
511                                         nsnull);
512    if (!encoded_token) {
513       (void) gss_release_buffer(&minor_status, &output_token);
514       (void) gss_release_name(&minor_status, &server);
515       gss_release_cred(&minor_status, &cred);
516       return NS_ERROR_OUT_OF_MEMORY;
517    }
518
519    LOG(("Sending a token of length %d\n", output_token.length));
520
521    // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
522    *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
523    if (!(*creds)) {
524       PR_Free(encoded_token);
525       (void) gss_release_buffer(&minor_status, &output_token);
526       (void) gss_release_name(&minor_status, &server);
527       gss_release_cred(&minor_status, &cred);
528       return NS_ERROR_OUT_OF_MEMORY;
529    }
530
531    sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
532    PR_Free(encoded_token);
533
534    (void) gss_release_buffer(&minor_status, &output_token);
535    (void) gss_release_name(&minor_status, &server);
536       gss_release_cred(&minor_status, &cred);
537
538    LOG(("returning the call"));
539
540    return NS_OK;
541 }