Back to index

lightning-sunbird  0.9+nobinonly
nsAuthGSSAPI.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 #include "nsNativeCharsetUtils.h"
00059 
00060 #include "nsAuthGSSAPI.h"
00061 
00062 #ifdef XP_MACOSX
00063 #include <Kerberos/Kerberos.h>
00064 #endif
00065 
00066 #ifdef XP_MACOSX
00067 typedef KLStatus (*KLCacheHasValidTickets_type)(
00068     KLPrincipal,
00069     KLKerberosVersion,
00070     KLBoolean *,
00071     KLPrincipal *,
00072     char **);
00073 #endif
00074 
00075 //-----------------------------------------------------------------------------
00076 
00077 // We define GSS_C_NT_HOSTBASED_SERVICE explicitly since it may be referenced
00078 // by by a different name depending on the implementation of gss but always
00079 // has the same value
00080 
00081 static gss_OID_desc gss_c_nt_hostbased_service = 
00082     { 10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04" };
00083 
00084 static const char kNegotiateAuthGssLib[] =
00085     "network.negotiate-auth.gsslib";
00086 static const char kNegotiateAuthNativeImp[] = 
00087    "network.negotiate-auth.using-native-gsslib";
00088 
00089 static const char *gssFuncStr[] = {
00090     "gss_display_status", 
00091     "gss_init_sec_context", 
00092     "gss_indicate_mechs",
00093     "gss_release_oid_set",
00094     "gss_delete_sec_context",
00095     "gss_import_name",
00096     "gss_release_buffer",
00097     "gss_release_name",
00098     "gss_wrap",
00099     "gss_unwrap"
00100 };
00101 
00102 #define gssFuncItems NS_ARRAY_LENGTH(gssFuncStr)
00103 
00104 static PRFuncPtr gssFunPtr[gssFuncItems]; 
00105 static PRBool    gssNativeImp = PR_TRUE;
00106 static PRBool    gssFunInit = PR_FALSE;
00107 
00108 #define gss_display_status_ptr      ((gss_display_status_type)*gssFunPtr[0])
00109 #define gss_init_sec_context_ptr    ((gss_init_sec_context_type)*gssFunPtr[1])
00110 #define gss_indicate_mechs_ptr      ((gss_indicate_mechs_type)*gssFunPtr[2])
00111 #define gss_release_oid_set_ptr     ((gss_release_oid_set_type)*gssFunPtr[3])
00112 #define gss_delete_sec_context_ptr  ((gss_delete_sec_context_type)*gssFunPtr[4])
00113 #define gss_import_name_ptr         ((gss_import_name_type)*gssFunPtr[5])
00114 #define gss_release_buffer_ptr      ((gss_release_buffer_type)*gssFunPtr[6])
00115 #define gss_release_name_ptr        ((gss_release_name_type)*gssFunPtr[7])
00116 #define gss_wrap_ptr                ((gss_wrap_type)*gssFunPtr[8])
00117 #define gss_unwrap_ptr              ((gss_unwrap_type)*gssFunPtr[9])
00118 
00119 #ifdef XP_MACOSX
00120 static PRFuncPtr KLCacheHasValidTicketsPtr;
00121 #define KLCacheHasValidTickets_ptr \
00122         ((KLCacheHasValidTickets_type)*KLCacheHasValidTicketsPtr)
00123 #endif
00124 
00125 static nsresult
00126 gssInit()
00127 {
00128     nsXPIDLCString libPath;
00129     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00130     if (prefs) {
00131         prefs->GetCharPref(kNegotiateAuthGssLib, getter_Copies(libPath)); 
00132         prefs->GetBoolPref(kNegotiateAuthNativeImp, &gssNativeImp); 
00133     }
00134 
00135     PRLibrary *lib = NULL;
00136 
00137     if (!libPath.IsEmpty()) {
00138         LOG(("Attempting to load user specified library [%s]\n", libPath.get()));
00139         gssNativeImp = PR_FALSE;
00140         lib = PR_LoadLibrary(libPath.get());
00141     }
00142     else {
00143 #ifdef XP_WIN
00144         char *libName = PR_GetLibraryName(NULL, "gssapi32");
00145         if (libName) {
00146             lib = PR_LoadLibrary("gssapi32");
00147             PR_FreeLibraryName(libName);
00148         }
00149 #else
00150         
00151         const char *const libNames[] = {
00152             "gss",
00153             "gssapi_krb5",
00154             "gssapi"
00155         };
00156         
00157         const char *const verLibNames[] = {
00158             "libgssapi_krb5.so.2", /* MIT - FC, Suse10, Debian */
00159             "libgssapi.so.4",      /* Heimdal - Suse10, MDK */
00160             "libgssapi.so.1"       /* Heimdal - Suse9, CITI - FC, MDK, Suse10*/
00161         };
00162 
00163         for (size_t i = 0; i < NS_ARRAY_LENGTH(verLibNames) && !lib; ++i) {
00164             lib = PR_LoadLibrary(verLibNames[i]);
00165  
00166             /* The CITI libgssapi library calls exit() during
00167              * initialization if it's not correctly configured. Try to
00168              * ensure that we never use this library for our GSSAPI
00169              * support, as its just a wrapper library, anyway.
00170              * See Bugzilla #325433
00171              */
00172             if (lib &&
00173                 PR_FindFunctionSymbol(lib, 
00174                                       "internal_krb5_gss_initialize") &&
00175                 PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) {
00176                 LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
00177                 PR_UnloadLibrary(lib);
00178                 lib = NULL;
00179             }
00180         }
00181 
00182         for (size_t i = 0; i < NS_ARRAY_LENGTH(libNames) && !lib; ++i) {
00183             char *libName = PR_GetLibraryName(NULL, libNames[i]);
00184             if (libName) {
00185                 lib = PR_LoadLibrary(libName);
00186                 PR_FreeLibraryName(libName);
00187 
00188                 if (lib &&
00189                     PR_FindFunctionSymbol(lib, 
00190                                           "internal_krb5_gss_initialize") &&
00191                     PR_FindFunctionSymbol(lib, "gssd_pname_to_uid")) {
00192                     LOG(("CITI libgssapi found, which calls exit(). Skipping\n"));
00193                     PR_UnloadLibrary(lib);
00194                     lib = NULL;
00195                 } 
00196             }
00197         }
00198 #endif
00199     }
00200     
00201     if (!lib) {
00202         LOG(("Fail to load gssapi library\n"));
00203         return NS_ERROR_FAILURE;
00204     }
00205 
00206     LOG(("Attempting to load gss functions\n"));
00207 
00208     for (size_t i = 0; i < gssFuncItems; ++i) {
00209         gssFunPtr[i] = PR_FindFunctionSymbol(lib, gssFuncStr[i]);
00210         if (!gssFunPtr[i]) {
00211             LOG(("Fail to load %s function from gssapi library\n", gssFuncStr[i]));
00212             PR_UnloadLibrary(lib);
00213             return NS_ERROR_FAILURE;
00214         }
00215     }
00216 #ifdef XP_MACOSX
00217     if (gssNativeImp &&
00218             !(KLCacheHasValidTicketsPtr =
00219                PR_FindFunctionSymbol(lib, "KLCacheHasValidTickets"))) {
00220         LOG(("Fail to load KLCacheHasValidTickets function from gssapi library\n"));
00221         PR_UnloadLibrary(lib);
00222         return NS_ERROR_FAILURE;
00223     }
00224 #endif
00225 
00226     gssFunInit = PR_TRUE;
00227     return NS_OK;
00228 }
00229 
00230 #if defined( PR_LOGGING )
00231 
00232 // Generate proper GSSAPI error messages from the major and
00233 // minor status codes.
00234 void
00235 LogGssError(OM_uint32 maj_stat, OM_uint32 min_stat, const char *prefix)
00236 {
00237     OM_uint32 new_stat;
00238     OM_uint32 msg_ctx = 0;
00239     gss_buffer_desc status1_string;
00240     gss_buffer_desc status2_string;
00241     OM_uint32 ret;
00242     nsCAutoString errorStr;
00243     errorStr.Assign(prefix);
00244 
00245     if (!gssFunInit)
00246         return;
00247 
00248     errorStr += ": ";
00249     do {
00250         ret = gss_display_status_ptr(&new_stat,
00251                                      maj_stat,
00252                                      GSS_C_GSS_CODE,
00253                                      GSS_C_NULL_OID,
00254                                      &msg_ctx,
00255                                      &status1_string);
00256         errorStr.Append((const char *) status1_string.value, status1_string.length);
00257         gss_release_buffer_ptr(&new_stat, &status1_string);
00258 
00259         errorStr += '\n';
00260         ret = gss_display_status_ptr(&new_stat,
00261                                      min_stat,
00262                                      GSS_C_MECH_CODE,
00263                                      GSS_C_NULL_OID,
00264                                      &msg_ctx,
00265                                      &status2_string);
00266         errorStr.Append((const char *) status2_string.value, status2_string.length);
00267         errorStr += '\n';
00268     } while (!GSS_ERROR(ret) && msg_ctx != 0);
00269 
00270     LOG(("%s\n", errorStr.get()));
00271 }
00272 
00273 #else /* PR_LOGGING */
00274 
00275 #define LogGssError(x,y,z)
00276 
00277 #endif /* PR_LOGGING */
00278 
00279 //-----------------------------------------------------------------------------
00280 
00281 nsAuthGSSAPI::nsAuthGSSAPI(pType package)
00282     : mServiceFlags(REQ_DEFAULT)
00283 {
00284     OM_uint32 minstat;
00285     OM_uint32 majstat;
00286     gss_OID_set mech_set;
00287     gss_OID item;
00288 
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 nsAuthGSSAPI::nsAuthGSSAPI()\n"));
00296 
00297     mComplete = PR_FALSE;
00298 
00299     if (!gssFunInit && NS_FAILED(gssInit()))
00300         return;
00301 
00302     mCtx = GSS_C_NO_CONTEXT;
00303     mMechOID = &gss_krb5_mech_oid_desc;
00304 
00305     // if the type is kerberos we accept it as default
00306     // and exit 
00307 
00308     if (package == PACKAGE_TYPE_KERBEROS)
00309         return;
00310 
00311     // Now, look at the list of supported mechanisms,
00312     // if SPNEGO is found, then use it.
00313     // Otherwise, set the desired mechanism to
00314     // GSS_C_NO_OID and let the system try to use
00315     // the default mechanism.
00316     //
00317     // Using Kerberos directly (instead of negotiating
00318     // with SPNEGO) may work in some cases depending
00319     // on how smart the server side is.
00320     
00321     majstat = gss_indicate_mechs_ptr(&minstat, &mech_set);
00322     if (GSS_ERROR(majstat))
00323         return;
00324 
00325     if (mech_set) {
00326         for (i=0; i<mech_set->count; i++) {
00327             item = &mech_set->elements[i];    
00328             if (item->length == gss_spnego_mech_oid_desc.length &&
00329                 !memcmp(item->elements, gss_spnego_mech_oid_desc.elements,
00330                 item->length)) {
00331                 // ok, we found it
00332                 mMechOID = &gss_spnego_mech_oid_desc;
00333                 break;
00334             }
00335         }
00336         gss_release_oid_set_ptr(&minstat, &mech_set);
00337     }
00338 }
00339 
00340 void
00341 nsAuthGSSAPI::Reset()
00342 {
00343     if (gssFunInit && mCtx != GSS_C_NO_CONTEXT) {
00344         OM_uint32 minor_status;
00345         gss_delete_sec_context_ptr(&minor_status, &mCtx, GSS_C_NO_BUFFER);
00346     }
00347     mCtx = GSS_C_NO_CONTEXT;
00348     mComplete = PR_FALSE;
00349 }
00350 
00351 NS_IMPL_ISUPPORTS1(nsAuthGSSAPI, nsIAuthModule)
00352 
00353 NS_IMETHODIMP
00354 nsAuthGSSAPI::Init(const char *serviceName,
00355                    PRUint32    serviceFlags,
00356                    const PRUnichar *domain,
00357                    const PRUnichar *username,
00358                    const PRUnichar *password)
00359 {
00360     // we don't expect to be passed any user credentials
00361     NS_ASSERTION(!domain && !username && !password, "unexpected credentials");
00362 
00363     // it's critial that the caller supply a service name to be used
00364     NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
00365 
00366     LOG(("entering nsAuthGSSAPI::Init()\n"));
00367 
00368     if (!gssFunInit)
00369        return NS_ERROR_NOT_INITIALIZED;
00370 
00371     mServiceName = serviceName;
00372     mServiceFlags = serviceFlags;
00373     return NS_OK;
00374 }
00375 
00376 NS_IMETHODIMP
00377 nsAuthGSSAPI::GetNextToken(const void *inToken,
00378                            PRUint32    inTokenLen,
00379                            void      **outToken,
00380                            PRUint32   *outTokenLen)
00381 {
00382     OM_uint32 major_status, minor_status;
00383     OM_uint32 req_flags = 0;
00384     gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
00385     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
00386     gss_buffer_t  in_token_ptr = GSS_C_NO_BUFFER;
00387     gss_name_t server;
00388     nsCAutoString userbuf;
00389     nsresult rv;
00390 
00391     LOG(("entering nsAuthGSSAPI::GetNextToken()\n"));
00392 
00393     if (!gssFunInit)
00394        return NS_ERROR_NOT_INITIALIZED;
00395 
00396     // If they've called us again after we're complete, reset to start afresh.
00397     if (mComplete)
00398         Reset();
00399     
00400     if (mServiceFlags & REQ_DELEGATE)
00401         req_flags |= GSS_C_DELEG_FLAG;
00402 
00403     if (mServiceFlags & REQ_MUTUAL_AUTH)
00404         req_flags |= GSS_C_MUTUAL_FLAG;
00405 
00406     input_token.value = (void *)mServiceName.get();
00407     input_token.length = mServiceName.Length() + 1;
00408 
00409     major_status = gss_import_name_ptr(&minor_status,
00410                                    &input_token,
00411                                    &gss_c_nt_hostbased_service,
00412                                    &server);
00413     input_token.value = NULL;
00414     input_token.length = 0;
00415     if (GSS_ERROR(major_status)) {
00416         LogGssError(major_status, minor_status, "gss_import_name() failed");
00417         return NS_ERROR_FAILURE;
00418     }
00419 
00420     if (inToken) {
00421         input_token.length = inTokenLen;
00422         input_token.value = (void *) inToken;
00423         in_token_ptr = &input_token;
00424     }
00425     else if (mCtx != GSS_C_NO_CONTEXT) {
00426         // If there is no input token, then we are starting a new
00427         // authentication sequence.  If we have already initialized our
00428         // security context, then we're in trouble because it means that the
00429         // first sequence failed.  We need to bail or else we might end up in
00430         // an infinite loop.
00431         LOG(("Cannot restart authentication sequence!"));
00432         return NS_ERROR_UNEXPECTED; 
00433     }
00434 
00435 #if defined(XP_MACOSX)
00436     // Suppress Kerberos prompts to get credentials.  See bug 240643.
00437     // We can only use Mac OS X specific kerb functions if we are using 
00438     // the native lib
00439     KLBoolean found;    
00440     PRBool doingMailTask = ( mServiceName.Find("imap@") || mServiceName.Find("pop@") || mServiceName.Find("smtp@"));
00441     
00442     if(( doingMailTask == PR_FALSE ) && (gssNativeImp &&
00443          (KLCacheHasValidTickets_ptr(NULL, kerberosVersion_V5, &found, NULL, NULL) != klNoErr || !found)))
00444     {
00445         major_status = GSS_S_FAILURE;
00446         minor_status = 0;
00447     }
00448     else
00449 #endif /* XP_MACOSX */
00450     major_status = gss_init_sec_context_ptr(&minor_status,
00451                                             GSS_C_NO_CREDENTIAL,
00452                                             &mCtx,
00453                                             server,
00454                                             mMechOID,
00455                                             req_flags,
00456                                             GSS_C_INDEFINITE,
00457                                             GSS_C_NO_CHANNEL_BINDINGS,
00458                                             in_token_ptr,
00459                                             nsnull,
00460                                             &output_token,
00461                                             nsnull,
00462                                             nsnull);
00463 
00464     if (GSS_ERROR(major_status)) {
00465         LogGssError(major_status, minor_status, "gss_init_sec_context() failed");
00466         Reset();
00467         rv = NS_ERROR_FAILURE;
00468         goto end;
00469     }
00470     if (major_status == GSS_S_COMPLETE) {
00471         // Mark ourselves as being complete, so that if we're called again
00472         // we know to start afresh.
00473         mComplete = PR_TRUE;
00474     }
00475     else if (major_status == GSS_S_CONTINUE_NEEDED) {
00476         //
00477         // The important thing is that we do NOT reset the
00478         // context here because it will be needed on the
00479         // next call.
00480         //
00481     } 
00482     
00483     *outTokenLen = output_token.length;
00484     if (output_token.length != 0)
00485         *outToken = nsMemory::Clone(output_token.value, output_token.length);
00486     else
00487         *outToken = NULL;
00488     
00489     gss_release_buffer_ptr(&minor_status, &output_token);
00490 
00491     if (major_status == GSS_S_COMPLETE)
00492         rv = NS_SUCCESS_AUTH_FINISHED;
00493     else
00494         rv = NS_OK;
00495 
00496 end:
00497     gss_release_name_ptr(&minor_status, &server);
00498 
00499     LOG(("  leaving nsAuthGSSAPI::GetNextToken [rv=%x]", rv));
00500     return rv;
00501 }
00502 
00503 NS_IMETHODIMP
00504 nsAuthGSSAPI::Unwrap(const void *inToken,
00505                      PRUint32    inTokenLen,
00506                      void      **outToken,
00507                      PRUint32   *outTokenLen)
00508 {
00509     OM_uint32 major_status, minor_status;
00510 
00511     gss_buffer_desc input_token;
00512     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
00513 
00514     input_token.value = (void *) inToken;
00515     input_token.length = inTokenLen;
00516 
00517     major_status = gss_unwrap_ptr(&minor_status,
00518                                   mCtx,
00519                                   &input_token,
00520                                   &output_token,
00521                                   NULL,
00522                                   NULL);
00523     if (GSS_ERROR(major_status)) {
00524         LogGssError(major_status, minor_status, "gss_unwrap() failed");
00525         Reset();
00526         gss_release_buffer_ptr(&minor_status, &output_token);
00527         return NS_ERROR_FAILURE;
00528     }
00529 
00530     *outTokenLen = output_token.length;
00531 
00532     if (output_token.length)
00533         *outToken = nsMemory::Clone(output_token.value, output_token.length);
00534     else
00535         *outToken = NULL;
00536 
00537     gss_release_buffer_ptr(&minor_status, &output_token);
00538 
00539     return NS_OK;
00540 }
00541  
00542 NS_IMETHODIMP
00543 nsAuthGSSAPI::Wrap(const void *inToken,
00544                    PRUint32    inTokenLen,
00545                    PRBool      confidential,
00546                    void      **outToken,
00547                    PRUint32   *outTokenLen)
00548 {
00549     OM_uint32 major_status, minor_status;
00550 
00551     gss_buffer_desc input_token;
00552     gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
00553 
00554     input_token.value = (void *) inToken;
00555     input_token.length = inTokenLen;
00556 
00557     major_status = gss_wrap_ptr(&minor_status,
00558                                 mCtx,
00559                                 confidential,
00560                                 GSS_C_QOP_DEFAULT,
00561                                 &input_token,
00562                                 NULL,
00563                                 &output_token);
00564     
00565     if (GSS_ERROR(major_status)) {
00566         LogGssError(major_status, minor_status, "gss_wrap() failed");
00567         Reset();
00568         gss_release_buffer_ptr(&minor_status, &output_token);
00569         return NS_ERROR_FAILURE;
00570     }
00571 
00572     *outTokenLen = output_token.length;
00573 
00574     /* it is not possible for output_token.length to be zero */
00575     *outToken = nsMemory::Clone(output_token.value, output_token.length);
00576     gss_release_buffer_ptr(&minor_status, &output_token);
00577 
00578     return NS_OK;
00579 }
00580