/******************************************************************************/
/*                                                                            */
/*                        X r d S e c s s s K T . c c                         */
/*                                                                            */
/* (c) 2008 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              */
/*                                                                            */
/* 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 <http://www.gnu.org/licenses/>.        */
/*                                                                            */
/* 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 <fcntl.h>
#include <iostream>
#include <cstdio>
#include <stddef.h>
#include <cstdlib>
#include <cstring>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "XrdSecsss/XrdSecsssKT.hh"

#include "XrdOuc/XrdOucErrInfo.hh"
#include "XrdOuc/XrdOucStream.hh"
#include "XrdOuc/XrdOucUtils.hh"
#include "XrdSys/XrdSysE2T.hh"

#ifndef ENODATA
#define ENODATA ENOATTR
#endif
  
/******************************************************************************/
/*                    S t a t i c   D e f i n i t i o n s                     */
/******************************************************************************/

int XrdSecsssKT::randFD = -1;
  
/******************************************************************************/
/*                       X r d S e c s s s K T R e f r                        */
/******************************************************************************/
  
void *XrdSecsssKTRefresh(void *Data)
{
   XrdSecsssKT *theKT = (XrdSecsssKT *)Data;
   struct timespec naptime = {theKT->RefrTime(), 0};

// Loop and check if keytab has changed
//
   while(1) {nanosleep(&naptime, 0); theKT->Refresh();}

   return (void *)0;
}

/******************************************************************************/
/*                           C o n s t r u c t o r                            */
/******************************************************************************/
  
XrdSecsssKT::XrdSecsssKT(XrdOucErrInfo *eInfo, const char *kPath,
                         xMode oMode, int refrInt)
{
   static const char *eText = "Unable to start keytab refresh thread";
   const char *devRand = "/dev/urandom";
   struct stat sbuf;
   int retc;

// Do some common initialization
//
   ktRefID= 0;
   ktPath = (kPath ? strdup(kPath) : 0);
   ktList = 0; kthiID = 0; ktMode = oMode; ktRefT = (time_t)refrInt;
   if (eInfo) eInfo->setErrCode(0);

// Prepare /dev/random if we have it
//
   if (stat(devRand, &sbuf)) devRand = "/dev/random";
   if ((randFD = open(devRand, O_RDONLY)) < 0
   && oMode != isClient && errno != ENOENT)
      eMsg("sssKT",errno,"Unable to generate random key"," opening ",devRand);

// First get the stat information for the file
//
   if (!kPath)
      {if (oMode != isAdmin)
          {eMsg("sssKT", -1, "Keytable path not specified.");
           if (eInfo) eInfo->setErrInfo(EINVAL, "Keytable path missing.");
           return;
          }
       sbuf.st_mtime = 0; sbuf.st_mode = S_IRWXU;
      } else if (stat(kPath, &sbuf))
                {if (eInfo) eInfo->setErrInfo(errno, "Keytable not found");
                 if (errno != ENOENT || oMode != isAdmin)
                    eMsg("sssKT",errno,"Unable process keytable ",kPath);
                 return;
                }

// Now read in the whole key table and start possible refresh thread
//
   if ((ktList = getKeyTab(eInfo, sbuf.st_mtime, sbuf.st_mode))
   && (oMode != isAdmin) && (!eInfo || eInfo->getErrInfo() == 0))
      {if ((retc = XrdSysThread::Run(&ktRefID,XrdSecsssKTRefresh, (void *)this,
                                     XRDSYSTHREAD_HOLD)))
          {eMsg("sssKT", errno, eText); eInfo->setErrInfo(-1, eText);}
      }
}

/******************************************************************************/
/*                            D e s t r u c t o r                             */
/******************************************************************************/

XrdSecsssKT::~XrdSecsssKT()
{
   ktEnt *ktP;
   void  *Dummy;

// Lock against others
//
   myMutex.Lock();

// Kill the refresh thread first
//
   if (ktRefID && !XrdSysThread::Kill(ktRefID))
      XrdSysThread::Join(ktRefID, &Dummy);
   ktRefID= 0;

// Now we can safely clean up
//
   if (ktPath) {free(ktPath); ktPath = 0;}

   while((ktP = ktList)) {ktList = ktList->Next; delete ktP;}

   myMutex.UnLock();
}
  
