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