/******************************************************************************/ /* */ /* X r d S e c P r o t o c o l k r b 5 . c c */ /* */ /* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */ /* All Rights Reserved */ /* Produced by Andrew Hanushevsky for Stanford University under contract */ /* DE-AC02-76-SFO0515 with the Department of Energy */ /* Modifications: */ /* - January 2007: add support for forwarded tickets */ /* (author: G. Ganis, CERN) */ /* */ /* This file is part of the XRootD software suite. */ /* */ /* XRootD is free software: you can redistribute it and/or modify it under */ /* the terms of the GNU Lesser General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* XRootD is distributed in the hope that it will be useful, but WITHOUT */ /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */ /* License for more details. */ /* */ /* You should have received a copy of the GNU Lesser General Public License */ /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */ /* COPYING (GPL license). If not, see . */ /* */ /* The copyright holder's institutional names and contributor's names may not */ /* be used to endorse or promote products derived from this software without */ /* specific prior written permission of the institution or contributor. */ /******************************************************************************/ #include #include #include #include #include #include #include #include #include #include extern "C" { #include "krb5.h" #ifdef HAVE_ET_COM_ERR_H #include "et/com_err.h" #else #include "com_err.h" #endif } #include "XrdVersion.hh" #include "XrdNet/XrdNetAddrInfo.hh" #include "XrdNet/XrdNetUtils.hh" #include "XrdOuc/XrdOucErrInfo.hh" #include "XrdOuc/XrdOucEnv.hh" #include "XrdSys/XrdSysHeaders.hh" #include "XrdSys/XrdSysPthread.hh" #include "XrdSys/XrdSysPwd.hh" #include "XrdOuc/XrdOucTokenizer.hh" #include "XrdSec/XrdSecInterface.hh" /******************************************************************************/ /* D e f i n e s */ /******************************************************************************/ #define krb_etxt(x) (char *)error_message(x) #define XrdSecPROTOIDENT "krb5" #define XrdSecPROTOIDLEN sizeof(XrdSecPROTOIDENT) #define XrdSecNOIPCHK 0x0001 #define XrdSecEXPTKN 0x0002 #define XrdSecINITTKN 0x0004 #define XrdSecDEBUG 0x1000 #define XrdSecMAXPATHLEN 4096 #define CLDBG(x) if (client_options & XrdSecDEBUG) cerr <<"Seckrb5: " <= XrdSecMAXPATHLEN) ? XrdSecMAXPATHLEN -1 : lt; memcpy(ExpFile, expfile, lt); ExpFile[lt] = 0; } } XrdSecProtocolkrb5(const char *KP, const char *hname, XrdNetAddrInfo &endPoint) : XrdSecProtocol(XrdSecPROTOIDENT) {Service = (KP ? strdup(KP) : 0); Entity.host = strdup(hname); epAddr = endPoint; Entity.addrInfo = &epAddr; CName[0] = '?'; CName[1] = '\0'; Entity.name = CName; Step = 0; AuthContext = 0; AuthClientContext = 0; Ticket = 0; Creds = 0; } void Delete(); private: ~XrdSecProtocolkrb5() {} // Delete() does it all static int Fatal(XrdOucErrInfo *erp,int rc,const char *msg1,char *KP=0,int krc=0); static int get_krbCreds(char *KP, krb5_creds **krb_creds); void SetAddr(krb5_address &ipadd); static XrdSysMutex krbContext; // Server static XrdSysMutex krbClientContext;// Client static int options; // Server static int client_options;// Client static krb5_context krb_context; // Server static krb5_context krb_client_context; // Client static krb5_ccache krb_client_ccache; // Client static krb5_ccache krb_ccache; // Server static krb5_keytab krb_keytab; // Server static krb5_principal krb_principal; // Server static char *Principal; // Server's principal name static char *Parms; // Server parameters static char ExpFile[XrdSecMAXPATHLEN]; // Server: (template for) // file to export token int exp_krbTkn(XrdSecCredentials *cred, XrdOucErrInfo *erp); int get_krbFwdCreds(char *KP, krb5_data *outdata); XrdNetAddrInfo epAddr; char CName[256]; // Kerberos limit char *Service; // Target principal for client char Step; // Indicates at which step we are krb5_auth_context AuthContext; // Authetication context krb5_auth_context AuthClientContext; // Authetication context krb5_ticket *Ticket; // Ticket associated to client authentication krb5_creds *Creds; // Client: credentials }; /******************************************************************************/ /* S t a t i c D a t a */ /******************************************************************************/ XrdSysMutex XrdSecProtocolkrb5::krbContext; // Server XrdSysMutex XrdSecProtocolkrb5::krbClientContext; // Client int XrdSecProtocolkrb5::client_options = 0;// Client int XrdSecProtocolkrb5::options = 0; // Server krb5_context XrdSecProtocolkrb5::krb_context; // Server krb5_context XrdSecProtocolkrb5::krb_client_context; // Client krb5_ccache XrdSecProtocolkrb5::krb_client_ccache; // Client krb5_ccache XrdSecProtocolkrb5::krb_ccache; // Server krb5_keytab XrdSecProtocolkrb5::krb_keytab = NULL; // Server krb5_principal XrdSecProtocolkrb5::krb_principal; // Server char *XrdSecProtocolkrb5::Principal = 0; // Server char *XrdSecProtocolkrb5::Parms = 0; // Server char XrdSecProtocolkrb5::ExpFile[XrdSecMAXPATHLEN] = "/tmp/krb5cc_"; /******************************************************************************/ /* D e l e t e */ /******************************************************************************/ void XrdSecProtocolkrb5::Delete() { if (Parms) {free(Parms); Parms = 0;} if (Creds) krb5_free_creds(krb_context, Creds); if (Ticket) krb5_free_ticket(krb_context, Ticket); if (AuthContext) krb5_auth_con_free(krb_context, AuthContext); if (AuthClientContext) krb5_auth_con_free(krb_client_context, AuthClientContext); if (Entity.host) free(Entity.host); if (Service) free(Service); delete this; } /******************************************************************************/ /* g e t C r e d e n t i a l s */ /******************************************************************************/ XrdSecCredentials *XrdSecProtocolkrb5::getCredentials(XrdSecParameters *noparm, XrdOucErrInfo *error) { char *buff; int bsz; krb_rc rc; krb5_data outbuf; CLDBG("getCredentials"); // Supply null credentials if so needed for this protocol // if (!Service) {CLDBG("Null credentials supplied."); return new XrdSecCredentials(0,0); } CLDBG("context lock"); krbClientContext.Lock(); CLDBG("context locked"); // We support passing the credential cache path via Url parameter // char *ccn = (error && error->getEnv()) ? error->getEnv()->Get("xrd.k5ccname") : 0; const char *kccn = ccn ? (const char *)ccn : getenv("KRB5CCNAME"); char ccname[128]; if (!kccn) {snprintf(ccname, 128, "/tmp/krb5cc_%d", geteuid()); if (access(ccname, R_OK) == 0) {kccn = ccname;} } CLDBG((kccn ? kccn : "credentials cache unset")); // Initialize the context and get the cache default. // if ((rc = krb5_init_context(&krb_client_context))) {Fatal(error, ENOPROTOOPT, "Kerberos initialization failed", Service, rc); return (XrdSecCredentials *)0; } CLDBG("init context"); // Set the name of the default credentials cache for the Kerberos context // if ((rc = krb5_cc_set_default_name(krb_client_context, kccn))) {Fatal(error, ENOPROTOOPT, "Kerberos default credentials cache setting failed", Service, rc); return (XrdSecCredentials *)0; } CLDBG("cc set default name"); // Obtain the default cache location // if ((rc = krb5_cc_default(krb_client_context, &krb_client_ccache))) {Fatal(error, ENOPROTOOPT, "Unable to locate cred cache", Service, rc); return (XrdSecCredentials *)0; } CLDBG("cc default"); // Check if the server asked for a forwardable ticket // char *pfwd = 0; if ((pfwd = (char *) strstr(Service,",fwd"))) { client_options |= XrdSecEXPTKN; *pfwd = 0; } // Clear outgoing ticket and lock the kerberos context // outbuf.length = 0; outbuf.data = 0; // If this is not the first call, we are asked to send over a delegated ticket: // we must create it first // we save it into a file and return signalling the end of the hand-shake // if (Step > 0) {if ((rc = get_krbFwdCreds(Service, &outbuf))) {krbClientContext.UnLock(); Fatal(error, ESRCH, "Unable to get forwarded credentials", Service, rc); return (XrdSecCredentials *)0; } else {bsz = XrdSecPROTOIDLEN+outbuf.length; if (!(buff = (char *)malloc(bsz))) {krbClientContext.UnLock(); Fatal(error, ENOMEM, "Insufficient memory for credentials.", Service); return (XrdSecCredentials *)0; } strcpy(buff, XrdSecPROTOIDENT); memcpy((void *)(buff+XrdSecPROTOIDLEN), (const void *)outbuf.data, (size_t)outbuf.length); CLDBG("Returned " <ticket_flags & TKT_FLG_FORWARDABLE)) { if ((client_options & XrdSecINITTKN) && !reinitdone && caninittkn) { // Need to re-init CLPRT("Existing ticket is not forwardable: re-init "); rc = system(reinitcmd); CLDBG("getCredentials: return code from '"<size <= (int)XrdSecPROTOIDLEN || !cred->buffer) {strncpy(Entity.prot, "host", sizeof(Entity.prot)); return 0; } // Check if this is a recognized protocol // if (strcmp(cred->buffer, XrdSecPROTOIDENT)) {char emsg[256]; snprintf(emsg, sizeof(emsg), "Authentication protocol id mismatch (%.4s != %.4s).", XrdSecPROTOIDENT, cred->buffer); Fatal(error, EINVAL, emsg, Principal); return -1; } CLDBG("protocol check"); char printit[4096]; sprintf(printit,"Step is %d",Step); CLDBG(printit); // If this is not the first call the buffer contains a forwarded token: // we save it into a file and return signalling the end of the hand-shake // if (Step > 0) {if ((rc = exp_krbTkn(cred, error))) iferror = (char *)"Unable to export the token to file"; if (rc && iferror) { krbContext.UnLock(); return Fatal(error, EINVAL, iferror, Principal, rc); } krbContext.UnLock(); return 0; } CLDBG("protocol check"); // Increment the step // Step += 1; // Indicate who we are // strncpy(Entity.prot, XrdSecPROTOIDENT, sizeof(Entity.prot)); // Create a kerberos style ticket and obtain the kerberos mutex // CLDBG("Context Lock"); inbuf.length = cred->size -XrdSecPROTOIDLEN; inbuf.data = &cred->buffer[XrdSecPROTOIDLEN]; krbContext.Lock(); // Check if whether the IP address in the credentials must match that of // the incomming host. // CLDBG("Context Locked"); if (!(XrdSecProtocolkrb5::options & XrdSecNOIPCHK)) {SetAddr(ipadd); iferror = (char *)"Unable to validate ip address;"; if (!(rc=krb5_auth_con_init(krb_context, &AuthContext))) rc=krb5_auth_con_setaddrs(krb_context, AuthContext, NULL, &ipadd); } // Decode the credentials and extract client's name // if (!rc) {if ((rc = krb5_rd_req(krb_context, &AuthContext, &inbuf, (krb5_const_principal)krb_principal, krb_keytab, NULL, &Ticket))) iferror = (char *)"Unable to authenticate credentials;"; else if ((rc = krb5_aname_to_localname(krb_context, Ticket->enc_part2->client, sizeof(CName)-1, CName))) iferror = (char *)"Unable to extract client name;"; } // Make sure the name is null-terminated // CName[sizeof(CName)-1] = '\0'; // If requested, ask the client for a forwardable token int hsrc = 0; if (!rc && XrdSecProtocolkrb5::options & XrdSecEXPTKN) { // We just ask for more; the client knows what to send over hsrc = 1; // We need to fill-in a fake buffer int len = strlen("fwdtgt") + 1; char *buf = (char *) malloc(len); memcpy(buf, "fwdtgt", len-1); buf[len-1] = 0; *parms = new XrdSecParameters(buf, len); } // Release any allocated storage at this point and unlock mutex // krbContext.UnLock(); // Diagnose any errors // if (rc && iferror) return Fatal(error, EACCES, iferror, Principal, rc); // All done // return hsrc; } /******************************************************************************/ /* I n i t i a l i z a t i o n M e t h o d s */ /******************************************************************************/ /******************************************************************************/ /* I n i t */ /******************************************************************************/ int XrdSecProtocolkrb5::Init(XrdOucErrInfo *erp, char *KP, char *kfn) { krb_rc rc; char buff[2048]; // Create a kerberos context. There is one such context per protocol object. // // If we have no principal then this is a client-side call: initializations are done // in getCredentials to allow for multiple client principals // if (!KP) return 0; if ((rc = krb5_init_context(&krb_context))) return Fatal(erp, ENOPROTOOPT, "Kerberos initialization failed", KP, rc); // Obtain the default cache location // if ((rc = krb5_cc_default(krb_context, &krb_ccache))) return Fatal(erp, ENOPROTOOPT, "Unable to locate cred cache", KP, rc); // Try to resolve the keyfile name // if (kfn && *kfn) {if ((rc = krb5_kt_resolve(krb_context, kfn, &krb_keytab))) {snprintf(buff, sizeof(buff), "Unable to find keytab '%s';", kfn); return Fatal(erp, ESRCH, buff, Principal, rc); } } else { krb5_kt_default(krb_context, &krb_keytab); } // Keytab name // char krb_kt_name[1024]; if ((rc = krb5_kt_get_name(krb_context, krb_keytab, &krb_kt_name[0], 1024))) {snprintf(buff, sizeof(buff), "Unable to get keytab name;"); return Fatal(erp, ESRCH, buff, Principal, rc); } // Check if we can read access the keytab file // krb5_kt_cursor ktc; if ((rc = krb5_kt_start_seq_get(krb_context, krb_keytab, &ktc))) {snprintf(buff, sizeof(buff), "Unable to start sequence on the keytab file %s", krb_kt_name); return Fatal(erp, EPERM, buff, Principal, rc); } if ((rc = krb5_kt_end_seq_get(krb_context, krb_keytab, &ktc))) {snprintf(buff, sizeof(buff), "WARNING: unable to end sequence on the keytab file %s", krb_kt_name); CLPRT(buff); } // Now, extract the "principal/instance@realm" from the stream // if ((rc = krb5_parse_name(krb_context,KP,&krb_principal))) return Fatal(erp, EINVAL, "Cannot parse service name", KP, rc); // Establish the correct principal to use // if ((rc = krb5_unparse_name(krb_context,(krb5_const_principal)krb_principal, (char **)&Principal))) return Fatal(erp, EINVAL, "Unable to unparse principal;", KP, rc); // All done // return 0; } /******************************************************************************/ /* P r i v a t e M e t h o d s */ /******************************************************************************/ /******************************************************************************/ /* F a t a l */ /******************************************************************************/ int XrdSecProtocolkrb5::Fatal(XrdOucErrInfo *erp, int rc, const char *msg, char *KP, int krc) { const char *msgv[8]; int k, i = 0; msgv[i++] = "Seckrb5: "; //0 msgv[i++] = msg; //1 if (krc) {msgv[i++] = "; "; //2 msgv[i++] = krb_etxt(krc); //3 } if (KP) {msgv[i++] = " (p="; //4 msgv[i++] = KP; //5 msgv[i++] = ")."; //6 } if (erp) erp->setErrInfo(rc, msgv, i); else {for (k = 0; k < i; k++) cerr <"); if (pusr) {int ln = strlen(CName); if (ln != 6) { // Adjust the space int lm = strlen(ccfile) - (int)(pusr + 6 - &ccfile[0]); memmove(pusr+ln, pusr+6, lm); } // Copy the name memcpy(pusr, CName, ln); // Adjust the length nlen += (ln - 6); } char *puid = (char *) strstr(&ccfile[0], ""); struct passwd *pw; XrdSysPwd thePwd(CName, &pw); if (puid) {char cuid[20] = {0}; if (pw) sprintf(cuid, "%d", pw->pw_uid); int ln = strlen(cuid); if (ln != 5) { // Adjust the space int lm = strlen(ccfile) - (int)(puid + 5 - &ccfile[0]); memmove(puid+ln, pusr+5, lm); } // Copy the name memcpy(puid, cuid, ln); // Adjust the length nlen += (ln - 5); } // Terminate to the new length // ccfile[nlen] = 0; // Point the received creds // krbContext.Lock(); krb5_data forwardCreds; forwardCreds.data = &cred->buffer[XrdSecPROTOIDLEN]; forwardCreds.length = cred->size -XrdSecPROTOIDLEN; // Get the replay cache // krb5_rcache rcache; if ((rc = krb5_get_server_rcache(krb_context, krb5_princ_component(krb_context, krb_principal, 0), &rcache))) return rc; if ((rc = krb5_auth_con_setrcache(krb_context, AuthContext, rcache))) return rc; // Fill-in remote address // SetAddr(ipadd); if ((rc = krb5_auth_con_setaddrs(krb_context, AuthContext, 0, &ipadd))) return rc; // Readout the credentials // krb5_creds **creds = 0; if ((rc = krb5_rd_cred(krb_context, AuthContext, &forwardCreds, &creds, 0))) return rc; // Resolve cache name krb5_ccache cache = 0; if ((rc = krb5_cc_resolve(krb_context, ccfile, &cache))) return rc; // Init cache // if ((rc = krb5_cc_initialize(krb_context, cache, Ticket->enc_part2->client))) return rc; // Store credentials in cache // if ((rc = krb5_cc_store_cred(krb_context, cache, *creds))) return rc; // Close cache if ((rc = krb5_cc_close(krb_context, cache))) return rc; // Change permission and ownership of the file // if (chmod(ccfile, 0600) == -1) return Fatal(erp, errno, "Unable to change file permissions;", ccfile, 0); // Done // return 0; } /******************************************************************************/ /* S e t A d d r */ /******************************************************************************/ void XrdSecProtocolkrb5::SetAddr(krb5_address &ipadd) { // The below is a hack but that's how it is actually done! // if (epAddr.Family() == AF_INET6) {struct sockaddr_in6 *ip = (struct sockaddr_in6 *)epAddr.SockAddr(); ipadd.addrtype = ADDRTYPE_INET6; ipadd.length = sizeof(ip->sin6_addr); ipadd.contents = (krb5_octet *)&ip->sin6_addr; } else { struct sockaddr_in *ip = (struct sockaddr_in *)epAddr.SockAddr(); ipadd.addrtype = ADDRTYPE_INET; ipadd.length = sizeof(ip->sin_addr); ipadd.contents = (krb5_octet *)&ip->sin_addr; } } /******************************************************************************/ /* X r d S e c p r o t o c o l k r b 5 I n i t */ /******************************************************************************/ extern "C" { char *XrdSecProtocolkrb5Init(const char mode, const char *parms, XrdOucErrInfo *erp) { char *op, *KPrincipal=0, *Keytab=0, *ExpFile=0; char parmbuff[1024]; XrdOucTokenizer inParms(parmbuff); int options = XrdSecNOIPCHK; static bool serverinitialized = false; // For client-side one-time initialization, we only need to set debug flag and // initialize the kerberos context and cache location. // if ((mode == 'c') || (serverinitialized)) { int opts = 0; if (getenv("XrdSecDEBUG")) opts |= XrdSecDEBUG; if (getenv("XrdSecKRB5INITTKN")) opts |= XrdSecINITTKN; XrdSecProtocolkrb5::setClientOpts(opts); return (XrdSecProtocolkrb5::Init(erp) ? (char *)0 : (char *)""); } if (!serverinitialized) { serverinitialized = true; } // Duplicate the parms // if (parms) strlcpy(parmbuff, parms, sizeof(parmbuff)); else {char *msg = (char *)"Seckrb5: Kerberos parameters not specified."; if (erp) erp->setErrInfo(EINVAL, msg); else cerr <] [-ipchk] [-exptkn[:filetemplate]] // if (inParms.GetLine()) {if ((op = inParms.GetToken()) && *op == '/') {Keytab = op; op = inParms.GetToken();} if (op && !strcmp(op, "-ipchk")) {options &= ~XrdSecNOIPCHK; op = inParms.GetToken(); } if (op && !strncmp(op, "-exptkn", 7)) {options |= XrdSecEXPTKN; if (op[7] == ':') ExpFile = op+8; op = inParms.GetToken(); } KPrincipal = strdup(op); } if (ExpFile) fprintf(stderr,"Template for exports: %s\n", ExpFile); else fprintf(stderr,"Template for exports not set\n"); // Now make sure that we have all the right info // if (!KPrincipal) {char *msg = (char *)"Seckrb5: Kerberos principal not specified."; if (erp) erp->setErrInfo(EINVAL, msg); else cerr <"); char *phost = (char *) strstr(&KPrincipal[0], ""); if (phost) {char *hn = XrdNetUtils::MyHostName(); if (hn) {int lhn = strlen(hn); if (lhn != lkey) { // Allocate, if needed int lnew = plen - lkey + lhn; if (lnew > plen) { KPrincipal = (char *) realloc(KPrincipal, lnew+1); KPrincipal[lnew] = 0; phost = (char *) strstr(&KPrincipal[0], ""); } // Adjust the space int lm = plen - (int)(phost + lkey - &KPrincipal[0]); memmove(phost + lhn, phost + lkey, lm); } // Copy the name memcpy(phost, hn, lhn); // Cleanup free(hn); } } // Now initialize the server // options |= XrdSecDEBUG; XrdSecProtocolkrb5::setExpFile(ExpFile); XrdSecProtocolkrb5::setOpts(options); if (!XrdSecProtocolkrb5::Init(erp, KPrincipal, Keytab)) {free(KPrincipal); int lpars = strlen(XrdSecProtocolkrb5::getPrincipal()); if (options & XrdSecEXPTKN) lpars += strlen(",fwd"); char *params = (char *)malloc(lpars+1); if (params) {memset(params,0,lpars+1); strcpy(params,XrdSecProtocolkrb5::getPrincipal()); if (options & XrdSecEXPTKN) strcat(params,",fwd"); XrdSecProtocolkrb5::setParms(params); return params; } return (char *)0; } // Failure // free(KPrincipal); return (char *)0; } } /******************************************************************************/ /* X r d S e c P r o t o c o l k r b 5 O b j e c t */ /******************************************************************************/ extern "C" { XrdSecProtocol *XrdSecProtocolkrb5Object(const char mode, const char *hostname, XrdNetAddrInfo &endPoint, const char *parms, XrdOucErrInfo *erp) { XrdSecProtocolkrb5 *prot; char *KPrincipal=0; // If this is a client call, then we need to get the target principal from the // parms (which must be the first and only token). For servers, we use the // context we established at initialization time. // if (mode == 'c') {if ((KPrincipal = (char *)parms)) while(*KPrincipal == ' ') KPrincipal++; if (!KPrincipal || !*KPrincipal) {char *msg = (char *)"Seckrb5: Kerberos principal not specified."; if (erp) erp->setErrInfo(EINVAL, msg); else cerr <setErrInfo(ENOMEM, msg); else cerr <