/******************************************************************************/
/*                               a d d K e y                                  */
/******************************************************************************/
  
void XrdSecsssKT::addKey(ktEnt &ktNew)
{
   ktEnt *ktPP = 0, *ktP;

// Generate a key for this entry
//
   genKey(ktNew.Data.Val, ktNew.Data.Len);
   ktNew.Data.Crt = time(0);
   ktNew.Data.ID  = static_cast<long long>(ktNew.Data.Crt & 0x7fffffff) << 32L
                  | static_cast<long long>(++kthiID);

// Locate place to insert this key
//
   ktP = ktList;
   while(ktP && !isKey(*ktP, &ktNew, 0)) {ktPP = ktP; ktP = ktP->Next;}

// Now chain in the entry
//
   if (ktPP) ktPP->Next = &ktNew;
      else   ktList     = &ktNew;
   ktNew.Next = ktP;
}

/******************************************************************************/
/*                                d e l K e y                                 */
/******************************************************************************/
  
int XrdSecsssKT::delKey(ktEnt &ktDel)
{
   ktEnt *ktN, *ktPP = 0, *ktP = ktList;
   int nDel = 0;

// Remove all matching keys
//
   while(ktP)
        {if (isKey(ktDel, ktP))
            {if (ktPP) ktPP->Next = ktP->Next;
                else   ktList     = ktP->Next;
             ktN = ktP; ktP = ktP->Next; delete ktN; nDel++;
            } else {ktPP = ktP; ktP = ktP->Next;}
        }

   return nDel;
}

/******************************************************************************/
/*                                g e t K e y                                 */
/******************************************************************************/
  
int XrdSecsssKT::getKey(ktEnt &theEnt, bool andKeyID)
{
   ktEnt *ktP, *ktN;

// Lock the keytab to prevent modification
//
   myMutex.Lock();
   ktP = ktList;

// Find first key by key name (used normally by clients), by keyID, or both
//
     if (!*theEnt.Data.Name)
        {if (theEnt.Data.ID >= 0)
            while(ktP && ktP->Data.ID != theEnt.Data.ID)  ktP = ktP->Next;
        }
   else if (andKeyID)
        {while(ktP && ((ktP->Data.ID != theEnt.Data.ID)
            || strcmp(ktP->Data.Name,theEnt.Data.Name))) ktP=ktP->Next;
        }
   else {while(ktP && strcmp(ktP->Data.Name,theEnt.Data.Name)) ktP=ktP->Next;
         while(ktP && ktP->Data.Exp <= time(0))
              {if (!(ktN=ktP->Next) 
               ||  strcmp(ktN->Data.Name,theEnt.Data.Name)) break;
               ktP = ktN;
              }
        }

// If we found a match, export it
//
   if (ktP) theEnt = *ktP;
   myMutex.UnLock();

// Indicate if key expired
//
   if (!ktP) return ENOENT;
   return (theEnt.Data.Exp && theEnt.Data.Exp <= time(0) ? -1 : 0);
}

/******************************************************************************/
/*                                 g e n F N                                  */
/******************************************************************************/
  
char *XrdSecsssKT::genFN()
{
   static char fnbuff[1040];
   const char *pfx;

// Get the path prefix
//
   if (!(pfx = getenv("HOME")) || !*pfx) pfx = "";

// Format the name
//
   snprintf(fnbuff, sizeof(fnbuff), "%s/.xrd/sss.keytab", pfx);
   return fnbuff;
}
  
/******************************************************************************/
/*                                g e n K e y                                 */
/******************************************************************************/
  
