initial commit
[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 "nsHttpMoonshot.h"
62
63 /* #define HAVE_GSS_C_NT_HOSTBASED_SERVICE 1 */
64 #include <gssapi.h>
65 #ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE 
66 #ifndef HEIMDAL
67  #include <gssapi/gssapi_generic.h> 
68 #endif
69 #endif
70
71 static gss_OID_desc gss_krb5_mech_oid_desc =
72 {9, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"};
73
74 static gss_OID_desc gss_spnego_mech_oid_desc = 
75 {6, (void *)"\x2b\x06\x01\x05\x05\x02"};
76
77 // in order to do logging, the following environment variables need to be set:
78 // 
79 //      set NSPR_LOG_MODULES=negotiate:4
80 //      set NSPR_LOG_FILE=negotiate.log
81
82 #if defined(PR_LOGGING)
83
84     PRLogModuleInfo *gHttpLog = nsnull;
85     static PRLogModuleInfo* gNegotiateLog = nsnull;
86
87 #endif
88
89  #define LOG4(args) PR_LOG(gNegotiateLog, 4, args)
90  #define LOG(args) LOG4(args)
91
92 static void
93 parse_oid(char *mechanism, gss_OID * oid)
94 {
95     char   *mechstr = 0;
96     gss_buffer_desc tok;
97     OM_uint32 maj_stat, min_stat;
98     size_t i, mechlen = strlen(mechanism);
99
100     if (isdigit((int) mechanism[0])) {
101         mechstr = (char *)malloc(mechlen + 5);
102         if (!mechstr) {
103             fprintf(stderr, "Couldn't allocate mechanism scratch!\n");
104             return;
105         }
106         mechstr[0] = '{';
107         mechstr[1] = ' ';
108         for (i = 0; i < mechlen; i++)
109             mechstr[i + 2] = (mechanism[i] == '.') ? ' ' : mechanism[i];
110         mechstr[mechlen + 2] = ' ';
111         mechstr[mechlen + 3] = ' ';
112         mechstr[mechlen + 4] = '\0';
113         tok.value = mechstr;
114     } else
115         tok.value = mechanism;
116     tok.length = strlen((const char *)tok.value);
117     maj_stat = gss_str_to_oid(&min_stat, &tok, oid);
118     if (maj_stat != GSS_S_COMPLETE) {
119         //display_status("str_to_oid", maj_stat, min_stat);
120         return;
121     }
122     if (mechstr)
123         free(mechstr);
124 }
125
126 class nsMoonshotSessionState : public nsISupports
127 {
128 public:
129     NS_DECL_ISUPPORTS
130
131     nsMoonshotSessionState();
132
133     virtual ~nsMoonshotSessionState() {
134         OM_uint32 minor_status;
135         if (mCtx != GSS_C_NO_CONTEXT)
136                 (void)gss_delete_sec_context(&minor_status, &mCtx, GSS_C_NO_BUFFER);
137         mCtx = GSS_C_NO_CONTEXT;
138         mech_oid = GSS_C_NO_OID;
139     }
140
141     NS_IMETHOD Reset() {
142         OM_uint32 minor_status;
143         if (mCtx != GSS_C_NO_CONTEXT)
144                 (void)gss_delete_sec_context(&minor_status, &mCtx, GSS_C_NO_BUFFER);
145         mCtx = GSS_C_NO_CONTEXT;
146         context_state = 0;
147         return NS_OK;
148      }
149      gss_OID GetOID() { return (mech_oid); }
150
151   // TEST
152      int GetCount() { return ++count; }
153
154      gss_ctx_id_t mCtx;
155      int context_state;
156 private:
157      gss_OID mech_oid;
158      int count;
159 };
160
161 nsMoonshotSessionState::nsMoonshotSessionState()
162 {
163         OM_uint32 minstat, majstat;
164         //gss_buffer_desc buffer;
165         gss_OID_set mech_set;
166         //int mech_found = 0;
167         unsigned int i;
168         gss_OID item;
169         
170
171         mCtx = GSS_C_NO_CONTEXT;
172         mech_oid = &gss_krb5_mech_oid_desc;
173         context_state = 0;
174
175         //
176         // Now, look at the list of supported mechanisms,
177         // if SPNEGO is found, then use it.
178         // Otherwise, set the desired mechanism to krb5
179         //
180         // Using Kerberos directly (instead of negotiating
181         // with SPNEGO) may work in some cases depending
182         // on how smart the server side is.
183         //
184
185         // TEST
186         count = 0;
187         LOG(("nsMoonshotSessionState::nsMoonshotSessionState [count=%d]\n", count));
188
189         majstat = gss_indicate_mechs(&minstat, &mech_set);
190         if (GSS_ERROR(majstat))
191            return;
192
193         for (i=0; i<mech_set->count; i++) {
194            item = &mech_set->elements[i];
195            if (item->length == gss_spnego_mech_oid_desc.length &&
196                 !memcmp(item->elements, gss_spnego_mech_oid_desc.elements,
197                         item->length)) {
198               mech_oid = &gss_spnego_mech_oid_desc;
199               break;
200            }
201         }
202         (void) gss_release_oid_set(&minstat, &mech_set);
203 /* HACK: */
204         parse_oid("{1 3 6 1 4 1 5322 22 1 18}", &mech_oid);
205 }
206
207 NS_IMPL_ISUPPORTS0(nsMoonshotSessionState)
208
209 #if 1
210 nsHttpMoonshot::nsHttpMoonshot()
211 {
212    NS_INIT_ISUPPORTS();
213
214 #if defined(PR_LOGGING)
215    if (!gNegotiateLog)
216       gNegotiateLog = PR_NewLogModule("moonshot");
217 #endif /* PR_LOGGING */
218
219 }
220 #endif
221
222 #if 1
223 nsHttpMoonshot::~nsHttpMoonshot()
224 {
225 }
226 #endif
227
228 NS_IMETHODIMP
229 nsHttpMoonshot::GetAuthFlags(PRUint32 *flags)
230 {
231   //
232   // GSSAPI creds should not be reused across multiple requests.
233   // Only perform the negotiation when it is explicitly requested
234   // by the server.  Thus, do *NOT* use the "REUSABLE_CREDENTIALS"
235   // flag here.
236   //
237   *flags = REQUEST_BASED; 
238   return NS_OK;
239 }
240
241 //
242 // Always set *identityInvalid == FALSE here.  This 
243 // will prevent the browser from popping up the authentication
244 // prompt window.  Because GSSAPI does not have an API
245 // for fetching initial credentials (ex: A Kerberos TGT),
246 // there is no correct way to get the users credentials.
247 // 
248 NS_IMETHODIMP
249 nsHttpMoonshot::ChallengeReceived(nsIHttpChannel *httpChannel,
250                                    const char *challenge,
251                                    PRBool isProxyAuth,
252                                    nsISupports **sessionState,
253                                    nsISupports **continuationState,
254                                    PRBool *identityInvalid)
255 {
256     nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
257
258     *identityInvalid = PR_FALSE;
259     //
260     // Use this opportunity to instantiate the session object
261     // that gets used later when we generate the credentials.
262     //
263     if (!session) {
264         session = new nsMoonshotSessionState();
265         if (!session)
266                 return(NS_ERROR_OUT_OF_MEMORY);
267         NS_ADDREF(*sessionState = session);
268         LOG(("nsHttpMoonshot::A new session context established\n"));
269     } else {
270       LOG(("nsHttpMoonshot::Still using context from previous request [ctx=%p]\n", session->mCtx));
271     }
272
273     return NS_OK;
274 }
275
276 #if 0
277 NS_IMPL_ISUPPORTS2(nsHttpMoonshot, nsIHttpAuthenticator,
278                                     nsIHttpAuthenticator_1_9_2)
279 #else
280 NS_IMPL_ISUPPORTS1(nsHttpMoonshot, nsIHttpAuthenticator)
281 #endif
282
283 //
284 // Generate proper GSSAPI error messages from the major and
285 // minor status codes.
286 //
287 void
288 nsHttpMoonshot::LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, char *prefix)
289 {
290    OM_uint32 new_stat;
291    OM_uint32 msg_ctx = 0;
292    gss_buffer_desc status1_string;
293    gss_buffer_desc status2_string;
294    OM_uint32 ret;
295    nsCAutoString error(prefix);
296
297    error += ": ";
298    do {
299       ret = gss_display_status (&new_stat,
300                                maj_stat,
301                                GSS_C_GSS_CODE,
302                                GSS_C_NULL_OID,
303                                &msg_ctx,
304                                &status1_string);
305       error += (char *)status1_string.value;
306       error += "\n";
307       ret = gss_display_status (&new_stat,
308                                min_stat,
309                                GSS_C_MECH_CODE,
310                                GSS_C_NULL_OID,
311                                &msg_ctx,
312                                &status2_string);
313       error += (char *)status2_string.value;
314       error += "\n";
315
316    } while (!GSS_ERROR(ret) && msg_ctx != 0);
317
318    // LOG(("%s", ToNewCString(error)));
319    LOG(("%s\n", error.get()));
320 }
321
322 //
323 // GenerateCredentials
324 //
325 // This routine is responsible for creating the correct authentication
326 // blob to pass to the server that requested "Negotiate" authentication.
327 //
328 NS_IMETHODIMP
329 nsHttpMoonshot::GenerateCredentials(nsIHttpChannel *httpChannel,
330                                      const char *challenge,
331                                      PRBool isProxyAuth,
332                                      const PRUnichar *domain,
333                                      const PRUnichar *user,
334                                      const PRUnichar *password,
335                                      nsISupports **sessionState,
336                                      nsISupports **continuationState,
337                                      char **creds)
338 {
339     LOG(("nsHttpMoonshot::GenerateCredentials [challenge=%s]\n", challenge));
340
341     PRUint32 unused;
342     return GenerateCredentials_1_9_2(httpChannel,
343                                      challenge,
344                                      isProxyAuth,
345                                      domain,
346                                      user,
347                                      password,
348                                      sessionState,
349                                      continuationState,
350                                      &unused,
351                                      creds);
352 }
353
354 NS_IMETHODIMP
355 nsHttpMoonshot::GenerateCredentials_1_9_2(nsIHttpChannel *httpChannel,
356                                                const char *challenge,
357                                                PRBool isProxyAuth,
358                                                const PRUnichar *domain,
359                                                const PRUnichar *username,
360                                                const PRUnichar *password,
361                                                nsISupports **sessionState,
362                                                nsISupports **continuationState,
363                                                PRUint32 *flags,
364                                                char **creds)
365 {
366    OM_uint32 major_status, minor_status;
367    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
368    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
369    gss_buffer_t  in_token_ptr = GSS_C_NO_BUFFER;
370    gss_name_t server;
371    nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
372    gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
373
374
375    nsCOMPtr<nsIURI> uri;
376    nsresult rv;
377    nsCAutoString service;
378    
379    LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
380
381    NS_ENSURE_ARG_POINTER(creds);
382
383    PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
384                 strlen(NEGOTIATE_AUTH));
385    NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
386
387    rv = httpChannel->GetURI(getter_AddRefs(uri));
388    if (NS_FAILED(rv)) return rv;
389
390    rv = uri->GetAsciiHost(service);
391    if (NS_FAILED(rv)) return rv;
392    
393    LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n", 
394        service.get()));
395
396   // TEST
397    LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
398
399    //
400    // The correct service name for IIS servers is "HTTP/f.q.d.n", so
401    // construct the proper service name for passing to "gss_import_name".
402    //
403    // TODO: Possibly make this a configurable service name for use
404    // with non-standard servers that use stuff like "khttp/f.q.d.n" 
405    // instead.
406    //
407 /* DK:   service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
408 #if 0
409    service.Insert("HTTP@", 0);
410 #else
411    service.Insert("host@", 0);
412 #endif
413
414    input_token.value = (void *)service.get();
415    input_token.length = service.Length() + 1;
416
417    major_status = gss_import_name(&minor_status,
418                                  &input_token,
419 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
420                                  GSS_C_NT_HOSTBASED_SERVICE,
421 #else
422                                  gss_nt_service_name,
423 #endif
424                                  &server);
425    input_token.value = NULL;
426    input_token.length = 0;
427    if (GSS_ERROR(major_status)) {
428       LogGssError(major_status, minor_status, "gss_import_name() failed");
429       return NS_ERROR_FAILURE;
430    }
431
432    //
433    // If the "Negotiate:" header had some data associated with it,
434    // that data should be used as the input to this call.  This may
435    // be a continuation of an earlier call because GSSAPI authentication
436    // often takes multiple round-trips to complete depending on the
437    // context flags given.  We want to use MUTUAL_AUTHENTICATION which
438    // generally *does* require multiple round-trips.  Don't assume
439    // auth can be completed in just 1 call.
440    //
441    unsigned int len = strlen(challenge);
442
443    if (len > strlen(NEGOTIATE_AUTH)) {
444         challenge += strlen(NEGOTIATE_AUTH);
445         while (*challenge == ' ') challenge++;
446         len = strlen(challenge);
447
448
449         if(len && (0 == (len & 3)) )
450         {
451          if( (char)'=' == challenge[len-1] )
452          {
453              if( (char)'=' == challenge[len-2] )
454              {
455                  len -= 2;
456              }
457              else
458              {
459                  len -= 1;
460              }
461          }
462      }
463
464
465         input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
466 //        input_token.length = (len * 3)/4;
467         input_token.value = malloc(input_token.length + 1);
468         if (!input_token.value)
469                 return (NS_ERROR_OUT_OF_MEMORY);
470
471         //
472         // Decode the response that followed the "Negotiate" token
473         //
474         if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
475                 free(input_token.value);
476                 return(NS_ERROR_UNEXPECTED);
477         }
478         in_token_ptr = &input_token;
479         LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
480    } else {
481         //
482         // Starting over, clear out any existing context and don't
483         // use an input token.
484         //
485         // TEST
486 /*      if (session->context_state == 2) {
487           *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
488           if (!(*creds)) {
489               return NS_ERROR_OUT_OF_MEMORY;
490           }
491
492           sprintf(*creds, "%s", NEGOTIATE_AUTH);
493           return NS_OK;
494         } else { */
495           session->Reset();
496           in_token_ptr = GSS_C_NO_BUFFER;
497         //}
498    }
499
500    /* HACK */
501    {
502         OM_uint32 maj_stat, min_stat;
503         gss_buffer_desc tmp_token;
504         gss_name_t gss_username = GSS_C_NO_NAME;
505         gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
506
507         tmp_token.value = (void *) "steve@local";
508         tmp_token.length = strlen((const char *)tmp_token.value);
509         maj_stat = gss_import_name(&min_stat, &tmp_token,
510                                    GSS_C_NT_USER_NAME,
511                                    &gss_username);
512
513         if (GSS_ERROR(maj_stat)) {
514             LogGssError(maj_stat, min_stat, "gss_import_name() failed");
515             session->Reset();
516             return NS_ERROR_FAILURE;
517         }
518
519         mechs.elements = session->GetOID();
520         mechs.count = 1;
521         mechsp = &mechs;
522
523         tmp_token.value = (void *)"testing";
524         tmp_token.length = strlen((const char*)tmp_token.value);
525         maj_stat = gss_acquire_cred_with_password(&min_stat,
526                                                   gss_username, &tmp_token, 0,
527                                                   mechsp, GSS_C_INITIATE,
528                                                   &cred, NULL, NULL);
529         if (GSS_ERROR(maj_stat)) {
530             LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
531             session->Reset();
532             return NS_ERROR_FAILURE;
533         }
534    }
535
536    major_status = gss_init_sec_context(&minor_status,
537                                     cred,
538                                     &session->mCtx,
539                                     server,
540                                     session->GetOID(),
541                                     GSS_C_MUTUAL_FLAG,
542                                     /* GSS_C_INDEFINITE */ 0,
543                                     GSS_C_NO_CHANNEL_BINDINGS,
544                                     in_token_ptr,
545                                     nsnull,
546                                     &output_token,
547                                     nsnull,
548                                     nsnull);
549
550    if (GSS_ERROR(major_status)) {
551       LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
552       (void) gss_release_name(&minor_status, &server);
553       gss_release_cred(&minor_status, &cred);
554       session->Reset();
555       if (input_token.length > 0 && input_token.value != NULL)
556               (void) gss_release_buffer(&minor_status, &input_token);
557       return NS_ERROR_FAILURE;
558    }
559
560    if (major_status == GSS_S_COMPLETE) {
561         //
562         // We are done with this authentication, reset the context. 
563         //
564         // TEST
565         // session->Reset();
566         session->context_state = 2;
567         LOG(("GSS Auth done"));
568    } else if (major_status == GSS_S_CONTINUE_NEEDED) {
569         //
570         // We could create a continuation state, but its not
571         // really necessary.
572         //
573         // The important thing is that we do NOT reset the
574         // session context here because it will be needed on the
575         // next call.
576         //
577         // TEST
578         session->context_state = 1;
579         LOG(("GSS Auth continuing"));
580    } 
581
582    // We don't need the input token data anymore.
583    if (input_token.length > 0 && input_token.value != NULL)
584         (void) gss_release_buffer(&minor_status, &input_token);
585
586    if (output_token.length == 0) {
587       LOG(("No GSS output token to send, exiting"));
588       (void) gss_release_name(&minor_status, &server);
589       gss_release_cred(&minor_status, &cred);
590       return NS_ERROR_FAILURE;
591    }
592
593    //
594    // The token output from the gss_init_sec_context call is
595    // encoded and used as the Authentication response for the
596    // server.
597    //
598    char *encoded_token = PL_Base64Encode((char *)output_token.value,
599                                         output_token.length,
600                                         nsnull);
601    if (!encoded_token) {
602       (void) gss_release_buffer(&minor_status, &output_token);
603       (void) gss_release_name(&minor_status, &server);
604       gss_release_cred(&minor_status, &cred);
605       return NS_ERROR_OUT_OF_MEMORY;
606    }
607
608    LOG(("Sending a token of length %d\n", output_token.length));
609
610    // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
611    *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
612    if (!(*creds)) {
613       PR_Free(encoded_token);
614       (void) gss_release_buffer(&minor_status, &output_token);
615       (void) gss_release_name(&minor_status, &server);
616       gss_release_cred(&minor_status, &cred);
617       return NS_ERROR_OUT_OF_MEMORY;
618    }
619
620    sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
621    PR_Free(encoded_token);
622
623    (void) gss_release_buffer(&minor_status, &output_token);
624    (void) gss_release_name(&minor_status, &server);
625       gss_release_cred(&minor_status, &cred);
626
627    LOG(("returning the call"));
628
629    return NS_OK;
630 }
631
632 #if 0
633 static NS_METHOD
634 nsMoonshotConstructor(nsISupports *outer, REFNSIID iid, void **result)
635 {
636     if (outer)
637         return NS_ERROR_NO_AGGREGATION;
638 }
639 #endif