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 ((ec = krb5_unparse_name(krb_context,
514  (krb5_const_principal)Ticket->enc_part2->client,
515  (char **)&cpName)))
516  {char mBuff[1024];
517  snprintf(mBuff, sizeof(mBuff),
518  "[principal unparse failed; %s]", krb_etxt(ec));
519  cPrincipal = mBuff;
520  } else {
521  cPrincipal = cpName;
522  krb5_free_unparsed_name(krb_context, cpName);
523  }
524  }
525  }
526 
527 // Make sure the name is null-terminated
528 //
529  CName[sizeof(CName)-1] = '\0';
530 
531 // If requested, ask the client for a forwardable token
532  int hsrc = 0;
533  if (!rc && XrdSecProtocolkrb5::options & XrdSecEXPTKN) {
534  // We just ask for more; the client knows what to send over
535  hsrc = 1;
536  // We need to fill-in a fake buffer
537  int len = strlen("fwdtgt") + 1;
538  char *buf = (char *) malloc(len);
539  memcpy(buf, "fwdtgt", len-1);
540  buf[len-1] = 0;
541  *parms = new XrdSecParameters(buf, len);
542  }
543 
544 // Release any allocated storage at this point and unlock mutex
545 //
546  krbContext.UnLock();
547 
548 // Diagnose any errors
549 //
550  if (rc && iferror)
551  return Fatal(error, EACCES, iferror,
552  (isCP ? cPrincipal.c_str() : Principal), rc, isCP);
553 
554 // All done
555 //
556  return hsrc;
557 }
558 
559 /******************************************************************************/
560 /* I n i t i a l i z a t i o n M e t h o d s */
561 /******************************************************************************/
562 /******************************************************************************/
563 /* I n i t */
564 /******************************************************************************/
565 
566 int XrdSecProtocolkrb5::Init(XrdOucErrInfo *erp, char *KP, char *kfn)
567 {
568  krb_rc rc;
569  char buff[2048];
570 
571 // Create a kerberos context. There is one such context per protocol object.
572 //
573 
574 // If we have no principal then this is a client-side call: initializations are done
575 // in getCredentials to allow for multiple client principals
576 //
577  if (!KP) return 0;
578 
579  if ((rc = krb5_init_context(&krb_context)))
580  return Fatal(erp, ENOPROTOOPT, "Kerberos initialization failed", KP, rc);
581 
582 // Obtain the default cache location
583 //
584  if ((rc = krb5_cc_default(krb_context, &krb_ccache)))
585  return Fatal(erp, ENOPROTOOPT, "Unable to locate cred cache", KP, rc);
586 
587 // Try to resolve the keyfile name
588 //
589  if (kfn && *kfn)
590  {if ((rc = krb5_kt_resolve(krb_context, kfn, &krb_keytab)))
591  {snprintf(buff, sizeof(buff), "Unable to find keytab '%s';", kfn);
592  return Fatal(erp, ESRCH, buff, Principal, rc);
593  }
594  } else {
595  krb5_kt_default(krb_context, &krb_keytab);
596  }
597 
598 // Keytab name
599 //
600  char krb_kt_name[1024];
601  if ((rc = krb5_kt_get_name(krb_context, krb_keytab, &krb_kt_name[0], 1024)))
602  {snprintf(buff, sizeof(buff), "Unable to get keytab name;");
603  return Fatal(erp, ESRCH, buff, Principal, rc);
604  }
605 
606 // Check if we can read access the keytab file
607 //
608  krb5_kt_cursor ktc;
609  if ((rc = krb5_kt_start_seq_get(krb_context, krb_keytab, &ktc)))
610  {snprintf(buff, sizeof(buff), "Unable to start sequence on the keytab file %s", krb_kt_name);
611  return Fatal(erp, EPERM, buff, Principal, rc);
612  }
613  if ((rc = krb5_kt_end_seq_get(krb_context, krb_keytab, &ktc)))
614  {snprintf(buff, sizeof(buff), "WARNING: unable to end sequence on the keytab file %s", krb_kt_name);
615  CLPRT(buff);
616  }
617 
618 // Now, extract the "principal/instance@realm" from the stream
619 //
620  if ((rc = krb5_parse_name(krb_context,KP,&krb_principal)))
621  return Fatal(erp, EINVAL, "Cannot parse service principal name", KP, rc);
622 
623 // Establish the correct principal to use
624 //
625  if ((rc = krb5_unparse_name(krb_context,(krb5_const_principal)krb_principal,
626  (char **)&Principal)))
627  return Fatal(erp, EINVAL, "Unable to unparse service principal;", KP, rc);
628 
629 // All done
630 //
631  return 0;
632 }
633 
634 /******************************************************************************/
635 /* P r i v a t e M e t h o d s */
636 /******************************************************************************/
637 /******************************************************************************/
638 /* F a t a l */
639 /******************************************************************************/
640 
641 int XrdSecProtocolkrb5::Fatal(XrdOucErrInfo *erp, int rc, const char *msg,
642  const char *KP, int krc, bool isClient)
643 {
644  const char *msgv[8];
645  int k, i = 0;
646 
647  msgv[i++] = "Seckrb5: "; //0
648  msgv[i++] = msg; //1
649  if (krc) {msgv[i++] = "; "; //2
650  msgv[i++] = krb_etxt(krc); //3
651  }
652  if (KP) {const char* who = (isClient ? "(client=" : "(server=");
653  msgv[i++] = who; //4
654  msgv[i++] = KP; //5
655  msgv[i++] = ")."; //6
656  }
657  if (erp) erp->setErrInfo(rc, msgv, i);
658  else {for (k = 0; k < i; k++) std::cerr <<msgv[k];
659  std::cerr <<std::endl;
660  }
661 
662  return -1;
663 }
664 
665 /******************************************************************************/
666 /* g e t _ k r b C r e d s */
667 /******************************************************************************/
668 
669 // Warning! The krbClientContext lock must be held prior to calling this routine
670 
671 int XrdSecProtocolkrb5::get_krbCreds(char *KP, krb5_creds **krb_creds)
672 {
673  krb_rc rc;
674  krb5_principal the_principal;
675  krb5_creds mycreds;
676 
677 // Clear my credentials
678 //
679  memset((char *)&mycreds, 0, sizeof(mycreds));
680 
681 // Setup the "principal/instance@realm"
682 //
683  if ((rc = krb5_parse_name(krb_client_context,KP,&the_principal)))
684  {CLDBG("get_krbCreds: Cannot parse service name;" <<krb_etxt(rc));
685  return rc;
686  }
687 
688 // Copy the current target principal into the credentials
689 //
690  if ((rc = krb5_copy_principal(krb_client_context, the_principal, &mycreds.server)))
691  {CLDBG("get_krbCreds: err copying principal to creds; " <<krb_etxt(rc));
692  krb5_free_principal(krb_client_context, the_principal);
693  return rc;
694  }
695 
696 // Get our principal name
697 //
698  if ((rc = krb5_cc_get_principal(krb_client_context, krb_client_ccache, &mycreds.client)))
699  {CLDBG("get_krbCreds: err copying client name to creds; " <<krb_etxt(rc));
700  krb5_free_cred_contents(krb_client_context, &mycreds);
701  krb5_free_principal(krb_client_context, the_principal);
702  return rc;
703  }
704 
705 // Now get the credentials (free our local info)
706 //
707  rc = krb5_get_credentials(krb_client_context, 0, krb_client_ccache, &mycreds, krb_creds);
708  krb5_free_cred_contents(krb_client_context, &mycreds);
709  krb5_free_principal(krb_client_context, the_principal);
710 
711 // Check if all went well
712 //
713  if (rc) {CLDBG("get_krbCreds: unable to get creds; " <<krb_etxt(rc));}
714  return rc;
715 }
716 
717 /******************************************************************************/
718 /* g e t _ k r b F w d C r e d s */
719 /******************************************************************************/
720 
721 int XrdSecProtocolkrb5::get_krbFwdCreds(char *KP, krb5_data *outdata)
722 {
723  int rc;
724  krb5_principal client, server;
725 
726 // Fill-in our principal
727 //
728  if ((rc = krb5_cc_get_principal(krb_client_context, krb_client_ccache, &client)))
729  {CLDBG("get_krbFwdCreds: err filling client principal; " <<krb_etxt(rc));
730  return rc;
731  }
732 
733 // Fill-in target (service) principal
734 //
735  if ((rc = krb5_parse_name(krb_client_context, KP, &server)))
736  {CLDBG("get_krbFwdCreds: Cannot parse service principal;" <<krb_etxt(rc));
737  return rc;
738  }
739 
740 // Set the timestamp in the authentication context
741 //
742  if ((rc = krb5_auth_con_setflags(krb_client_context, AuthClientContext,
743  KRB5_AUTH_CONTEXT_RET_TIME)))
744  {CLDBG("Unable to set KRB5_AUTH_CONTEXT_RET_TIME"
745  " in the authentication context" << krb_etxt(rc));
746  return rc;
747  }
748 
749 // Acquire a TGT for use at a remote host system
750 //
751  if ((rc = krb5_fwd_tgt_creds(krb_client_context, AuthClientContext, 0 /*host*/,
752  client, server, krb_client_ccache, true,
753  outdata)))
754  {CLDBG("get_krbFwdCreds: err getting forwarded ticket;" <<krb_etxt(rc));
755  return rc;
756  }
757 
758 // Done
759 //
760  return rc;
761 }
762 
763 /******************************************************************************/
764 /* e x p _ k r b T k n */
765 /******************************************************************************/
766 
767 int XrdSecProtocolkrb5::exp_krbTkn(XrdSecCredentials *cred, XrdOucErrInfo *erp)
768 {
769  krb5_address ipadd;
770  int rc = 0;
771 
772 // Create the cache filename, expanding the keywords, if needed
773 //
774  char ccfile[XrdSecMAXPATHLEN];
775  strcpy(ccfile, XrdSecProtocolkrb5::ExpFile);
776  int nlen = strlen(ccfile);
777  char *pusr = (char *) strstr(&ccfile[0], "<user>");
778  if (pusr)
779  {int ln = strlen(CName);
780  if (ln != 6) {
781  // Adjust the space
782  int lm = strlen(ccfile) - (int)(pusr + 6 - &ccfile[0]);
783  memmove(pusr+ln, pusr+6, lm);
784  }
785  // Copy the name
786  memcpy(pusr, CName, ln);
787  // Adjust the length
788  nlen += (ln - 6);
789  }
790  char *puid = (char *) strstr(&ccfile[0], "<uid>");
791  struct passwd *pw;
792  XrdSysPwd thePwd(CName, &pw);
793  if (puid)
794  {char cuid[20] = {0};
795  if (pw)
796  sprintf(cuid, "%d", pw->pw_uid);
797  int ln = strlen(cuid);
798  if (ln != 5) {
799  // Adjust the space
800  int lm = strlen(ccfile) - (int)(puid + 5 - &ccfile[0]);
801  memmove(puid+ln, pusr+5, lm);
802  }
803  // Copy the name
804  memcpy(puid, cuid, ln);
805  // Adjust the length
806  nlen += (ln - 5);
807  }
808 
809 // Terminate to the new length
810 //
811  ccfile[nlen] = 0;
812 
813 // Point the received creds
814 //
815  krbContext.Lock();
816  krb5_data forwardCreds;
817  forwardCreds.data = &cred->buffer[XrdSecPROTOIDLEN];
818  forwardCreds.length = cred->size -XrdSecPROTOIDLEN;
819 
820 // Get the replay cache
821 //
822  krb5_rcache rcache;
823  if ((rc = krb5_get_server_rcache(krb_context,
824  krb5_princ_component(krb_context, krb_principal, 0),
825  &rcache)))
826  return rc;
827  if ((rc = krb5_auth_con_setrcache(krb_context, AuthContext, rcache)))
828  return rc;
829 
830 // Fill-in remote address
831 //
832  SetAddr(ipadd);
833  if ((rc = krb5_auth_con_setaddrs(krb_context, AuthContext, 0, &ipadd)))
834  return rc;
835 
836 // Readout the credentials
837 //
838  krb5_creds **creds = 0;
839  if ((rc = krb5_rd_cred(krb_context, AuthContext,
840  &forwardCreds, &creds, 0)))
841  return rc;
842 
843 // Resolve cache name
844  krb5_ccache cache = 0;
845  if ((rc = krb5_cc_resolve(krb_context, ccfile, &cache)))
846  return rc;
847 
848 // Init cache
849 //
850  if ((rc = krb5_cc_initialize(krb_context, cache,
851  Ticket->enc_part2->client)))
852  return rc;
853 
854 // Store credentials in cache
855 //
856  if ((rc = krb5_cc_store_cred(krb_context, cache, *creds)))
857  return rc;
858 
859 // Close cache
860  if ((rc = krb5_cc_close(krb_context, cache)))
861  return rc;
862 
863 // Change permission and ownership of the file
864 //
865  if (chmod(ccfile, 0600) == -1)
866  return Fatal(erp, errno, "Unable to change file permissions;", ccfile, 0);
867 
868 // Done
869 //
870  return 0;
871 }
872 
873 /******************************************************************************/
874 /* S e t A d d r */
875 /******************************************************************************/
876 
877 void XrdSecProtocolkrb5::SetAddr(krb5_address &ipadd)
878 {
879 // The below is a hack but that's how it is actually done!
880 //
881  if (epAddr.Family() == AF_INET6)
882  {struct sockaddr_in6 *ip = (struct sockaddr_in6 *)epAddr.SockAddr();
883  ipadd.addrtype = ADDRTYPE_INET6;
884  ipadd.length = sizeof(ip->sin6_addr);
885  ipadd.contents = (krb5_octet *)&ip->sin6_addr;
886  } else {
887  struct sockaddr_in *ip = (struct sockaddr_in *)epAddr.SockAddr();
888  ipadd.addrtype = ADDRTYPE_INET;
889  ipadd.length = sizeof(ip->sin_addr);
890  ipadd.contents = (krb5_octet *)&ip->sin_addr;
891  }
892 }
893 
894 /******************************************************************************/
895 /* X r d S e c p r o t o c o l k r b 5 I n i t */
896 /******************************************************************************/
897 
898 extern "C"
899 {
900 char *XrdSecProtocolkrb5Init(const char mode,
901  const char *parms,
902  XrdOucErrInfo *erp)
903 {
904  char *op, *KPrincipal=0, *Keytab=0, *ExpFile=0;
905  char parmbuff[1024];
906  XrdOucTokenizer inParms(parmbuff);
907  int options = XrdSecNOIPCHK;
908  static bool serverinitialized = false;
909 
910 // For client-side one-time initialization, we only need to set debug flag and
911 // initialize the kerberos context and cache location.
912 //
913  if ((mode == 'c') || (serverinitialized))
914  {
915  int opts = 0;
916  if (getenv("XrdSecDEBUG")) opts |= XrdSecDEBUG;
917  if (getenv("XrdSecKRB5INITTKN")) opts |= XrdSecINITTKN;
919  return (XrdSecProtocolkrb5::Init(erp) ? (char *)0 : (char *)"");
920  }
921 
922  if (!serverinitialized) {
923  serverinitialized = true;
924  }
925 
926 // Duplicate the parms
927 //
928  if (parms) strlcpy(parmbuff, parms, sizeof(parmbuff));
929  else {char *msg = (char *)"Seckrb5: Kerberos parameters not specified.";
930  if (erp) erp->setErrInfo(EINVAL, msg);
931  else std::cerr <<msg <<std::endl;
932  return (char *)0;
933  }
934 
935 // Expected parameters: [<keytab>] [-ipchk] [-exptkn[:filetemplate]] <principal>
936 //
937  if (inParms.GetLine())
938  {if ((op = inParms.GetToken()) && *op == '/')
939  {Keytab = op; op = inParms.GetToken();}
940  if (op && !strcmp(op, "-ipchk"))
941  {options &= ~XrdSecNOIPCHK;
942  op = inParms.GetToken();
943  }
944  if (op && !strncmp(op, "-exptkn", 7))
945  {options |= XrdSecEXPTKN;
946  if (op[7] == ':') ExpFile = op+8;
947  op = inParms.GetToken();
948  }
949  KPrincipal = strdup(op);
950  }
951 
952  if (ExpFile)
953  fprintf(stderr,"Template for exports: %s\n", ExpFile);
954  else
955  fprintf(stderr,"Template for exports not set\n");
956 
957 // Now make sure that we have all the right info
958 //
959  if (!KPrincipal)
960  {char *msg = (char *)"Seckrb5: Kerberos principal not specified.";
961  if (erp) erp->setErrInfo(EINVAL, msg);
962  else std::cerr <<msg <<std::endl;
963  return (char *)0;
964  }
965 
966 // Expand possible keywords in the principal
967 //
968  int plen = strlen(KPrincipal);
969  int lkey = strlen("<host>");
970  char *phost = (char *) strstr(&KPrincipal[0], "<host>");
971  if (phost)
972  {char *hn = XrdNetUtils::MyHostName();
973  if (hn)
974  {int lhn = strlen(hn);
975  if (lhn != lkey) {
976  // Allocate, if needed
977  int lnew = plen - lkey + lhn;
978  if (lnew > plen) {
979  KPrincipal = (char *) realloc(KPrincipal, lnew+1);
980  KPrincipal[lnew] = 0;
981  phost = (char *) strstr(&KPrincipal[0], "<host>");
982  }
983  // Adjust the space
984  int lm = plen - (int)(phost + lkey - &KPrincipal[0]);
985  memmove(phost + lhn, phost + lkey, lm);
986  }
987  // Copy the name
988  memcpy(phost, hn, lhn);
989  // Cleanup
990  free(hn);
991  }
992  }
993 
994 // Now initialize the server
995 //
996  options |= XrdSecDEBUG;
999  if (!XrdSecProtocolkrb5::Init(erp, KPrincipal, Keytab))
1000  {free(KPrincipal);
1001  int lpars = strlen(XrdSecProtocolkrb5::getPrincipal());
1002  if (options & XrdSecEXPTKN)
1003  lpars += strlen(",fwd");
1004  char *params = (char *)malloc(lpars+1);
1005  if (params)
1006  {memset(params,0,lpars+1);
1007  strcpy(params,XrdSecProtocolkrb5::getPrincipal());
1008  if (options & XrdSecEXPTKN)
1009  strcat(params,",fwd");
1011  return params;
1012  }
1013  return (char *)0;
1014  }
1015 
1016 // Failure
1017 //
1018  free(KPrincipal);
1019  return (char *)0;
1020 }
1021 }
1022 
1023 /******************************************************************************/
1024 /* X r d S e c P r o t o c o l k r b 5 O b j e c t */
1025 /******************************************************************************/
1026 
1027 extern "C"
1028 {
1030  const char *hostname,
1031  XrdNetAddrInfo &endPoint,
1032  const char *parms,
1033  XrdOucErrInfo *erp)
1034 {
1035  XrdSecProtocolkrb5 *prot;
1036  char *KPrincipal=0;
1037 
1038 // If this is a client call, then we need to get the target principal from the
1039 // parms (which must be the first and only token). For servers, we use the
1040 // context we established at initialization time.
1041 //
1042  if (mode == 'c')
1043  {if ((KPrincipal = (char *)parms)) while(*KPrincipal == ' ') KPrincipal++;
1044  if (!KPrincipal || !*KPrincipal)
1045  {char *msg = (char *)"Seckrb5: Kerberos principal not specified.";
1046  if (erp) erp->setErrInfo(EINVAL, msg);
1047  else std::cerr <<msg <<std::endl;
1048  return (XrdSecProtocol *)0;
1049  }
1050  }
1051 
1052 // Get a new protocol object
1053 //
1054  if (!(prot = new XrdSecProtocolkrb5(KPrincipal, hostname, endPoint)))
1055  {char *msg = (char *)"Seckrb5: Insufficient memory for protocol.";
1056  if (erp) erp->setErrInfo(ENOMEM, msg);
1057  else std::cerr <<msg <<std::endl;
1058  return (XrdSecProtocol *)0;
1059  }
1060 
1061 // All done
1062 //
1063  return prot;
1064 }
1065 
1066 void
1067  __eprintf (const char *string, const char *expression,
1068  unsigned int line, const char *filename)
1069  {
1070  fprintf (stderr, string, expression, line, filename);
1071  fflush (stderr);
1072  abort ();
1073  }
1074 }
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.