void XrdSecsssKT::genKey(char *kBP, int kLen)
{
   struct timeval tval;
   int kTemp;

// See if we can directly service the key. Make sure that we get some entropy
// because some /dev/random devices start out really cold.
//
   if (randFD >= 0) 
      {char *buffP = kBP;
       int i, Got, Want = kLen, zcnt = 0, maxZ = kLen*25/100;
       while(Want)
       do { {do {Got = read(randFD, buffP, Want);}
                while(Got < 0 && errno == EINTR);
             if (Got > 0) {buffP += Got; Want -= Got;}
            }
          } while(Got > 0 && Want);
       if (!Want)
          {for (i = 0; i < kLen; i++) if (!kBP[i]) zcnt++;
           if (zcnt <= maxZ) return;
          }
      }

// Generate a seed
//
   gettimeofday(&tval, 0);
   if (tval.tv_usec == 0) tval.tv_usec = tval.tv_sec;
   tval.tv_usec = tval.tv_usec ^ getpid();
   srand48(static_cast<long>(tval.tv_usec));

// Now generate the key (we ignore he fact that longs may be 4 or 8 bytes)
//
   while(kLen > 0)
        {kTemp = mrand48();
         memcpy(kBP, &kTemp, (4 > kLen ? kLen : 4));
         kBP += 4; kLen -= 4;
        }
}

/******************************************************************************/
/*                               R e f r e s h                                */
/******************************************************************************/
  
void XrdSecsssKT::Refresh()
{
   XrdOucErrInfo eInfo;
   ktEnt *ktNew, *ktOld, *ktNext;
   struct stat sbuf;
   int retc = 0;

// Get change time of keytable and if changed, update it
//
   if (stat(ktPath, &sbuf) == 0)
      {if (sbuf.st_mtime == ktMtime) return;
       if ((ktNew = getKeyTab(&eInfo, sbuf.st_mtime, sbuf.st_mode))
       && eInfo.getErrInfo() == 0)
          {myMutex.Lock(); ktOld = ktList; ktList = ktNew; myMutex.UnLock();
          } else ktOld = ktNew;
       while(ktOld) {ktNext = ktOld->Next; delete ktOld; ktOld = ktNext;}
       if ((retc == eInfo.getErrInfo()) == 0) return;
      } else retc = errno;

// Refresh failed
//
   eMsg("Refresh",retc,"Unable to refresh keytable",ktPath);
}

/******************************************************************************/
/*                               R e w r i t e                                */
/******************************************************************************/
  
