XRootD
XrdSecProtocolkrb5.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* */
3 /* X r d S e c P r o t o c o l k r b 5 . c c */
4 /* */
5 /* (c) 2003 by the Board of Trustees of the Leland Stanford, Jr., University */
6 /* All Rights Reserved */
7 /* Produced by Andrew Hanushevsky for Stanford University under contract */
8 /* DE-AC02-76-SFO0515 with the Department of Energy */
9 /* Modifications: */
10 /* - January 2007: add support for forwarded tickets */
11 /* (author: G. Ganis, CERN) */
12 /* */
13 /* This file is part of the XRootD software suite. */
14 /* */
15 /* XRootD is free software: you can redistribute it and/or modify it under */
16 /* the terms of the GNU Lesser General Public License as published by the */
17 /* Free Software Foundation, either version 3 of the License, or (at your */
18 /* option) any later version. */
19 /* */
20 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
21 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
22 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
23 /* License for more details. */
24 /* */
25 /* You should have received a copy of the GNU Lesser General Public License */
26 /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
27 /* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
28 /* */
29 /* The copyright holder's institutional names and contributor's names may not */
30 /* be used to endorse or promote products derived from this software without */
31 /* specific prior written permission of the institution or contributor. */
32 /******************************************************************************/
33 
34 #include <unistd.h>
35 #include <cctype>
36 #include <cerrno>
37 #include <cstdlib>
38 #include <string>
39 #include <strings.h>
40 #include <cstdio>
41 #include <sys/param.h>
42 #include <pwd.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 
46 extern "C" {
47 #include "krb5.h"
48 #ifdef HAVE_ET_COM_ERR_H
49 #include "et/com_err.h"
50 #else
51 #include "com_err.h"
52 #endif
53 }
54 
55 #include "XrdVersion.hh"
56 
57 #include "XrdNet/XrdNetAddrInfo.hh"
58 #include "XrdNet/XrdNetUtils.hh"
59 #include "XrdOuc/XrdOucErrInfo.hh"
60 #include "XrdOuc/XrdOucEnv.hh"
61 #include "XrdSys/XrdSysHeaders.hh"
62 #include "XrdSys/XrdSysPthread.hh"
63 #include "XrdSys/XrdSysPwd.hh"
66 
67 /******************************************************************************/
68 /* D e f i n e s */
69 /******************************************************************************/
70 
71 #define krb_etxt(x) (char *)error_message(x)
72 
73 #define XrdSecPROTOIDENT "krb5"
74 #define XrdSecPROTOIDLEN sizeof(XrdSecPROTOIDENT)
75 #define XrdSecNOIPCHK 0x0001
76 #define XrdSecEXPTKN 0x0002
77 #define XrdSecINITTKN 0x0004
78 #define XrdSecDEBUG 0x1000
79 
80 #define XrdSecMAXPATHLEN 4096
81 
82 #define CLDBG(x) if (client_options & XrdSecDEBUG) std::cerr <<"Seckrb5: " <<x <<std::endl;
83 #define CLPRT(x) std::cerr <<"Seckrb5: " <<x <<std::endl;
84 
85 typedef krb5_error_code krb_rc;
86 
87 /******************************************************************************/
88 /* X r d S e c P r o t o c o l k r b 5 C l a s s */
89 /******************************************************************************/
90 
92 {
93 public:
94 friend class XrdSecProtocolDummy; // Avoid stupid gcc warnings about destructor
95 
97  XrdSecParameters **parms,
98  XrdOucErrInfo *einfo=0);
99 
101  XrdOucErrInfo *einfo=0);
102 
103 static char *getPrincipal() {return Principal;}
104 
105 static int Init(XrdOucErrInfo *einfo, char *KP=0, char *kfn=0);
106 
107 static void setOpts(int opts) {options = opts;}
108 static void setClientOpts(int opts) {client_options = opts;}
109 static void setParms(char *param) {Parms = param;}
110 static void setExpFile(char *expfile)
111  {if (expfile)
112  {int lt = strlen(expfile);
113  lt = (lt >= XrdSecMAXPATHLEN) ?
114  XrdSecMAXPATHLEN -1 : lt;
115  memcpy(ExpFile, expfile, lt);
116  ExpFile[lt] = 0;
117  }
118  }
119 
120  XrdSecProtocolkrb5(const char *KP,
121  const char *hname,
122  XrdNetAddrInfo &endPoint)
124  {Service = (KP ? strdup(KP) : 0);
125  Entity.host = strdup(hname);
126  epAddr = endPoint;
127  Entity.addrInfo = &epAddr;
128  CName[0] = '?'; CName[1] = '\0';
129  Entity.name = CName;
130  Step = 0;
131  AuthContext = 0;
132  AuthClientContext = 0;
133  Ticket = 0;
134  Creds = 0;
135  }
136 
137  void Delete();
138 
139 private:
140 
141  ~XrdSecProtocolkrb5() {} // Delete() does it all
142 
143 static int Fatal(XrdOucErrInfo *erp, int rc, const char *msg1,
144  const char *KP=0, int krc=0, bool isClient=false);
145 static int get_krbCreds(char *KP, krb5_creds **krb_creds);
146  void SetAddr(krb5_address &ipadd);
147 
148 static XrdSysMutex krbContext; // Server
149 static XrdSysMutex krbClientContext;// Client
150 static int options; // Server
151 static int client_options;// Client
152 static krb5_context krb_context; // Server
153 static krb5_context krb_client_context; // Client
154 static krb5_ccache krb_client_ccache; // Client
155 static krb5_ccache krb_ccache; // Server
156 static krb5_keytab krb_keytab; // Server
157 static krb5_principal krb_principal; // Server
158 
159 static char *Principal; // Server's principal name
160 static char *Parms; // Server parameters
161 
162 static char ExpFile[XrdSecMAXPATHLEN]; // Server: (template for)
163  // file to export token
164 int exp_krbTkn(XrdSecCredentials *cred, XrdOucErrInfo *erp);
165 int get_krbFwdCreds(char *KP, krb5_data *outdata);
166 
167 XrdNetAddrInfo epAddr;
168 char CName[256]; // Kerberos limit
169 char *Service; // Target principal for client
170 char Step; // Indicates at which step we are
171 krb5_auth_context AuthContext; // Authetication context
172 krb5_auth_context AuthClientContext; // Authetication context
173 krb5_ticket *Ticket; // Ticket associated to client authentication
174 krb5_creds *Creds; // Client: credentials
175 };
176 
177 /******************************************************************************/
178 /* S t a t i c D a t a */
179 /******************************************************************************/
180 
181 XrdSysMutex XrdSecProtocolkrb5::krbContext; // Server
182 XrdSysMutex XrdSecProtocolkrb5::krbClientContext; // Client
183 
184 int XrdSecProtocolkrb5::client_options = 0;// Client
185 int XrdSecProtocolkrb5::options = 0; // Server
186 krb5_context XrdSecProtocolkrb5::krb_context; // Server
187 krb5_context XrdSecProtocolkrb5::krb_client_context; // Client
188 krb5_ccache XrdSecProtocolkrb5::krb_client_ccache; // Client
189 krb5_ccache XrdSecProtocolkrb5::krb_ccache; // Server
190 krb5_keytab XrdSecProtocolkrb5::krb_keytab = NULL; // Server
191 krb5_principal XrdSecProtocolkrb5::krb_principal; // Server
192 
193 char *XrdSecProtocolkrb5::Principal = 0; // Server
194 char *XrdSecProtocolkrb5::Parms = 0; // Server
195 
196 char XrdSecProtocolkrb5::ExpFile[XrdSecMAXPATHLEN] = "/tmp/krb5cc_<uid>";
197 
198 /******************************************************************************/
199 /* D e l e t e */
200 /******************************************************************************/
201 
203 {
204  if (Parms) {free(Parms); Parms = 0;}
205  if (Creds) krb5_free_creds(krb_context, Creds);
206  if (Ticket) krb5_free_ticket(krb_context, Ticket);
207  if (AuthContext) krb5_auth_con_free(krb_context, AuthContext);
208  if (AuthClientContext) krb5_auth_con_free(krb_client_context, AuthClientContext);
209  if (Entity.host) free(Entity.host);
210  if (Service) free(Service);
211  delete this;
212 }
213 
214 /******************************************************************************/
215 /* g e t C r e d e n t i a l s */
216 /******************************************************************************/
217 
219  XrdOucErrInfo *error)
220 {
221  char *buff;
222  int bsz;
223  krb_rc rc;
224  krb5_data outbuf;
225  CLDBG("getCredentials");
226 // Supply null credentials if so needed for this protocol
227 //
228  if (!Service)
229  {CLDBG("Null credentials supplied.");
230  return new XrdSecCredentials(0,0);
231  }
232 
233  CLDBG("context lock");
234  krbClientContext.Lock();
235  CLDBG("context locked");
236 
237 // We support passing the credential cache path via Url parameter
238 //
239  char *ccn = (error && error->getEnv()) ? error->getEnv()->Get("xrd.k5ccname") : 0;
240  const char *kccn = ccn ? (const char *)ccn : getenv("KRB5CCNAME");
241  char ccname[128];
242  if (!kccn)
243  {snprintf(ccname, 128, "/tmp/krb5cc_%d", geteuid());
244  if (access(ccname, R_OK) == 0)
245  {kccn = ccname;}
246  }
247  CLDBG((kccn ? kccn : "credentials cache unset"));
248 
249 // Initialize the context and get the cache default.
250 //
251  if ((rc = krb5_init_context(&krb_client_context)))
252  {krbClientContext.UnLock();
253  Fatal(error, ENOPROTOOPT, "Kerberos initialization failed", Service, rc);
254  return (XrdSecCredentials *)0;
255  }
256 
257  CLDBG("init context");
258 
259 // Set the name of the default credentials cache for the Kerberos context
260 //
261  if ((rc = krb5_cc_set_default_name(krb_client_context, kccn)))
262  {krbClientContext.UnLock();
263  Fatal(error, ENOPROTOOPT, "Kerberos default credentials cache setting failed", Service, rc);
264  return (XrdSecCredentials *)0;
265  }
266 
267  CLDBG("cc set default name");
268 
269 // Obtain the default cache location
270 //
271  if ((rc = krb5_cc_default(krb_client_context, &krb_client_ccache)))
272  {krbClientContext.UnLock();
273  Fatal(error, ENOPROTOOPT, "Unable to locate cred cache", Service, rc);
274  return (XrdSecCredentials *)0;
275  }
276 
277  CLDBG("cc default");
278 // Check if the server asked for a forwardable ticket
279 //
280  char *pfwd = 0;
281  if ((pfwd = (char *) strstr(Service,",fwd")))
282  {
283  client_options |= XrdSecEXPTKN;
284  *pfwd = 0;
285  }
286 
287 // Clear outgoing ticket and lock the kerberos context
288 //
289  outbuf.length = 0; outbuf.data = 0;
290 
291 // If this is not the first call, we are asked to send over a delegated ticket:
292 // we must create it first
293 // we save it into a file and return signalling the end of the hand-shake
294 //
295 
296  if (Step > 0)
297  {if ((rc = get_krbFwdCreds(Service, &outbuf)))
298  {krbClientContext.UnLock();
299  Fatal(error, ESRCH, "Unable to get forwarded credentials", Service, rc);
300  return (XrdSecCredentials *)0;
301  } else
302  {bsz = XrdSecPROTOIDLEN+outbuf.length;
303  if (!(buff = (char *)malloc(bsz)))
304  {krbClientContext.UnLock();
305  Fatal(error, ENOMEM, "Insufficient memory for credentials.", Service);
306  return (XrdSecCredentials *)0;
307  }
308  strcpy(buff, XrdSecPROTOIDENT);
309  memcpy((void *)(buff+XrdSecPROTOIDLEN),
310  (const void *)outbuf.data, (size_t)outbuf.length);
311  CLDBG("Returned " <<bsz <<" bytes of creds; p=" <<Service);
312  if (outbuf.data) free(outbuf.data);
313  krbClientContext.UnLock();
314  return new XrdSecCredentials(buff, bsz);
315  }
316  }
317 
318 // Increment the step
319 //
320  Step += 1;
321 
322 // Get a service ticket for this principal
323 //
324  bool caninittkn = (isatty(0) == 0 || isatty(1) == 0) ? 0 : 1;
325  const char *reinitcmd = (client_options & XrdSecEXPTKN) ? "kinit -f" : "kinit";
326  bool notdone = 1;
327  bool reinitdone = 0;
328  while (notdone)
329  {if ((rc = (krb_rc)get_krbCreds(Service, &Creds)))
330  { if (!(client_options & XrdSecINITTKN) || reinitdone || !caninittkn)
331  {krbClientContext.UnLock();
332  const char *m = (!(client_options & XrdSecINITTKN)) ?
333  "No or invalid credentials" : "Unable to get credentials";
334  Fatal(error, ESRCH, m, Service, rc);
335  return (XrdSecCredentials *)0;
336  } else {// Need to re-init
337  CLPRT("Ticket missing or invalid: re-init ");
338  rc = system(reinitcmd);
339  CLDBG("getCredentials: return code from '"<<reinitcmd<<
340  "': "<< rc);
341  reinitdone = 1;
342  continue;
343  }
344  }
345  if (client_options & XrdSecEXPTKN)
346  {// Make sure the ticket is forwardable
347  if (!(Creds->ticket_flags & TKT_FLG_FORWARDABLE))
348  { if ((client_options & XrdSecINITTKN) && !reinitdone && caninittkn)
349  { // Need to re-init
350  CLPRT("Existing ticket is not forwardable: re-init ");
351  rc = system(reinitcmd);
352  CLDBG("getCredentials: return code from '"<<reinitcmd<<
353  "': "<< rc);
354  reinitdone = 1;
355  continue;
356  } else {
357  krbClientContext.UnLock();
358  Fatal(error, ESRCH, "Existing ticket is not forwardable: cannot continue",
359  Service, rc);
360  return (XrdSecCredentials *)0;
361  }
362  }
363  }
364  // We are done
365  notdone = 0;
366  }
367 
368 // Set the RET_TIME flag in the authentication context
369 //
370  if ((rc = krb5_auth_con_init(krb_client_context, &AuthClientContext)))
371  {krbClientContext.UnLock();
372  Fatal(error, ESRCH, "Unable to init a new auth context", Service, rc);
373  return (XrdSecCredentials *)0;
374  }
375 
376 // Generate a kerberos-style authentication message
377 //
378  rc = krb5_mk_req_extended(krb_client_context, &AuthClientContext,
379  AP_OPTS_USE_SESSION_KEY,(krb5_data *)0, Creds,&outbuf);
380 
381 // Check if all succeeded. If so, copy the ticket into the buffer. We wish
382 // we could place the ticket directly into the buffer but architectural
383 // differences won't allow us that optimization.
384 //
385  if (!rc)
386  {bsz = XrdSecPROTOIDLEN+outbuf.length;
387  if (!(buff = (char *)malloc(bsz)))
388  {krbClientContext.UnLock();
389  Fatal(error, ENOMEM, "Insufficient memory for credentials.", Service);
390  return (XrdSecCredentials *)0;
391  }
392  strcpy(buff, XrdSecPROTOIDENT);
393  memcpy((void *)(buff+XrdSecPROTOIDLEN),
394  (const void *)outbuf.data, (size_t)outbuf.length);
395  CLDBG("Returned " <<bsz <<" bytes of creds; p=" <<Service);
396  if (outbuf.data) free(outbuf.data);
397  krbClientContext.UnLock();
398  return new XrdSecCredentials(buff, bsz);
399  }
400 
401 // Diagnose the failure
402 //
403  if (outbuf.data) free(outbuf.data);
404  krbClientContext.UnLock();
405  Fatal(error, EACCES, "Unable to get credentials", Service, rc);
406  return (XrdSecCredentials *)0;
407 }
408 
409 /******************************************************************************/
410 /* S e r v e r O r i e n t e d M e t h o d s */
411 /******************************************************************************/
412 /******************************************************************************/
413 /* A u t h e n t i c a t e */
414 /******************************************************************************/
415 
417  XrdSecParameters **parms,
418  XrdOucErrInfo *error)
419 {
420  krb5_data inbuf; /* Kerberos data */
421  krb5_address ipadd;
422  krb_rc rc = 0;
423  const char *iferror = 0;
424  std::string cPrincipal;
425  bool isCP = false;
426 
427 // Check if we have any credentials or if no credentials really needed.
428 // In either case, use host name as client name
429 //
430  if (cred->size <= (int)XrdSecPROTOIDLEN || !cred->buffer)
431  {strncpy(Entity.prot, "host", sizeof(Entity.prot));
432  return 0;
433  }
434 
435 // Check if this is a recognized protocol
436 //
437  if (strcmp(cred->buffer, XrdSecPROTOIDENT))
438  {char emsg[256];
439  snprintf(emsg, sizeof(emsg),
440  "Authentication protocol id mismatch (%.4s != %.4s).",
441  XrdSecPROTOIDENT, cred->buffer);
442  Fatal(error, EINVAL, emsg, Principal);
443  return -1;
444  }
445 
446  CLDBG("protocol check");
447 
448  char printit[4096];
449  sprintf(printit,"Step is %d",Step);
450  CLDBG(printit);
451 // If this is not the first call the buffer contains a forwarded token:
452 // we save it into a file and return signalling the end of the hand-shake
453 //
454  if (Step > 0)
455  {if ((rc = exp_krbTkn(cred, error)))
456  iferror = "Unable to export the token to file";
457  if (rc && iferror) {
458  krbContext.UnLock();
459  return Fatal(error, EINVAL, iferror, Principal, rc);
460  }
461  krbContext.UnLock();
462 
463  return 0;
464  }
465 
466  CLDBG("protocol check");
467 
468 // Increment the step
469 //
470  Step += 1;
471 
472 // Indicate who we are
473 //
474  strncpy(Entity.prot, XrdSecPROTOIDENT, sizeof(Entity.prot));
475 
476 // Create a kerberos style ticket and obtain the kerberos mutex
477 //
478 
479  CLDBG("Context Lock");
480 
481  inbuf.length = cred->size -XrdSecPROTOIDLEN;
482  inbuf.data = &cred->buffer[XrdSecPROTOIDLEN];
483 
484  krbContext.Lock();
485 
486 // Check if whether the IP address in the credentials must match that of
487 // the incoming host.
488 //
489  CLDBG("Context Locked");
490  if (!(XrdSecProtocolkrb5::options & XrdSecNOIPCHK))
491  {SetAddr(ipadd);
492  iferror = "Unable to validate ip address;";
493  if (!(rc=krb5_auth_con_init(krb_context, &AuthContext)))
494  rc=krb5_auth_con_setaddrs(krb_context, AuthContext, NULL, &ipadd);
495  }
496 
497 // Decode the credentials and extract client's name
498 //
499  if (!rc)
500  {if ((rc = krb5_rd_req(krb_context, &AuthContext, &inbuf,
501  (krb5_const_principal)krb_principal,
502  krb_keytab, NULL, &Ticket)))
503  iferror = "Unable to authenticate credentials;";
504  else if ((rc = krb5_aname_to_localname(krb_context,
505  Ticket->enc_part2->client,
506  sizeof(CName)-1, CName)))
507  iferror = "Unable to get client localname";
508 
509  if (rc)
510  {char* cpName;
511  int ec;
512  isCP = true;
513  if (!Ticket || !Ticket->enc_part2)
514  cPrincipal = "[principal not available]";
515  else if ((ec = krb5_unparse_name(krb_context,
516  (krb5_const_principal)Ticket->enc_part2->client,
517  (char **)&cpName)))
518  {char mBuff[1024];
519  snprintf(mBuff, sizeof(mBuff),
520  "[principal unparse failed; %s]", krb_etxt(ec));
521  cPrincipal = mBuff;
522  } else {
523  cPrincipal = cpName;
524  krb5_free_unparsed_name(krb_context, cpName);
525  }
526  }
527  }
528 
529 // Make sure the name is null-terminated
530 //
531  CName[sizeof(CName)-1] = '\0';
532 
533 // If requested, ask the client for a forwardable token
534  int hsrc = 0;
535  if (!rc && XrdSecProtocolkrb5::options & XrdSecEXPTKN) {
536  // We just ask for more; the client knows what to send over
537  hsrc = 1;
538  // We need to fill-in a fake buffer
539  int len = strlen("fwdtgt") + 1;
540  char *buf = (char *) malloc(len);
541  memcpy(buf, "fwdtgt", len-1);
542  buf[len-1] = 0;
543  *parms = new XrdSecParameters(buf, len);
544  }
545 
546 // Release any allocated storage at this point and unlock mutex
547 //
548  krbContext.UnLock();
549 
550 // Diagnose any errors
551 //
552  if (rc && iferror)
553  return Fatal(error, EACCES, iferror,
554  (isCP ? cPrincipal.c_str() : Principal), rc, isCP);
555 
556 // All done
557 //
558  return hsrc;
559 }
560 
561 /******************************************************************************/
562 /* I n i t i a l i z a t i o n M e t h o d s */
563 /******************************************************************************/
564 /******************************************************************************/
565 /* I n i t */
566 /******************************************************************************/
567 
568 int XrdSecProtocolkrb5::Init(XrdOucErrInfo *erp, char *KP, char *kfn)
569 {
570  krb_rc rc;
571  char buff[2048];
572 
573 // Create a kerberos context. There is one such context per protocol object.
574 //
575 
576 // If we have no principal then this is a client-side call: initializations are done
577 // in getCredentials to allow for multiple client principals
578 //
579  if (!KP) return 0;
580 
581  if ((rc = krb5_init_context(&krb_context)))
582  return Fatal(erp, ENOPROTOOPT, "Kerberos initialization failed", KP, rc);
583 
584 // Obtain the default cache location
585 //
586  if ((rc = krb5_cc_default(krb_context, &krb_ccache)))
587  return Fatal(erp, ENOPROTOOPT, "Unable to locate cred cache", KP, rc);
588 
589 // Try to resolve the keyfile name
590 //
591  if (kfn && *kfn)
592  {if ((rc = krb5_kt_resolve(krb_context, kfn, &krb_keytab)))
593  {snprintf(buff, sizeof(buff), "Unable to find keytab '%s';", kfn);
594  return Fatal(erp, ESRCH, buff, Principal, rc);
595  }
596  } else {
597  krb5_kt_default(krb_context, &krb_keytab);
598  }
599 
600 // Keytab name
601 //
602  char krb_kt_name[1024];
603  if ((rc = krb5_kt_get_name(krb_context, krb_keytab, &krb_kt_name[0], 1024)))
604  {snprintf(buff, sizeof(buff), "Unable to get keytab name;");
605  return Fatal(erp, ESRCH, buff, Principal, rc);
606  }
607 
608 // Check if we can read access the keytab file
609 //
610  krb5_kt_cursor ktc;
611  if ((rc = krb5_kt_start_seq_get(krb_context, krb_keytab, &ktc)))
612  {snprintf(buff, sizeof(buff), "Unable to start sequence on the keytab file %s", krb_kt_name);
613  return Fatal(erp, EPERM, buff, Principal, rc);
614  }
615  if ((rc = krb5_kt_end_seq_get(krb_context, krb_keytab, &ktc)))
616  {snprintf(buff, sizeof(buff), "WARNING: unable to end sequence on the keytab file %s", krb_kt_name);
617  CLPRT(buff);
618  }
619 
620 // Now, extract the "principal/instance@realm" from the stream
621 //
622  if ((rc = krb5_parse_name(krb_context,KP,&krb_principal)))
623  return Fatal(erp, EINVAL, "Cannot parse service principal name", KP, rc);
624 
625 // Establish the correct principal to use
626 //
627  if ((rc = krb5_unparse_name(krb_context,(krb5_const_principal)krb_principal,
628  (char **)&Principal)))
629  return Fatal(erp, EINVAL, "Unable to unparse service principal;", KP, rc);
630 
631 // All done
632 //
633  return 0;
634 }
635 
636 /******************************************************************************/
637 /* P r i v a t e M e t h o d s */
638 /******************************************************************************/
639 /******************************************************************************/
640 /* F a t a l */
641 /******************************************************************************/
642 
643 int XrdSecProtocolkrb5::Fatal(XrdOucErrInfo *erp, int rc, const char *msg,
644  const char *KP, int krc, bool isClient)
645 {
646  const char *msgv[8];
647  int k, i = 0;
648 
649  msgv[i++] = "Seckrb5: "; //0
650  msgv[i++] = msg; //1
651  if (krc) {msgv[i++] = "; "; //2
652  msgv[i++] = krb_etxt(krc); //3
653  }
654  if (KP) {const char* who = (isClient ? "(client=" : "(server=");
655  msgv[i++] = who; //4
656  msgv[i++] = KP; //5
657  msgv[i++] = ")."; //6
658  }
659  if (erp) erp->setErrInfo(rc, msgv, i);
660  else {for (k = 0; k < i; k++) std::cerr <<msgv[k];
661  std::cerr <<std::endl;
662  }
663 
664  return -1;
665 }
666 
667 /******************************************************************************/
668 /* g e t _ k r b C r e d s */
669 /******************************************************************************/
670 
671 // Warning! The krbClientContext lock must be held prior to calling this routine
672 
673 int XrdSecProtocolkrb5::get_krbCreds(char *KP, krb5_creds **krb_creds)
674 {
675  krb_rc rc;
676  krb5_principal the_principal;
677  krb5_creds mycreds;
678 
679 // Clear my credentials
680 //
681  memset((char *)&mycreds, 0, sizeof(mycreds));
682 
683 // Setup the "principal/instance@realm"
684 //
685  if ((rc = krb5_parse_name(krb_client_context,KP,&the_principal)))
686  {CLDBG("get_krbCreds: Cannot parse service name;" <<krb_etxt(rc));
687  return rc;
688  }
689 
690 // Copy the current target principal into the credentials
691 //
692  if ((rc = krb5_copy_principal(krb_client_context, the_principal, &mycreds.server)))
693  {CLDBG("get_krbCreds: err copying principal to creds; " <<krb_etxt(rc));
694  krb5_free_principal(krb_client_context, the_principal);
695  return rc;
696  }
697 
698 // Get our principal name
699 //
700  if ((rc = krb5_cc_get_principal(krb_client_context, krb_client_ccache, &mycreds.client)))
701  {CLDBG("get_krbCreds: err copying client name to creds; " <<krb_etxt(rc));
702  krb5_free_cred_contents(krb_client_context, &mycreds);
703  krb5_free_principal(krb_client_context, the_principal);
704  return rc;
705  }
706 
707 // Now get the credentials (free our local info)
708 //
709  rc = krb5_get_credentials(krb_client_context, 0, krb_client_ccache, &mycreds, krb_creds);
710  krb5_free_cred_contents(krb_client_context, &mycreds);
711  krb5_free_principal(krb_client_context, the_principal);
712 
713 // Check if all went well
714 //
715  if (rc) {CLDBG("get_krbCreds: unable to get creds; " <<krb_etxt(rc));}
716  return rc;
717 }
718 
719 /******************************************************************************/
720 /* g e t _ k r b F w d C r e d s */
721 /******************************************************************************/
722 
723 int XrdSecProtocolkrb5::get_krbFwdCreds(char *KP, krb5_data *outdata)
724 {
725  int rc;
726  krb5_principal client, server;
727 
728 // Fill-in our principal
729 //
730  if ((rc = krb5_cc_get_principal(krb_client_context, krb_client_ccache, &client)))
731  {CLDBG("get_krbFwdCreds: err filling client principal; " <<krb_etxt(rc));
732  return rc;
733  }
734 
735 // Fill-in target (service) principal
736 //
737  if ((rc = krb5_parse_name(krb_client_context, KP, &server)))
738  {CLDBG("get_krbFwdCreds: Cannot parse service principal;" <<krb_etxt(rc));
739  return rc;
740  }
741 
742 // Set the timestamp in the authentication context
743 //
744  if ((rc = krb5_auth_con_setflags(krb_client_context, AuthClientContext,
745  KRB5_AUTH_CONTEXT_RET_TIME)))
746  {CLDBG("Unable to set KRB5_AUTH_CONTEXT_RET_TIME"
747  " in the authentication context" << krb_etxt(rc));
748  return rc;
749  }
750 
751 // Acquire a TGT for use at a remote host system
752 //
753  if ((rc = krb5_fwd_tgt_creds(krb_client_context, AuthClientContext, 0 /*host*/,
754  client, server, krb_client_ccache, true,
755  outdata)))
756  {CLDBG("get_krbFwdCreds: err getting forwarded ticket;" <<krb_etxt(rc));
757  return rc;
758  }
759 
760 // Done
761 //
762  return rc;
763 }
764 
765 /******************************************************************************/
766 /* e x p _ k r b T k n */
767 /******************************************************************************/
768 
769 int XrdSecProtocolkrb5::exp_krbTkn(XrdSecCredentials *cred, XrdOucErrInfo *erp)
770 {
771  krb5_address ipadd;
772  int rc = 0;
773 
774 // Create the cache filename, expanding the keywords, if needed
775 //
776  char ccfile[XrdSecMAXPATHLEN];
777  strcpy(ccfile, XrdSecProtocolkrb5::ExpFile);
778  int nlen = strlen(ccfile);
779  char *pusr = (char *) strstr(&ccfile[0], "<user>");
780  if (pusr)
781  {int ln = strlen(CName);
782  if (ln != 6) {
783  // Adjust the space
784  int lm = strlen(ccfile) - (int)(pusr + 6 - &ccfile[0]);
785  memmove(pusr+ln, pusr+6, lm);
786  }
787  // Copy the name
788  memcpy(pusr, CName, ln);
789  // Adjust the length
790  nlen += (ln - 6);
791  }
792  char *puid = (char *) strstr(&ccfile[0], "<uid>");
793  struct passwd *pw;
794  XrdSysPwd thePwd(CName, &pw);
795  if (puid)
796  {char cuid[20] = {0};
797  if (pw)
798  sprintf(cuid, "%d", pw->pw_uid);
799  int ln = strlen(cuid);
800  if (ln != 5) {
801  // Adjust the space
802  int lm = strlen(ccfile) - (int)(puid + 5 - &ccfile[0]);
803  memmove(puid+ln, pusr+5, lm);
804  }
805  // Copy the name
806  memcpy(puid, cuid, ln);
807  // Adjust the length
808  nlen += (ln - 5);
809  }
810 
811 // Terminate to the new length
812 //
813  ccfile[nlen] = 0;
814 
815 // Point the received creds
816 //
817  krbContext.Lock();
818  krb5_data forwardCreds;
819  forwardCreds.data = &cred->buffer[XrdSecPROTOIDLEN];
820  forwardCreds.length = cred->size -XrdSecPROTOIDLEN;
821 
822 // Get the replay cache
823 //
824  krb5_rcache rcache;
825  if ((rc = krb5_get_server_rcache(krb_context,
826  krb5_princ_component(krb_context, krb_principal, 0),
827  &rcache)))
828  return rc;
829  if ((rc = krb5_auth_con_setrcache(krb_context, AuthContext, rcache)))
830  return rc;
831 
832 // Fill-in remote address
833 //
834  SetAddr(ipadd);
835  if ((rc = krb5_auth_con_setaddrs(krb_context, AuthContext, 0, &ipadd)))
836  return rc;
837 
838 // Readout the credentials
839 //
840  krb5_creds **creds = 0;
841  if ((rc = krb5_rd_cred(krb_context, AuthContext,
842  &forwardCreds, &creds, 0)))
843  return rc;
844 
845 // Resolve cache name
846  krb5_ccache cache = 0;
847  if ((rc = krb5_cc_resolve(krb_context, ccfile, &cache)))
848  return rc;
849 
850 // Init cache
851 //
852  if ((rc = krb5_cc_initialize(krb_context, cache,
853  Ticket->enc_part2->client)))
854  return rc;
855 
856 // Store credentials in cache
857 //
858  if ((rc = krb5_cc_store_cred(krb_context, cache, *creds)))
859  return rc;
860 
861 // Close cache
862  if ((rc = krb5_cc_close(krb_context, cache)))
863  return rc;
864 
865 // Change permission and ownership of the file
866 //
867  if (chmod(ccfile, 0600) == -1)
868  return Fatal(erp, errno, "Unable to change file permissions;", ccfile, 0);
869 
870 // Done
871 //
872  return 0;
873 }
874 
875 /******************************************************************************/
876 /* S e t A d d r */
877 /******************************************************************************/
878 
879 void XrdSecProtocolkrb5::SetAddr(krb5_address &ipadd)
880 {
881 // The below is a hack but that's how it is actually done!
882 //
883  if (epAddr.Family() == AF_INET6)
884  {struct sockaddr_in6 *ip = (struct sockaddr_in6 *)epAddr.SockAddr();
885  ipadd.addrtype = ADDRTYPE_INET6;
886  ipadd.length = sizeof(ip->sin6_addr);
887  ipadd.contents = (krb5_octet *)&ip->sin6_addr;
888  } else {
889  struct sockaddr_in *ip = (struct sockaddr_in *)epAddr.SockAddr();
890  ipadd.addrtype = ADDRTYPE_INET;
891  ipadd.length = sizeof(ip->sin_addr);
892  ipadd.contents = (krb5_octet *)&ip->sin_addr;
893  }
894 }
895 
896 /******************************************************************************/
897 /* X r d S e c p r o t o c o l k r b 5 I n i t */
898 /******************************************************************************/
899 
900 extern "C"
901 {
902 char *XrdSecProtocolkrb5Init(const char mode,
903  const char *parms,
904  XrdOucErrInfo *erp)
905 {
906  char *op, *KPrincipal=0, *Keytab=0, *ExpFile=0;
907  char parmbuff[1024];
908  XrdOucTokenizer inParms(parmbuff);
909  int options = XrdSecNOIPCHK;
910  static bool serverinitialized = false;
911 
912 // For client-side one-time initialization, we only need to set debug flag and
913 // initialize the kerberos context and cache location.
914 //
915  if ((mode == 'c') || (serverinitialized))
916  {
917  int opts = 0;
918  if (getenv("XrdSecDEBUG")) opts |= XrdSecDEBUG;
919  if (getenv("XrdSecKRB5INITTKN")) opts |= XrdSecINITTKN;
921  return (XrdSecProtocolkrb5::Init(erp) ? (char *)0 : (char *)"");
922  }
923 
924  if (!serverinitialized) {
925  serverinitialized = true;
926  }
927 
928 // Duplicate the parms
929 //
930  if (parms) strlcpy(parmbuff, parms, sizeof(parmbuff));
931  else {char *msg = (char *)"Seckrb5: Kerberos parameters not specified.";
932  if (erp) erp->setErrInfo(EINVAL, msg);
933  else std::cerr <<msg <<std::endl;
934  return (char *)0;
935  }
936 
937 // Expected parameters: [<keytab>] [-ipchk] [-exptkn[:filetemplate]] <principal>
938 //
939  if (inParms.GetLine())
940  {if ((op = inParms.GetToken()) && *op == '/')
941  {Keytab = op; op = inParms.GetToken();}
942  if (op && !strcmp(op, "-ipchk"))
943  {options &= ~XrdSecNOIPCHK;
944  op = inParms.GetToken();
945  }
946  if (op && !strncmp(op, "-exptkn", 7))
947  {options |= XrdSecEXPTKN;
948  if (op[7] == ':') ExpFile = op+8;
949  op = inParms.GetToken();
950  }
951  KPrincipal = strdup(op);
952  }
953 
954  if (ExpFile)
955  fprintf(stderr,"Template for exports: %s\n", ExpFile);
956  else
957  fprintf(stderr,"Template for exports not set\n");
958 
959 // Now make sure that we have all the right info
960 //
961  if (!KPrincipal)
962  {char *msg = (char *)"Seckrb5: Kerberos principal not specified.";
963  if (erp) erp->setErrInfo(EINVAL, msg);
964  else std::cerr <<msg <<std::endl;
965  return (char *)0;
966  }
967 
968 // Expand possible keywords in the principal
969 //
970  int plen = strlen(KPrincipal);
971  int lkey = strlen("<host>");
972  char *phost = (char *) strstr(&KPrincipal[0], "<host>");
973  if (phost)
974  {char *hn = XrdNetUtils::MyHostName();
975  if (hn)
976  {int lhn = strlen(hn);
977  if (lhn != lkey) {
978  // Allocate, if needed
979  int lnew = plen - lkey + lhn;
980  if (lnew > plen) {
981  KPrincipal = (char *) realloc(KPrincipal, lnew+1);
982  KPrincipal[lnew] = 0;
983  phost = (char *) strstr(&KPrincipal[0], "<host>");
984  }
985  // Adjust the space
986  int lm = plen - (int)(phost + lkey - &KPrincipal[0]);
987  memmove(phost + lhn, phost + lkey, lm);
988  }
989  // Copy the name
990  memcpy(phost, hn, lhn);
991  // Cleanup
992  free(hn);
993  }
994  }
995 
996 // Now initialize the server
997 //
998  options |= XrdSecDEBUG;
1000  XrdSecProtocolkrb5::setOpts(options);
1001  if (!XrdSecProtocolkrb5::Init(erp, KPrincipal, Keytab))
1002  {free(KPrincipal);
1003  int lpars = strlen(XrdSecProtocolkrb5::getPrincipal());
1004  if (options & XrdSecEXPTKN)
1005  lpars += strlen(",fwd");
1006  char *params = (char *)malloc(lpars+1);
1007  if (params)
1008  {memset(params,0,lpars+1);
1009  strcpy(params,XrdSecProtocolkrb5::getPrincipal());
1010  if (options & XrdSecEXPTKN)
1011  strcat(params,",fwd");
1013  return params;
1014  }
1015  return (char *)0;
1016  }
1017 
1018 // Failure
1019 //
1020  free(KPrincipal);
1021  return (char *)0;
1022 }
1023 }
1024 
1025 /******************************************************************************/
1026 /* X r d S e c P r o t o c o l k r b 5 O b j e c t */
1027 /******************************************************************************/
1028 
1029 extern "C"
1030 {
1032  const char *hostname,
1033  XrdNetAddrInfo &endPoint,
1034  const char *parms,
1035  XrdOucErrInfo *erp)
1036 {
1037  XrdSecProtocolkrb5 *prot;
1038  char *KPrincipal=0;
1039 
1040 // If this is a client call, then we need to get the target principal from the
1041 // parms (which must be the first and only token). For servers, we use the
1042 // context we established at initialization time.
1043 //
1044  if (mode == 'c')
1045  {if ((KPrincipal = (char *)parms)) while(*KPrincipal == ' ') KPrincipal++;
1046  if (!KPrincipal || !*KPrincipal)
1047  {char *msg = (char *)"Seckrb5: Kerberos principal not specified.";
1048  if (erp) erp->setErrInfo(EINVAL, msg);
1049  else std::cerr <<msg <<std::endl;
1050  return (XrdSecProtocol *)0;
1051  }
1052  }
1053 
1054 // Get a new protocol object
1055 //
1056  if (!(prot = new XrdSecProtocolkrb5(KPrincipal, hostname, endPoint)))
1057  {char *msg = (char *)"Seckrb5: Insufficient memory for protocol.";
1058  if (erp) erp->setErrInfo(ENOMEM, msg);
1059  else std::cerr <<msg <<std::endl;
1060  return (XrdSecProtocol *)0;
1061  }
1062 
1063 // All done
1064 //
1065  return prot;
1066 }
1067 
1068 void
1069  __eprintf (const char *string, const char *expression,
1070  unsigned int line, const char *filename)
1071  {
1072  fprintf (stderr, string, expression, line, filename);
1073  fflush (stderr);
1074  abort ();
1075  }
1076 }
XrdVERSIONINFO(XrdClGetPlugIn, XrdClGetPlugIn) extern "C"
void Fatal(const char *op, const char *target)
Definition: XrdCrc32c.cc:58
int access(const char *path, int amode)
int fflush(FILE *stream)
XrdSecBuffer XrdSecParameters
XrdSecBuffer XrdSecCredentials
XrdSecProtocol * XrdSecProtocolkrb5Object(const char mode, const char *hostname, XrdNetAddrInfo &endPoint, const char *parms, XrdOucErrInfo *erp)
char * XrdSecProtocolkrb5Init(const char mode, const char *parms, XrdOucErrInfo *erp)
#define XrdSecDEBUG
#define CLDBG(x)
#define krb_etxt(x)
#define CLPRT(x)
#define XrdSecPROTOIDLEN
#define XrdSecMAXPATHLEN
#define XrdSecEXPTKN
#define XrdSecPROTOIDENT
krb5_error_code krb_rc
#define XrdSecINITTKN
#define XrdSecNOIPCHK
void __eprintf(const char *string, const char *expression, unsigned int line, const char *filename)
struct myOpts opts
int emsg(int rc, char *msg)
size_t strlcpy(char *dst, const char *src, size_t sz)
const sockaddr * SockAddr()
int Family() const
static char * MyHostName(const char *eName="*unknown*", const char **eText=0)
Definition: XrdNetUtils.cc:667
char * Get(const char *varname)
Definition: XrdOucEnv.hh:69
int setErrInfo(int code, const char *emsg)
XrdOucEnv * getEnv()
char * GetToken(char **rest=0, int lowcase=0)
XrdNetAddrInfo * addrInfo
Entity's connection details.
Definition: XrdSecEntity.hh:80
char prot[XrdSecPROTOIDSIZE]
Auth protocol used (e.g. krb5)
Definition: XrdSecEntity.hh:67
char * name
Entity's name.
Definition: XrdSecEntity.hh:69
char * host
Entity's host name dnr dependent.
Definition: XrdSecEntity.hh:70
XrdSecEntity Entity
int Authenticate(XrdSecCredentials *cred, XrdSecParameters **parms, XrdOucErrInfo *einfo=0)
XrdSecProtocolkrb5(const char *KP, const char *hname, XrdNetAddrInfo &endPoint)
void Delete()
Delete the protocol object. DO NOT use C++ delete() on this object.
static void setOpts(int opts)
static void setClientOpts(int opts)
static char * getPrincipal()
static void setExpFile(char *expfile)
static void setParms(char *param)
static int Init(XrdOucErrInfo *einfo, char *KP=0, char *kfn=0)
XrdSecCredentials * getCredentials(XrdSecParameters *parm=0, XrdOucErrInfo *einfo=0)
friend class XrdSecProtocolDummy
XrdSsiService * Service
Generic structure to pass security information back and forth.
char * buffer
Pointer to the buffer.
int size
Size of the buffer or length of data in the buffer.