Back to index

lightning-sunbird  0.9+nobinonly
nsNegotiateAuthGSSAPI.cpp
Go to the documentation of this file.
00001 /* vim:set ts=4 sw=4 sts=4 et cindent: */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is the Negotiateauth
00016  *
00017  * The Initial Developer of the Original Code is Daniel Kouril.
00018  * Portions created by the Initial Developer are Copyright (C) 2003
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *   Daniel Kouril <kouril@ics.muni.cz> (original author)
00023  *   Wyllys Ingersoll <wyllys.ingersoll@sun.com>
00024  *   Christopher Nebergall <cneberg@sandia.gov>
00025  *   Darin Fisher <darin@meer.net>
00026  *   Mark Mentovai <mark@moxienet.com>
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either the GNU General Public License Version 2 or later (the "GPL"), or
00030  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 //
00043 // GSSAPI Authentication Support Module
00044 //
00045 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
00046 // (formerly draft-brezak-spnego-http-04.txt)
00047 //
00048 // Also described here:
00049 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
00050 //
00051 //
00052 
00053 #include "prlink.h"
00054 #include "nsCOMPtr.h"
00055 #include "nsIPrefService.h"
00056 #include "nsIPrefBranch.h"
00057 #include "nsIServiceManager.h"
00058 
00059 #include "nsNegotiateAuth.h"
00060 #include "nsNegotiateAuthGSSAPI.h"
00061 
00062 #ifdef XP_MACOSX
00063 #include <Kerberos/Kerberos.h>
00064 #endif
00065 
00066 // function pointers for gss functions
00067 //
00068 typedef OM_uint32 (*gss_display_status_type)(
00069     OM_uint32 *,
00070     OM_uint32,
00071     int,
00072     gss_OID,
00073     OM_uint32 *,
00074     gss_buffer_t);
00075 
00076 typedef OM_uint32 (*gss_init_sec_context_type)(
00077     OM_uint32 *,
00078     gss_cred_id_t,
00079     gss_ctx_id_t *,
00080     gss_name_t,
00081     gss_OID,
00082     OM_uint32,
00083     OM_uint32,
00084     gss_channel_bindings_t,
00085     gss_buffer_t,
00086     gss_OID *,
00087     gss_buffer_t,
00088     OM_uint32 *,
00089     OM_uint32 *);
00090      
00091 typedef OM_uint32 (*gss_indicate_mechs_type)(
00092     OM_uint32 *,
00093     gss_OID_set *);
00094 
00095 typedef OM_uint32 (*gss_release_oid_set_type)(
00096     OM_uint32 *,
00097     gss_OID_set *);
00098 
00099 typedef OM_uint32 (*gss_delete_sec_context_type)(
00100     OM_uint32 *,
00101     gss_ctx_id_t *,
00102     gss_buffer_t);
00103 
00104 typedef OM_uint32 (*gss_import_name_type)(
00105     OM_uint32 *,
00106     gss_buffer_t,
00107     gss_OID,
00108     gss_name_t *);
00109 
00110 typedef OM_uint32 (*gss_release_buffer_type)(
00111     OM_uint32 *,
00112     gss_buffer_t);
00113 
00114 typedef OM_uint32 (*gss_release_name_type)(
00115     OM_uint32 *, 
00116     gss_name_t *);
00117 
00118 #ifdef XP_MACOSX
00119 typedef KLStatus (*KLCacheHasValidTickets_type)(
00120     KLPrincipal,
00121     KLKerberosVersion,
00122     KLBoolean *,
00123     KLPrincipal *,
00124     char **);
00125 #endif
00126 
00127 //-----------------------------------------------------------------------------
00128 
00129 // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced
00130 // by by a different name depending on the implementation of gss but always
00131 // has the same value
00132 
00133 static gss_OID_desc gss_c_nt_hostbased_service = 
00134     { 10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04" };
00135 
00136 static const char kNegotiateAuthGssLib[] =
00137     "network.negotiate-auth.gsslib";
00138 static const char kNegotiateAuthNativeImp[] = 
00139    "network.negotiate-auth.using-native-gsslib";
00140 
00141 static const char *gssFuncStr[] = {
00142     "gss_display_status", 
00143     "gss_init_sec_context", 
00144     "gss_indicate_mechs",
00145     "gss_release_oid_set",
00146     "gss_delete_sec_context",
00147     "gss_import_name",
00148     "gss_release_buffer",
00149     "gss_release_name"
00150 };
00151 
00152 #define gssFuncItems NS_ARRAY_LENGTH(gssFuncStr)
00153 
00154 static PRFuncPtr gssFunPtr[gssFuncItems]; 
00155 static PRBool    gssNativeImp = PR_TRUE;
00156 static PRBool    gssFunInit = PR_FALSE;
00157 
00158 #define gss_display_status_ptr      ((gss_display_status_type)*gssFunPtr[0])
00159 #define gss_init_sec_context_ptr    ((gss_init_sec_context_type)*gssFunPtr[1])
00160 #define gss_indicate_mechs_ptr      ((gss_indicate_mechs_type)*gssFunPtr[2])
00161 #define gss_release_oid_set_ptr     ((gss_release_oid_set_type)*gssFunPtr[3])
00162 #define gss_delete_sec_context_ptr  ((gss_delete_sec_context_type)*gssFunPtr[4])
00163 #define gss_import_name_ptr         ((gss_import_name_type)*gssFunPtr[5])
00164 #define gss_release_buffer_ptr      ((gss_release_buffer_type)*gssFunPtr[6])
00165 #define gss_release_name_ptr        ((gss_release_name_type)*gssFunPtr[7])
00166 
00167 #ifdef XP_MACOSX
00168 static PRFuncPtr KLCacheHasValidTicketsPtr;
00169 #define KLCacheHasValidTickets_ptr \
00170         ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
00171 #endif
00172 
00173 static nsresult
00174 gssInit()
00175 {
00176     nsXPIDLCString libPath;
00177     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00178     if (prefs) {
00179         prefs->GetCharPref(kNegotiateAuthGssLib, getter_Copies(libPath)); 
00180         prefs->GetBoolPref(kNegotiateAuthNativeImp, &gssNativeImp); 
00181     }
00182 
00183     PRLibrary *lib = NULL;
00184 
00185     if (!libPath.IsEmpty()) {
00186         LOG(("Attempting to load user specified library [%s]\n", libPath.get()));
00187         gssNativeImp = PR_FALSE;
00188         lib = PR_LoadLibrary(libPath.get());
00189     }
00190     else {
00191         const char *const libNames[] = {
00192             "gss",
00193             "gssapi_krb5",
00194             "gssapi"
00195         };
00196 
00197         for (size_t i = 0; i < NS_ARRAY_LENGTH(libNames) && !lib; ++i) {
00198             char *libName = PR_GetLibraryName(NULL, libNames[i]);
00199             if (libName) {
00200                 lib = PR_LoadLibrary(libName);
00201                 PR_FreeLibraryName(libName);
00202             }
00203         }
00204     }
00205 
00206     if (!lib) {
00207         LOG(("Fail to load gssapi library\n"));
00208         return NS_ERROR_FAILURE;
00209     }
00210 
00211     LOG(("Attempting to load gss functions\n"));
00212 
00213     for (size_t i = 0; i < gssFuncItems; ++i) {
00214         gssFunPtr[i] = PR_FindFunctionSymbol(lib, gssFuncStr[i]);
00215         if (!gssFunPtr[i]) {
00216             LOG(("Fail to load %s function from gssapi library\n", gssFuncStr[i]));
00217             PR_UnloadLibrary(lib);
00218             return NS_ERROR_FAILURE;
00219         }
00220     }
00221 #ifdef XP_MACOSX
00222     if (gssNativeImp &&
00223             !(KLCacheHasValidTicketsPtr =
00224                PR_FindFunctionSymbol(lib, "KLCacheHasValidTickets"))) {
00225         LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n"));
00226         PR_UnloadLibrary(lib);
00227         return NS_ERROR_FAILURE;
00228     }
00229 #endif
00230 
00231     gssFunInit = PR_TRUE;
00232     return NS_OK;
00233 }
00234 
00235 #if defined( PR_LOGGING )
00236 
00237 // Generate proper GSSAPI error messages from the major and
00238 // minor status codes.
00239 void
00240 LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, const char *prefix)
00241 {
00242     OM_uint32 new_stat;
00243     OM_uint32 msg_ctx = 0;
00244     gss_buffer_desc status1_string;
00245     gss_buffer_desc status2_string;
00246     OM_uint32 ret;
00247     nsCAutoString error(prefix);
00248 
00249     if (!gssFunInit)
00250         return;
00251 
00252     error += ": ";
00253     do {
00254         ret = gss_display_status_ptr(&new_stat,
00255                                      maj_stat,
00256                                      GSS_C_GSS_CODE,
00257                                      GSS_C_NULL_OID,
00258                                      &msg_ctx,
00259                                      &status1_string);
00260         error += (const char *) status1_string.value;
00261         error += '\n';
00262         ret = gss_display_status_ptr(&new_stat,
00263                                      min_stat,
00264                                      GSS_C_MECH_CODE,
00265                                      GSS_C_NULL_OID,
00266                                      &msg_ctx,
00267                                      &status2_string);
00268         error += (const char *) status2_string.value;
00269         error += '\n';
00270     } while (!GSS_ERROR(ret) && msg_ctx != 0);
00271 
00272     LOG(("%s\n", error.get()));
00273 }
00274 
00275 #else /* PR_LOGGING */
00276 
00277 #define LogGssError(x,y,z)
00278 
00279 #endif /* PR_LOGGING */
00280 
00281 //-----------------------------------------------------------------------------
00282 
00283 nsNegotiateAuth::nsNegotiateAuth()
00284     : mServiceFlags(REQ_DEFAULT)
00285 {
00286     OM_uint32 minstat, majstat;
00287     gss_OID_set mech_set;
00288     gss_OID item;
00289     unsigned int i;
00290     static gss_OID_desc gss_krb5_mech_oid_desc =
00291         { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
00292     static gss_OID_desc gss_spnego_mech_oid_desc =
00293         { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
00294 
00295     LOG(("entering nsNegotiateAuth::nsNegotiateAuth()\n"));
00296 
00297     if (!gssFunInit && NS_FAILED(gssInit()))
00298         return;
00299 
00300     mCtx = GSS_C_NO_CONTEXT;
00301     mMechOID = &gss_krb5_mech_oid_desc;
00302 
00303     //
00304     // Now, look at the list of supported mechanisms,
00305     // if SPNEGO is found, then use it.
00306     // Otherwise, set the desired mechanism to
00307     // GSS_C_NO_OID and let the system try to use
00308     // the default mechanism.
00309     //
00310     // Using Kerberos directly (instead of negotiating
00311     // with SPNEGO) may work in some cases depending
00312     // on how smart the server side is.
00313     //
00314     majstat = gss_indicate_mechs_ptr(&minstat, &mech_set);
00315     if (GSS_ERROR(majstat))
00316         return;
00317 
00318     for (i=0; i<mech_set->count; i++) {
00319         item = &mech_set->elements[i];    
00320         if (item->length == gss_spnego_mech_oid_desc.length &&
00321             !memcmp(item->elements, gss_spnego_mech_oid_desc.elements,
00322             item->length)) {
00323             // ok, we found it
00324             mMechOID = &gss_spnego_mech_oid_desc;
00325             break;
00326         }
00327     }
00328     gss_release_oid_set_ptr(&minstat, &mech_set);
00329 }
00330 
00331 void
00332 nsNegotiateAuth::Reset()
00333 {
00334     if (gssFunInit && mCtx != GSS_C_NO_CONTEXT) {
00335         OM_uint32 minor_status;
00336         gss_delete_sec_context_ptr(&minor_status, &mCtx, GSS_C_NO_BUFFER);
00337     }
00338     mCtx = GSS_C_NO_CONTEXT;
00339 }
00340 
00341 NS_IMPL_ISUPPORTS1(nsNegotiateAuth, nsIAuthModule)
00342 
00343 NS_IMETHODIMP
00344 nsNegotiateAuth::Init(const char *serviceName,
00345                       PRUint32    serviceFlags,
00346                       const PRUnichar *domain,
00347                       const PRUnichar *username,
00348                       const PRUnichar *password)
00349 {
00350     // we don't expect to be passed any user credentials
00351     NS_ASSERTION(!domain && !username && !password, "unexpected credentials");
00352 
00353     // it's critial that the caller supply a service name to be used
00354     NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
00355 
00356     LOG(("entering nsNegotiateAuth::Init()\n"));
00357 
00358     if (!gssFunInit)
00359        return NS_ERROR_NOT_INITIALIZED;
00360 
00361     mServiceName = serviceName;
00362     mServiceFlags = serviceFlags;
00363     return NS_OK;
00364 }
00365 
00366 NS_IMETHODIMP
00367 nsNegotiateAuth::GetNextToken(const void *inToken,
00368                               PRUint32    inTokenLen,
00369                               void      **outToken,
00370                               PRUint32   *outTokenLen)
00371 {
00372     OM_uint32 major_status, minor_status;
00373     OM_uint32 req_flags = 0;
00374     gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
00375     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
00376     gss_buffer_t  in_token_ptr = GSS_C_NO_BUFFER;
00377     gss_name_t server;
00378 
00379     LOG(("entering nsNegotiateAuth::GetNextToken()\n"));
00380 
00381     if (!gssFunInit)
00382        return NS_ERROR_NOT_INITIALIZED;
00383 
00384     if (mServiceFlags & REQ_DELEGATE)
00385         req_flags |= GSS_C_DELEG_FLAG;
00386 
00387     input_token.value = (void *)mServiceName.get();
00388     input_token.length = mServiceName.Length() + 1;
00389 
00390     major_status = gss_import_name_ptr(&minor_status,
00391                                    &input_token,
00392                                    &gss_c_nt_hostbased_service,
00393                                    &server);
00394     input_token.value = NULL;
00395     input_token.length = 0;
00396     if (GSS_ERROR(major_status)) {
00397         LogGssError(major_status, minor_status, "gss_import_name() failed");
00398         return NS_ERROR_FAILURE;
00399     }
00400 
00401     if (inToken) {
00402         input_token.length = inTokenLen;
00403         input_token.value = (void *) inToken;
00404         in_token_ptr = &input_token;
00405     }
00406     else if (mCtx != GSS_C_NO_CONTEXT) {
00407         // If there is no input token, then we are starting a new
00408         // authentication sequence.  If we have already initialized our
00409         // security context, then we're in trouble because it means that the
00410         // first sequence failed.  We need to bail or else we might end up in
00411         // an infinite loop.
00412         LOG(("Cannot restart authentication sequence!"));
00413         return NS_ERROR_UNEXPECTED; 
00414     }
00415 
00416 #if defined(XP_MACOSX)
00417     // Suppress Kerberos prompts to get credentials.  See bug 240643.
00418     // We can only use Mac OS X specific kerb functions if we are using 
00419     // the native lib
00420 
00421     KLBoolean found;
00422     if (gssNativeImp &&
00423         (KLCacheHasValidTickets_ptr(NULL, kerberosVersion_V5, &found, NULL,
00424                                     NULL)
00425          != klNoErr || !found))
00426     {
00427         major_status = GSS_S_FAILURE;
00428         minor_status = 0;
00429     }
00430     else
00431 #endif /* XP_MACOSX */
00432     major_status = gss_init_sec_context_ptr(&minor_status,
00433                                             GSS_C_NO_CREDENTIAL,
00434                                             &mCtx,
00435                                             server,
00436                                             mMechOID,
00437                                             req_flags,
00438                                             GSS_C_INDEFINITE,
00439                                             GSS_C_NO_CHANNEL_BINDINGS,
00440                                             in_token_ptr,
00441                                             nsnull,
00442                                             &output_token,
00443                                             nsnull,
00444                                             nsnull);
00445 
00446     nsresult rv;
00447     if (GSS_ERROR(major_status)) {
00448         LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
00449         Reset();
00450         rv = NS_ERROR_FAILURE;
00451         goto end;
00452     }
00453     if (major_status == GSS_S_COMPLETE) {
00454         //
00455         // We are done with this authentication, reset the context. 
00456         //
00457         Reset();
00458     }
00459     else if (major_status == GSS_S_CONTINUE_NEEDED) {
00460         //
00461         // The important thing is that we do NOT reset the
00462         // context here because it will be needed on the
00463         // next call.
00464         //
00465     } 
00466 
00467     if (output_token.length == 0) {
00468         LOG(("  No GSS output token to send, exiting"));
00469         rv = NS_ERROR_FAILURE;
00470         goto end;
00471     }
00472 
00473     *outTokenLen = output_token.length;
00474     *outToken = nsMemory::Clone(output_token.value, output_token.length);
00475 
00476     gss_release_buffer_ptr(&minor_status, &output_token);
00477     rv = NS_OK;
00478 
00479 end:
00480     gss_release_name_ptr(&minor_status, &server);
00481 
00482     LOG(("  leaving nsNegotiateAuth::GetNextToken [rv=%x]", rv));
00483     return rv;
00484 }