int XrdSecsssKT::Rewrite(int Keep, int &numKeys, int &numTot, int &numExp)
{
   char tmpFN[2048], buff[2048], kbuff[4096], *Slash;
   int ktFD, numID = 0, n, retc = 0;
   ktEnt ktCurr, *ktP, *ktN;
   mode_t theMode = fileMode(ktPath);

// Invoke mkpath in case the path is missing
//
   strcpy(tmpFN, ktPath);
   if ((Slash = rindex(tmpFN, '/'))) *Slash = '\0';
   retc = XrdOucUtils::makePath(tmpFN,S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
   if (retc) return (retc < 0 ? -retc : retc);
   if (Slash) *Slash = '/';

// Construct temporary filename
//
   sprintf(buff, ".%d", static_cast<int>(getpid()));
   strcat(tmpFN, buff);

// Open the file for output
//
   if ((ktFD = open(tmpFN, O_WRONLY|O_CREAT|O_TRUNC, theMode)) < 0)
      return errno;

// Write all of the keytable
//
   ktCurr.Data.Name[0] = ktCurr.Data.User[0] = ktCurr.Data.Grup[0] = 3;
   ktN = ktList; numKeys = numTot = numExp = 0;
   while((ktP = ktN))
        {ktN = ktN->Next; numTot++;
         if (ktP->Data.Name[0] == '\0') continue;
         if (ktP->Data.Exp && ktP->Data.Exp <= time(0)) {numExp++; continue;}
         if (!isKey(ktCurr, ktP, 0)) {ktCurr.NUG(ktP); numID = 0;}
            else if (Keep && numID >= Keep) continue;
         n = sprintf(buff, "%s0 u:%s g:%s n:%s N:%lld c:%lld e:%lld f:%lld k:",
                    (numKeys ? "\n" : ""),
                     ktP->Data.User,ktP->Data.Grup,ktP->Data.Name,ktP->Data.ID,
                     (long long) ktP->Data.Crt, (long long) ktP->Data.Exp,
                     ktP->Data.Flags);
         numID++; numKeys++; keyB2X(ktP, kbuff);
         if (write(ktFD, buff, n) < 0
         ||  write(ktFD, kbuff, ktP->Data.Len*2) < 0) break;
        }

// Check for errors
//
   if (ktP) retc = errno;
      else if (!numKeys) retc = ENODATA;

// Atomically trounce the original file if we can
//
   close(ktFD);
   if (!retc && rename(tmpFN, ktPath) < 0) retc = errno;

// All done
//
   unlink(tmpFN); 
   return retc;
}

/******************************************************************************/
/*                       P r i v a t e   M e t h o d s                        */
/******************************************************************************/
/******************************************************************************/
/*                                  e M s g                                   */
/******************************************************************************/

int XrdSecsssKT::eMsg(const char *epname, int rc,
                      const char *txt1, const char *txt2,
                      const char *txt3, const char *txt4)
{
              std::cerr <<"Secsss (" << epname <<"): ";
              std::cerr <<txt1;
   if (txt2)  std::cerr <<txt2;
   if (txt3)  std::cerr <<txt3;
   if (txt4)  std::cerr <<txt4;
  {if (rc>0) {std::cerr <<"; " <<XrdSysE2T(rc);}}
              std::cerr <<"\n" <<std::endl;

   return (rc ? (rc < 0 ? rc : -rc) : -1);
}

/******************************************************************************/
/*                             g e t K e y T a b                              */
/******************************************************************************/
  
XrdSecsssKT::ktEnt* XrdSecsssKT::getKeyTab(XrdOucErrInfo *eInfo,
                                           time_t Mtime, mode_t Amode)
{
   static const int altMode = S_IRWXG | S_IRWXO;
   XrdOucStream myKT;
   int ktFD = -1, retc, tmpID, recno = 0, NoGo = 0;
   const char *What = 0, *ktFN;
   char *lp, *tp, rbuff[64];
   ktEnt *ktP, *ktPP, *ktNew, *ktBase = 0;

// Verify that the keytable is only readable by us
//
   ktMtime = Mtime;
   if ((Amode & altMode) & ~fileMode(ktPath))
      {if (eInfo) eInfo->setErrInfo(EACCES, "Keytab file is not secure!");
       eMsg("getKeyTab",-1,"Unable to process ",ktPath,"; file is not secure!");
       return 0;
      }

// Open the file
//
   if (ktPath)
      {if ((ktFD = open(ktPath, O_RDONLY)) < 0)
          {if (eInfo) eInfo->setErrInfo(errno, "Unable to open keytab file.");
           eMsg("getKeyTab", errno, "Unable to open ", ktPath);
           return 0;
          } else ktFN = ktPath;
      } else {ktFD = dup(STDIN_FILENO); ktFN = "stdin";}

// Attach the fd to the stream
//
   if (ktFD < 0 || myKT.Attach(ktFD) != 0)
      {if (eInfo) eInfo->setErrInfo(EBADF, "Unable to attach to keytab file descriptor for reading.");
       eMsg("getKeyTab", -1, "Unable to attach to keytab file descriptor.");
       return nullptr;
      }

// Now start reading the keytable which always has the form:
//
// <format> <whatever data based on format>
//
do{while((lp = myKT.GetLine()))
        {recno++; What = 0;
         if (!*lp) continue;
         if (!(tp = myKT.GetToken()) || (strcmp("0", tp) && strcmp("1", tp)))
            {What = "keytable format missing or unsupported";      break;}
         if (!(ktNew = ktDecode0(myKT, eInfo)))
            {What = (eInfo ? eInfo->getErrText(): "invalid data"); break;}
         if (ktMode!=isAdmin && ktNew->Data.Exp && ktNew->Data.Exp <= time(0))
            {delete ktNew; continue;}
         tmpID = static_cast<int>(ktNew->Data.ID & 0x7fffffff);
         if (tmpID > kthiID) kthiID = tmpID;
         
         ktP = ktBase; ktPP = 0;
         while(ktP && !isKey(*ktP, ktNew, 0)) {ktPP=ktP; ktP=ktP->Next;}
         if (!ktP) {ktNew->Next = ktBase; ktBase = ktNew;}
            else {if (ktMode == isClient)
                     {if ((ktNew->Data.Exp == 0 && ktP->Data.Exp != 0)
                      ||  (ktP->Data.Exp!=0 && ktP->Data.Exp < ktNew->Data.Exp))
                          ktP->Set(*ktNew);
                      delete ktNew;
                     } else {
                      while(ktNew->Data.Crt < ktP->Data.Crt)
                           {ktPP = ktP; ktP = ktP->Next;
                            if (!ktP || !isKey(*ktP, ktNew, 0)) break;
                           }
                      if (ktPP) {ktPP->Next = ktNew; ktNew->Next = ktP;}
                         else   {ktNew->Next= ktBase; ktBase = ktNew;}
                     }
                 }
        }
   if (What)
      {sprintf(rbuff, "; line %d in ", recno);
       NoGo = eMsg("getKeyTab", -1, What, rbuff, ktFN);
      }
  } while(lp);

// Check for problems
//
        if (NoGo) {if (eInfo) eInfo->setErrInfo(EINVAL,"Invalid keytab file.");}
   else if ((retc = myKT.LastError()))
           {if (eInfo) eInfo->setErrInfo(retc,"Unable to read keytab file.");
            NoGo = eMsg("getKeyTab", retc, "Unable to read keytab ",ktFN);
           }
   else if (!ktBase)
           {if (eInfo) eInfo->setErrInfo(ESRCH,"Keytable is empty.");
            NoGo = eMsg("getKeyTab",-1,"No keys found in ",ktFN);
           }


// Check if an error should be returned
//
   if (eInfo && !NoGo) eInfo->setErrCode(0);

// All done
//
   myKT.Close();
   return ktBase;
}

/******************************************************************************/
/*                               g r p F i l e                                */
/******************************************************************************/
  
mode_t XrdSecsssKT::fileMode(const char *Path)
{
   int n;

   return (!Path || (n = strlen(Path)) < 5 || strcmp(".grp", &Path[n-4])
        ? S_IRUSR|S_IWUSR : S_IRUSR|S_IWUSR|S_IRGRP);
}

/******************************************************************************/
/*                                 i s K e y                                  */
/******************************************************************************/

int  XrdSecsssKT::isKey(ktEnt &ktRef, ktEnt *ktP, int Full)
{
   if (*ktRef.Data.Name && strcmp(ktP->Data.Name, ktRef.Data.Name)) return 0;
   if (*ktRef.Data.User && strcmp(ktP->Data.User, ktRef.Data.User)) return 0;
   if (*ktRef.Data.Grup && strcmp(ktP->Data.Grup, ktRef.Data.Grup)) return 0;
   if (Full && ktRef.Data.ID > 0
   && (ktP->Data.ID & 0x7fffffff) != ktRef.Data.ID) return 0;
   return 1;
}
  
/******************************************************************************/
/*                                k e y B 2 X                                 */
/******************************************************************************/

void XrdSecsssKT::keyB2X(ktEnt *theKT, char *buff)
{
   static const char xTab[] = "0123456789abcdef";
   int  kLen = theKT->Data.Len;
   char *kP  = theKT->Data.Val, Val;

// Convert
//
   while(kLen--)
        {Val = *kP++;
         *buff++ = xTab[(Val>>4) & 0x0f];
         *buff++ = xTab[ Val     & 0x0f];
        }
   *buff = '\0';
}
  
/******************************************************************************/
/*                                k e y X 2 B                                 */
/******************************************************************************/
  
void XrdSecsssKT::keyX2B(ktEnt *theKT, char *xKey)
{
   int len = XrdOucUtils::hex2bin(xKey, theKT->Data.Val, sizeof(theKT->Data.Val));
   theKT->Data.Len = len > 0 ? len : 0;
}

/******************************************************************************/
/*                             k t D e c o d e 0                              */
/******************************************************************************/
  
XrdSecsssKT::ktEnt *XrdSecsssKT::ktDecode0(XrdOucStream  &kTab,
                                           XrdOucErrInfo *eInfo)
{
   static const short haveCRT = 0x0001;
   static const short haveEXP = 0x0002;
   static const short isTIMET = 0x0003;
   static const short haveGRP = 0x0004;
   static const short haveKEY = 0x0008;
   static const short haveNAM = 0x0010;
   static const short haveNUM = 0x0020;
   static const short haveUSR = 0x0040;
   static const short haveFLG = 0x0080;

   static struct 
          {const char *Name; size_t Offset; int Ctl; short What; char Tag;}
          ktDesc[] = {
   {"crtdt",   offsetof(ktEnt::ktData,Crt),  0,                haveCRT, 'c'},
   {"expdt",   offsetof(ktEnt::ktData,Exp),  0,                haveEXP, 'e'},
   {"flags",   offsetof(ktEnt::ktData,Flags),0,                haveFLG, 'f'},
   {"group",   offsetof(ktEnt::ktData,Grup), ktEnt::GrupSZ,    haveGRP, 'g'},
   {"keyval",  offsetof(ktEnt::ktData,Val),  ktEnt::maxKLen*2, haveKEY, 'k'},
   {"keyname", offsetof(ktEnt::ktData,Name), ktEnt::NameSZ,    haveNAM, 'n'},
   {"keynum",  offsetof(ktEnt::ktData,ID),   0,                haveNUM, 'N'},
   {"user",    offsetof(ktEnt::ktData,User), ktEnt::UserSZ,    haveUSR, 'u'}
   };
   static const int ktDnum = sizeof(ktDesc)/sizeof(ktDesc[0]);

   ktEnt ktNew{};
   const char *Prob = 0, *What = "Whatever";
   char Tag, *Dest, *ep, *tp;
   long long nVal;
   short Have = 0;
   int i = 0;

// Decode the record using the tags described in the above table
//
while((tp = kTab.GetToken()) && !Prob)
     {Tag = *tp++;
      if (*tp++ == ':')
         for (i = 0; i < ktDnum; i++)
             if (ktDesc[i].Tag == Tag)
                {Dest = (char *)&(ktNew.Data) + ktDesc[i].Offset;
                 Have |= ktDesc[i].What; What = ktDesc[i].Name;
                 if (ktDesc[i].Ctl)
                    {if ((int)strlen(tp) > ktDesc[i].Ctl) Prob=" is too long";
                        else if (Tag == 'k') {
                               keyX2B(&ktNew, tp);
                               if (ktNew.Data.Len == 0)
                                 Prob=" has invalid value";
                             } else strcpy(Dest, tp);
                    } else {
                     nVal = strtoll(tp, &ep, 10);
                     if (ep && *ep) Prob = " has invalid value";
                        else if (ktDesc[i].What & isTIMET)
                                 *(time_t *)Dest = static_cast<time_t>(nVal);
                                else *(long long *)Dest = nVal;
                    }
                }
     }

// If no problem, make sure we have the essential elements
//
   if (!Prob)
      {if (!(Have & haveGRP)) strcpy(ktNew.Data.Grup, "nogroup");
       if (!(Have & haveNAM)) strcpy(ktNew.Data.Name, "nowhere");
          else {int n = strlen(ktNew.Data.Name);
                if (ktNew.Data.Name[n-1] == '+')
                   ktNew.Data.Opts |= ktEnt::noIPCK;
               }
       if (!(Have & haveUSR)) strcpy(ktNew.Data.User, "nobody");
            if (!(Have & haveKEY)) {What = "keyval"; Prob = " not found";}
       else if (!(Have & haveNUM)) {What = "keynum"; Prob = " not found";}
      }

// Check if we have a problem
//
   if (Prob)
      {const char *eVec[] = {What, Prob};
       if (eInfo) eInfo->setErrInfo(-1, eVec, 2);
       return 0;
      }

// Set special value options
//
   if (!strcmp(ktNew.Data.Grup, "anygroup"))
      ktNew.Data.Opts|=ktEnt::anyGRP;
      else if (!strcmp(ktNew.Data.Grup, "usrgroup"))
              ktNew.Data.Opts|=ktEnt::usrGRP;
   if (!strcmp(ktNew.Data.User, "anybody"))
      ktNew.Data.Opts|=ktEnt::anyUSR;
      else if (!strcmp(ktNew.Data.User, "allusers"))
              ktNew.Data.Opts|=ktEnt::allUSR;

// All done
//
   return new ktEnt(ktNew);
}
