Acquire credentials based on users' input
[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
284
285    nsCOMPtr<nsIURI> uri;
286    nsresult rv;
287    nsCAutoString service;
288    
289    LOG(("nsHttpMoonshot::GenerateCredentials() [challenge=%s]\n", challenge));
290
291    NS_ENSURE_ARG_POINTER(creds);
292
293    PRBool isGssapiAuth = !PL_strncasecmp(challenge, NEGOTIATE_AUTH,
294                 strlen(NEGOTIATE_AUTH));
295    NS_ENSURE_TRUE(isGssapiAuth, NS_ERROR_UNEXPECTED);
296
297    rv = httpChannel->GetURI(getter_AddRefs(uri));
298    if (NS_FAILED(rv)) return rv;
299
300    rv = uri->GetAsciiHost(service);
301    if (NS_FAILED(rv)) return rv;
302    
303    LOG(("nsHttpMoonshot::GenerateCredentials() : hostname = %s\n", 
304        service.get()));
305
306   // TEST
307 //   LOG(("nsHttpMoonshot::Count [count=%d]\n", session->GetCount()));
308
309    //
310    // The correct service name for IIS servers is "HTTP/f.q.d.n", so
311    // construct the proper service name for passing to "gss_import_name".
312    //
313    // TODO: Possibly make this a configurable service name for use
314    // with non-standard servers that use stuff like "khttp/f.q.d.n" 
315    // instead.
316    //
317 /* DK:   service.Insert(NS_LITERAL_CSTRING("HTTP@"), 0); */
318 #if 0
319    service.Insert("HTTP@", 0);
320 #else
321    service.Insert("host@", 0);
322 #endif
323
324    input_token.value = (void *)service.get();
325    input_token.length = service.Length() + 1;
326
327    major_status = gss_import_name(&minor_status,
328                                  &input_token,
329 #ifdef HAVE_GSS_C_NT_HOSTBASED_SERVICE
330                                  GSS_C_NT_HOSTBASED_SERVICE,
331 #else
332                                  gss_nt_service_name,
333 #endif
334                                  &server);
335    input_token.value = NULL;
336    input_token.length = 0;
337    if (GSS_ERROR(major_status)) {
338       LogGssError(major_status, minor_status, "gss_import_name() failed");
339       return NS_ERROR_FAILURE;
340    }
341
342    //
343    // If the "Negotiate:" header had some data associated with it,
344    // that data should be used as the input to this call.  This may
345    // be a continuation of an earlier call because GSSAPI authentication
346    // often takes multiple round-trips to complete depending on the
347    // context flags given.  We want to use MUTUAL_AUTHENTICATION which
348    // generally *does* require multiple round-trips.  Don't assume
349    // auth can be completed in just 1 call.
350    //
351    unsigned int len = strlen(challenge);
352
353    if (len > strlen(NEGOTIATE_AUTH)) {
354         challenge += strlen(NEGOTIATE_AUTH);
355         while (*challenge == ' ') challenge++;
356         len = strlen(challenge);
357
358
359         if(len && (0 == (len & 3)) )
360         {
361          if( (char)'=' == challenge[len-1] )
362          {
363              if( (char)'=' == challenge[len-2] )
364              {
365                  len -= 2;
366              }
367              else
368              {
369                  len -= 1;
370              }
371          }
372      }
373
374
375         input_token.length = (len / 4) * 3 + ((len % 4) * 3) / 4;
376 //        input_token.length = (len * 3)/4;
377         input_token.value = malloc(input_token.length + 1);
378         if (!input_token.value)
379                 return (NS_ERROR_OUT_OF_MEMORY);
380
381         //
382         // Decode the response that followed the "Negotiate" token
383         //
384         if (PL_Base64Decode(challenge, len, (char *) input_token.value) == NULL) {
385                 free(input_token.value);
386                 return(NS_ERROR_UNEXPECTED);
387         }
388         in_token_ptr = &input_token;
389         LOG(("nsHttpMoonshot::GenerateCredentials() : Received GSS token of length %d\n", input_token.length));
390    } else {
391         //
392         // Starting over, clear out any existing context and don't
393         // use an input token.
394         //
395         // TEST
396 /*      if (session->context_state == 2) {
397           *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1);
398           if (!(*creds)) {
399               return NS_ERROR_OUT_OF_MEMORY;
400           }
401
402           sprintf(*creds, "%s", NEGOTIATE_AUTH);
403           return NS_OK;
404         } else { */
405           session->Reset();
406           in_token_ptr = GSS_C_NO_BUFFER;
407         //}
408    }
409
410    if (session->gss_cred == GSS_C_NO_CREDENTIAL)
411    {
412         OM_uint32 maj_stat, min_stat;
413         gss_buffer_desc tmp_token;
414         gss_name_t gss_username = GSS_C_NO_NAME;
415         gss_OID_set_desc mechs, *mechsp = GSS_C_NO_OID_SET;
416         const char *p, *u;
417
418         u = strdup(NS_LossyConvertUTF16toASCII(username).get());
419         p = strdup(NS_LossyConvertUTF16toASCII(password).get());
420
421         tmp_token.value = (void *) u;
422         tmp_token.length = strlen((const char *)tmp_token.value);
423         maj_stat = gss_import_name(&min_stat, &tmp_token,
424                                    GSS_C_NT_USER_NAME,
425                                    &gss_username);
426
427         if (GSS_ERROR(maj_stat)) {
428             LogGssError(maj_stat, min_stat, "gss_import_name() failed");
429             session->Reset();
430             return NS_ERROR_FAILURE;
431         }
432
433         mechs.elements = GetOID();
434         mechs.count = 1;
435         mechsp = &mechs;
436
437         tmp_token.value = (void *) p;
438         tmp_token.length = strlen(p);//strlen((const char*)tmp_token.value);
439         maj_stat = gss_acquire_cred_with_password(&min_stat,
440                                                   gss_username, &tmp_token, 0,
441                                                   mechsp, GSS_C_INITIATE,
442                                                   &session->gss_cred, NULL, NULL);
443         if (GSS_ERROR(maj_stat)) {
444             LogGssError(maj_stat, min_stat, "gss_acquire_cred_with_password()");
445             session->Reset();
446             return NS_ERROR_FAILURE;
447         }
448
449         LOG(("Acquired credential for user '%s' using password '%s'\n",
450              u, p));
451    }
452
453    major_status = gss_init_sec_context(&minor_status,
454                                     session->gss_cred,
455                                     &session->gss_ctx,
456                                     server,
457                                     GetOID(),
458                                     GSS_C_MUTUAL_FLAG,
459                                     /* GSS_C_INDEFINITE */ 0,
460                                     GSS_C_NO_CHANNEL_BINDINGS,
461                                     in_token_ptr,
462                                     nsnull,
463                                     &output_token,
464                                     nsnull,
465                                     nsnull);
466
467    if (GSS_ERROR(major_status)) {
468       LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
469       (void) gss_release_name(&minor_status, &server);
470 //      gss_release_cred(&minor_status, &cred);
471       session->Reset();
472       if (input_token.length > 0 && input_token.value != NULL)
473               (void) gss_release_buffer(&minor_status, &input_token);
474       return NS_ERROR_FAILURE;
475    }
476
477    if (major_status == GSS_S_COMPLETE) {
478         //
479         // We are done with this authentication, reset the context. 
480         //
481         // TEST
482         // session->Reset();
483         session->gss_state = GSS_CTX_ESTABLISHED;
484         LOG(("GSS Auth done"));
485    } else if (major_status == GSS_S_CONTINUE_NEEDED) {
486         //
487         // We could create a continuation state, but its not
488         // really necessary.
489         //
490         // The important thing is that we do NOT reset the
491         // session context here because it will be needed on the
492         // next call.
493         //
494         // TEST
495         session->gss_state = GSS_CTX_IN_PROGRESS;
496         LOG(("GSS Auth continuing"));
497    } 
498
499    // We don't need the input token data anymore.
500    if (input_token.length > 0 && input_token.value != NULL)
501         (void) gss_release_buffer(&minor_status, &input_token);
502
503    if (output_token.length == 0) {
504       LOG(("No GSS output token to send, exiting"));
505       (void) gss_release_name(&minor_status, &server);
506 //      gss_release_cred(&minor_status, &cred);
507       return NS_ERROR_FAILURE;
508    }
509
510    //
511    // The token output from the gss_init_sec_context call is
512    // encoded and used as the Authentication response for the
513    // server.
514    //
515    char *encoded_token = PL_Base64Encode((char *)output_token.value,
516                                         output_token.length,
517                                         nsnull);
518    if (!encoded_token) {
519       (void) gss_release_buffer(&minor_status, &output_token);
520       (void) gss_release_name(&minor_status, &server);
521 //      gss_release_cred(&minor_status, &cred);
522       return NS_ERROR_OUT_OF_MEMORY;
523    }
524
525    LOG(("Sending a token of length %d\n", output_token.length));
526
527    // allocate a buffer sizeof("Negotiate" + " " + b64output_token + "\0")
528    *creds = (char *) malloc (strlen(NEGOTIATE_AUTH) + 1 + strlen(encoded_token) + 1);
529    if (!(*creds)) {
530       PR_Free(encoded_token);
531       (void) gss_release_buffer(&minor_status, &output_token);
532       (void) gss_release_name(&minor_status, &server);
533 //      gss_release_cred(&minor_status, &cred);
534       return NS_ERROR_OUT_OF_MEMORY;
535    }
536
537    sprintf(*creds, "%s %s", NEGOTIATE_AUTH, encoded_token);
538    PR_Free(encoded_token);
539
540    (void) gss_release_buffer(&minor_status, &output_token);
541    (void) gss_release_name(&minor_status, &server);
542 //      gss_release_cred(&minor_status, &cred);
543
544    LOG(("returning the call"));
545
546    return NS_OK;
547 }