- Logging
[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         LOG(("nsHttpMoonshot::A new session context established\n"));
178     } else {
179         LOG(("nsHttpMoonshot::Still using context from previous request\n"));
180     }
181
182     LOG(("nsHttpMoonshot:: gss_state = %d\n", session->gss_state));
183
184     *identityInvalid =
185         (session->gss_state == GSS_CTX_EMPTY) ? PR_TRUE : PR_FALSE;
186
187     return NS_OK;
188 }
189
190 #if 0
191 NS_IMPL_ISUPPORTS2(nsHttpMoonshot, nsIHttpAuthenticator,
192                                     nsIHttpAuthenticator_1_9_2)
193 #else
194 NS_IMPL_ISUPPORTS1(nsHttpMoonshot, nsIHttpAuthenticator)
195 #endif
196
197 //
198 // Generate proper GSSAPI error messages from the major and
199 // minor status codes.
200 //
201 void
202 nsHttpMoonshot::LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, char *prefix)
203 {
204    OM_uint32 new_stat;
205    OM_uint32 msg_ctx = 0;
206    gss_buffer_desc status1_string;
207    gss_buffer_desc status2_string;
208    OM_uint32 ret;
209    nsCAutoString error(prefix);
210
211    error += ": ";
212    do {
213       ret = gss_display_status (&new_stat,
214                                maj_stat,
215                                GSS_C_GSS_CODE,
216                                GSS_C_NULL_OID,
217                                &msg_ctx,
218                                &status1_string);
219       error += (char *)status1_string.value;
220       error += "\n";
221       ret = gss_display_status (&new_stat,
222                                min_stat,
223                                GSS_C_MECH_CODE,
224                                GSS_C_NULL_OID,
225                                &msg_ctx,
226                                &status2_string);
227       error += (char *)status2_string.value;
228       error += "\n";
229
230    } while (!GSS_ERROR(ret) && msg_ctx != 0);
231
232    // LOG(("%s", ToNewCString(error)));
233    LOG(("%s\n", error.get()));
234 }
235
236 //
237 // GenerateCredentials
238 //
239 // This routine is responsible for creating the correct authentication
240 // blob to pass to the server that requested "Negotiate" authentication.
241 //
242 NS_IMETHODIMP
243 nsHttpMoonshot::GenerateCredentials(nsIHttpChannel *httpChannel,
244                                      const char *challenge,
245                                      PRBool isProxyAuth,
246                                      const PRUnichar *domain,
247                                      const PRUnichar *user,
248                                      const PRUnichar *password,
249                                      nsISupports **sessionState,
250                                      nsISupports **continuationState,
251                                      char **creds)
252 {
253     LOG(("nsHttpMoonshot::GenerateCredentials [challenge=%s]\n", challenge));
254
255     PRUint32 unused;
256     return GenerateCredentials_1_9_2(httpChannel,
257                                      challenge,
258                                      isProxyAuth,
259                                      domain,
260                                      user,
261                                      password,
262                                      sessionState,
263                                      continuationState,
264                                      &unused,
265                                      creds);
266 }
267
268 NS_IMETHODIMP
269 nsHttpMoonshot::GenerateCredentials_1_9_2(nsIHttpChannel *httpChannel,
270                                                const char *challenge,
271                                                PRBool isProxyAuth,
272                                                const PRUnichar *domain,
273                                                const PRUnichar *username,
274                                                const PRUnichar *password,
275                                                nsISupports **sessionState,
276                                                nsISupports **continuationState,
277                                                PRUint32 *flags,
278                                                char **creds)
279 {
280    OM_uint32 major_status, minor_status;
281    gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
282    gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
283    gss_buffer_t  in_token_ptr = GSS_C_NO_BUFFER;
284    gss_name_t server;
285    nsMoonshotSessionState *session = (nsMoonshotSessionState *) *sessionState;
286
287
288    nsCOMPtr<nsIURI> uri;
289    nsresult rv;
290    nsCAutoString service;
291    
292    LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
293
294    NS_ENSURE_ARG_POINTER(creds);
295
296    PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
297                 strlen(NEGOTIATE_AUTH));
298    NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
299
300    rv = httpChannel->GetURI(getter_AddRefs(uri));
301    if (NS_FAILED(rv)) return rv;
302
303    rv = uri->GetAsciiHost(service);
304    if (NS_FAILED(rv)) return rv;
305    
306    LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n", 
307        service.get()));
308
309   // TEST
310 //   LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
311
312    //
313    // The correct service name for IIS servers is "HTTP/f.q.d.n", so
314    // construct the proper service name for passing to "gss_import_name".
315    //
316    // TODO: Possibly make this a configurable service name for use
317    // with non-standard servers that use stuff like "khttp/f.q.d.n" 
318    // instead.
319    //
320 /* DK:   service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
321 #if 0
322    service.Insert("HTTP@", 0);
323 #else
324    service.Insert("host@", 0);
325 #endif
326
327    input_token.value = (void *)service.get();
328    input_token.length = service.Length() + 1;
329
330    major_status = gss_import_name(&minor_status,
331                                  &input_token,
332 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
333                                  GSS_C_NT_HOSTBASED_SERVICE,
334 #else
335                                  gss_nt_service_name,
336 #endif
337                                  &server);
338    input_token.value = NULL;
339    input_token.length = 0;
340    if (GSS_ERROR(major_status)) {
341       LogGssError(major_status, minor_status, "gss_import_name() failed");
342       return NS_ERROR_FAILURE;
343    }
344
345    //
346    // If the "Negotiate:" header had some data associated with it,
347    // that data should be used as the input to this call.  This may
348    // be a continuation of an earlier call because GSSAPI authentication
349    // often takes multiple round-trips to complete depending on the
350    // context flags given.  We want to use MUTUAL_AUTHENTICATION which
351    // generally *does* require multiple round-trips.  Don't assume
352    // auth can be completed in just 1 call.
353    //
354    unsigned int len = strlen(challenge);
355
356    if (len > strlen(NEGOTIATE_AUTH)) {
357         challenge += strlen(NEGOTIATE_AUTH);
358         while (*challenge == ' ') challenge++;
359         len = strlen(challenge);
360
361
362         if(len && (0 == (len & 3)) )
363         {
364          if( (char)'=' == challenge[len-1] )
365          {
366              if( (char)'=' == challenge[len-2] )
367              {
368                  len -= 2;
369              }
370              else
371              {
372                  len -= 1;
373              }
374          }
375      }
376
377
378         input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
379 //        input_token.length = (len * 3)/4;
380         input_token.value = malloc(input_token.length + 1);
381         if (!input_token.value)
382                 return (NS_ERROR_OUT_OF_MEMORY);
383
384         //
385         // Decode the response that followed the "Negotiate" token
386         //
387         if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
388                 free(input_token.value);
389                 return(NS_ERROR_UNEXPECTED);
390         }
391         in_token_ptr = &input_token;
392         LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
393    } else {
394         //
395         // Starting over, clear out any existing context and don't
396         // use an input token.
397         //
398         // TEST
399 /*      if (session->context_state == 2) {
400           *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
401           if (!(*creds)) {
402               return NS_ERROR_OUT_OF_MEMORY;
403           }
404
405           sprintf(*creds, "%s", NEGOTIATE_AUTH);
406           return NS_OK;
407         } else { */
408           session->Reset();
409           in_token_ptr = GSS_C_NO_BUFFER;
410         //}
411    }
412
413    if (session->gss_cred == GSS_C_NO_CREDENTIAL)
414    {
415         OM_uint32 maj_stat, min_stat;
416         gss_buffer_desc tmp_token;
417         gss_name_t gss_username = GSS_C_NO_NAME;
418         gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
419         const char *p, *u;
420
421         u = strdup(NS_LossyConvertUTF16toASCII(username).get());
422         p = strdup(NS_LossyConvertUTF16toASCII(password).get());
423
424         LOG(("Acquiring credentials for user '%s' using password '%s'\n",
425                                  u, p));
426
427         tmp_token.value = (void *) u;
428         tmp_token.length = strlen((const char *)tmp_token.value);
429         maj_stat = gss_import_name(&min_stat, &tmp_token,
430                                    GSS_C_NT_USER_NAME,
431                                    &gss_username);
432
433         if (GSS_ERROR(maj_stat)) {
434             LogGssError(maj_stat, min_stat, "gss_import_name() failed");
435             session->Reset();
436             return NS_ERROR_FAILURE;
437         }
438
439         mechs.elements = GetOID();
440         mechs.count = 1;
441         mechsp = &mechs;
442
443         tmp_token.value = (void *) p;
444         tmp_token.length = strlen(p);//strlen((const char*)tmp_token.value);
445         maj_stat = gss_acquire_cred_with_password(&min_stat,
446                                                   gss_username, &tmp_token, 0,
447                                                   mechsp, GSS_C_INITIATE,
448                                                   &session->gss_cred, NULL, NULL);
449         if (GSS_ERROR(maj_stat)) {
450             LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
451             session->Reset();
452             return NS_ERROR_FAILURE;
453         }
454
455         LOG(("Acquired credential for user '%s' using password '%s'\n",
456              u, p));
457    }
458
459    major_status = gss_init_sec_context(&minor_status,
460                                     session->gss_cred,
461                                     &session->gss_ctx,
462                                     server,
463                                     GetOID(),
464                                     GSS_C_MUTUAL_FLAG,
465                                     /* GSS_C_INDEFINITE */ 0,
466                                     GSS_C_NO_CHANNEL_BINDINGS,
467                                     in_token_ptr,
468                                     nsnull,
469                                     &output_token,
470                                     nsnull,
471                                     nsnull);
472
473    if (GSS_ERROR(major_status)) {
474       LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
475       (void) gss_release_name(&minor_status, &server);
476 //      gss_release_cred(&minor_status, &cred);
477       session->Reset();
478       if (input_token.length > 0 && input_token.value != NULL)
479               (void) gss_release_buffer(&minor_status, &input_token);
480       return NS_ERROR_FAILURE;
481    }
482
483    if (major_status == GSS_S_COMPLETE) {
484         //
485         // We are done with this authentication, reset the context. 
486         //
487         // TEST
488         // session->Reset();
489         session->gss_state = GSS_CTX_ESTABLISHED;
490         LOG(("GSS Auth done"));
491    } else if (major_status == GSS_S_CONTINUE_NEEDED) {
492         //
493         // We could create a continuation state, but its not
494         // really necessary.
495         //
496         // The important thing is that we do NOT reset the
497         // session context here because it will be needed on the
498         // next call.
499         //
500         // TEST
501         session->gss_state = GSS_CTX_IN_PROGRESS;
502         LOG(("GSS Auth continuing"));
503    } 
504
505    // We don't need the input token data anymore.
506    if (input_token.length > 0 && input_token.value != NULL)
507         (void) gss_release_buffer(&minor_status, &input_token);
508
509    if (output_token.length == 0) {
510       LOG(("No GSS output token to send, exiting"));
511       (void) gss_release_name(&minor_status, &server);
512 //      gss_release_cred(&minor_status, &cred);
513       return NS_ERROR_FAILURE;
514    }
515
516    //
517    // The token output from the gss_init_sec_context call is
518    // encoded and used as the Authentication response for the
519    // server.
520    //
521    char *encoded_token = PL_Base64Encode((char *)output_token.value,
522                                         output_token.length,
523                                         nsnull);
524    if (!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    LOG(("Sending a token of length %d\n", output_token.length));
532
533    // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
534    *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
535    if (!(*creds)) {
536       PR_Free(encoded_token);
537       (void) gss_release_buffer(&minor_status, &output_token);
538       (void) gss_release_name(&minor_status, &server);
539 //      gss_release_cred(&minor_status, &cred);
540       return NS_ERROR_OUT_OF_MEMORY;
541    }
542
543    sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
544    PR_Free(encoded_token);
545
546    (void) gss_release_buffer(&minor_status, &output_token);
547    (void) gss_release_name(&minor_status, &server);
548 //      gss_release_cred(&minor_status, &cred);
549
550    LOG(("returning the call"));
551
552    return NS_OK;
553 }