/* ***** BEGIN LICENSE BLOCK *****
* Version: CDDL 1.0
*
* The contents of this file are subject to the COMMON DEVELOPMENT AND
* DISTRIBUTION LICENSE (CDDL) Version 1.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at http://www.sun.com/cddl/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is
* "netlabs.org Open Source Archive Administrator/Client"
*
* The Original Distribution Package is named
* "netlabs.org Open Source Archive Administrator/Client"
* and maintained and distributed by the Initial Developer
* and/or netlabs.org only.
*
* In addition to the CDDL the following applies:
* If you modify the Original Code, you may distribute it only
* as a part of a distribution package where
*  - the name of the package, created directories or
*    OS2.INI entries do not contain any of the terms
*    - "netlabs.org Open Source Archive"
*    - "NOSA"
*    - "NOSAC"
*    - "NOSAADM"
*  - neither netlabs.org nor the Initial Developer is stated as
*    the vendor or originator of the resulting Distribution Package,
*    which contains the Modified Code.
*
* The Initial Developer of the Original Code is
* netlabs.org: Christian Langanke <cla@clanganke.de>.
* Portions created by the Initial Developer are Copyright (C) 1999-2008
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** */

#define COMPILE_DEBUGPRINTACL  0

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#include <io.h>
#include <fcntl.h>
#include <sys\stat.h>

#define INCL_DOS
#define INCL_ERRORS
#include <os2.h>

#include "cvsacl.h"
#include "cvsenv.h"
#include "cvsstr.h"
#include "macros.h"

#define  MAXACLS           1024
#define  MAXTAGLEN           32

#define  USERDEF_DELIMITER ','
#define  USERDEF_WILDCARD  '*'
#define  USERDEF_NORIGHT   '-'

// --------------------------------------------------------

static BOOL _isAclValid( PCVSENV pcvsenv,
                         PSZ *apszAclList, ULONG ulAclCount,
                         PSZ pszCheckDirectory)
{
         BOOL           fAclValid = FALSE;
         BOOL           fAclFound = FALSE;
         ULONG          i;
         PSZ            p;
         PSZ           *apszAcl;
         PSZ            pszAclUser;

         CHAR           szDirectory[ _MAX_PATH];

do
   {
   // check parms
   if ((!apszAclList) ||
       (!pszCheckDirectory))
      break;

   // upcase directory
   strcpy( szDirectory, pszCheckDirectory);
   strupr( szDirectory);

   // is cvsroot at start of string ? then skip
   pszCheckDirectory = szDirectory;
   if (strstr( szDirectory, pcvsenv->pszCvsRootDir) == szDirectory)
      {
      pszCheckDirectory += strlen( pcvsenv->pszCvsRootDir);

      // handle base path (== empty string here)
      if (!*pszCheckDirectory)
         {
         pszCheckDirectory = szDirectory;
         strcpy( pszCheckDirectory, "\\");
         }
      }

   DPRINTF(( ">>search %s in ACLs\n", pszCheckDirectory));
   // go thru all ACLs
   for (i = 0, apszAcl = apszAclList; ((i < ulAclCount) && (!fAclFound)); i++, apszAcl++)
      {
      DPRINTF(( ">>> testing %s against %s\n", pszCheckDirectory, *apszAcl));
      if (!strcmp( *apszAcl, pszCheckDirectory))
         {
         // ACL found, don't check no further !
         fAclFound = TRUE;

         // check user definitions (MULTISZZ string)
         pszAclUser = *apszAcl + strlen( *apszAcl) + 1;

         while( *pszAclUser)
            {
            // testing this user def
            DPRINTF(( ">>>> testing user def %s\n", pszAclUser));

            // - wildcard or userid found ?
            if ((*pszAclUser == USERDEF_WILDCARD) ||
                (!stricmp( pszAclUser, pcvsenv->pszUser)))
               {
               DPRINTF(( ">>> USERDEF FOUND %s\n", pszAclUser));
               fAclValid = TRUE;
               break;
               }


            // testing for -<userid>
            // - 
            if ((*pszAclUser == USERDEF_NORIGHT) &&
                (!stricmp( pszAclUser + 1, pcvsenv->pszUser)))
               {
               DPRINTF(( ">>> USERDEF DENIAL FOUND %s\n", pszAclUser));
               fAclValid = FALSE;
               break;
               }
            // next user def
            pszAclUser = pszAclUser + strlen( pszAclUser) + 1;

            } // while( *pszAclUser)

         } // if (!strcmp( *apszAcl, pszCheckDirectory))

      } // for (i = 0, apszAcl = apszAclList; i < ulAclCount; i++, apszAcl++)

   // don't examine further if the specified directory
   // contains the archive database
   if (!stricmp( pszCheckDirectory, "\\CVSROOT"))
      break;

   // not allowed ? then go back one directory
   if ((!fAclValid) && (!fAclFound) && (strcmp( pszCheckDirectory, "\\")))
      {
      p = strrchr( pszCheckDirectory, '\\');
      if (p)
         {
         if (p == pszCheckDirectory)
            *(p+1) = 0;
         else
            *p = 0;
         fAclValid = _isAclValid( pcvsenv,
                                  apszAclList, ulAclCount,
                                  pszCheckDirectory);
         } // if (p)

      } // if ((!fAclValid) && (!fAclFound) && (strcmp( pszCheckDirectory, "\\")))

   } while (FALSE);

return fAclValid;
}


// --------------------------------------------------------

BOOL IsCommitAllowed( PCVSENV pcvsenv,
                      PSZ *apszAclList, ULONG ulAclCount,
                      PSZ pszCheckDirectory)
{
         BOOL           fCommitAllowed = FALSE;

do
   {
   // check parms
   if ((!apszAclList) ||
       (!pszCheckDirectory))
      break;

   // check for commit allowed
   DPRINTF(( "\n>check if user %s is allowed to commit in \n -> %s\n",
             pcvsenv->pszUser, pszCheckDirectory));
   fCommitAllowed = _isAclValid( pcvsenv,
                                 apszAclList, ulAclCount,
                                 pszCheckDirectory);
   } while (FALSE);


// show error message to client
if (!fCommitAllowed)
   {
   printf( "cvs commit: pre-commit check for %s\n"
           "cvs commit: access to directory denied for user: %s\n",
           pszCheckDirectory, pcvsenv->pszUser);
   }

return fCommitAllowed;

}


// --------------------------------------------------------

BOOL IsTagAllowed( PCVSENV pcvsenv,
                   PSZ *apszAclList, ULONG ulAclCount,
                   PSZ pszTagName, PSZ pszCheckDirectory)
{
         BOOL           fTagAllowed       = FALSE;
         BOOL           fPersonalTagRequired = FALSE;
         CHAR           szPersonalTag[ MAXTAGLEN];

do
   {
   // check parms
   if ((!apszAclList) ||
       (!pszTagName))
      break;

   // first of all, user must be allowed to commit in the directory
   DPRINTF(( "\n>check if user %s is allowed to commit in" "\n  -> %s\n",
             pcvsenv->pszUser, pszCheckDirectory));
   fTagAllowed = _isAclValid( pcvsenv,
                              apszAclList, ulAclCount,
                              pszCheckDirectory);
   if (!fTagAllowed)
      break;

   // is user allowed to write the archive database ?
   // then all tag names are allowed !
   DPRINTF(( "\n>check if user %s is administrator (may write CVSROOT)\n",
             pcvsenv->pszUser));
   fPersonalTagRequired = !_isAclValid( pcvsenv,
                                        apszAclList, ulAclCount,
                                        "\\CVSROOT");

   if (fPersonalTagRequired)
      {
      // now check if the tag name starts with username
      DPRINTF(( "\n>no admin: check if required personal tag is specified"));
      sprintf( szPersonalTag, "%s_", pcvsenv->pszUser);
      fTagAllowed = (strnicmp( szPersonalTag,
                               pszTagName, strlen( pcvsenv->pszUser)) == 0);
      }
   else
      fTagAllowed = TRUE;

   } while (FALSE);

// show error message to client
if (!fTagAllowed)
   {
   printf( "cvs tag: pre-tag check for %s\n", pszCheckDirectory);
   if (fPersonalTagRequired)
      {
      printf( "cvs tag: Invalid tag %s specified by user: %s.\n"
              "         %s may only use personal tags beginning with \"%s_\" !\n",
              pszTagName,
              pcvsenv->pszUser,
              pcvsenv->pszUser,
              pcvsenv->pszUser);
      }
   else
      printf( "cvs tag: access to directory denied for user: %s\n",
               pcvsenv->pszUser);
   }

return fTagAllowed;

}

// --------------------------------------------------------

int _compareAcl( const void *pvKey, const void *pvElement)
{

// reverse sort
if ((pvKey) && (pvElement))
   return strcmp( *(PSZ*)pvElement, *(PSZ*)pvKey);

if ((!pvKey) && (!pvElement))
   return 0;

return (pvKey < pvElement);
}

APIRET ReadAclFile( PSZ pszAclFile, PULONG pulAclCount, PSZ **papszAclList)

{
         APIRET         rc = NO_ERROR;
         ULONG          i;
         PSZ            pszRootAcl;
         PSZ           *apszAclList;

         ULONG          ulFileSize;
         ULONG          ulBuflen;

         HFILE          hfileAcl = NULLHANDLE;
         fpos_t         fpos;
         ULONG          ulBytesRead;

         PSZ            pszCurrentAclText;
         PSZ            pszNextAclText;
         PSZ            p;

         PSZ           *apszAclPtr = NULL;
         PSZ           *apszCurrentAclPtr;
         ULONG          ulAclCount = 0;

do

   {
   // check parms
   if ((!pszAclFile)   ||
       (!pulAclCount)  ||
       (!papszAclList))
      {
      rc = ERROR_INVALID_PARAMETER;
      break;
      }

   // open file
   hfileAcl = _open( pszAclFile, O_RDONLY | O_BINARY, S_IREAD);
   if (!hfileAcl)
      {
      rc = ERROR_OPEN_FAILED;
      printf( "error: cannot open ACL file %s.\n", pszAclFile);
      break;
      }

   // get filesize
   ulFileSize = _filelength( hfileAcl);

   // get memory for pointer list plus file contents
   ulBuflen   = (MAXACLS * sizeof( PSZ)) + ulFileSize + 2; // two zero bytes at end !
   apszAclPtr = malloc( ulBuflen);
   if (!apszAclPtr)
      {
      rc = ERROR_NOT_ENOUGH_MEMORY;
      printf( "error: cannot allocate memory for ACL file %s.\n", pszAclFile);
      break;
      }
   pszCurrentAclText = (PBYTE) apszAclPtr + (MAXACLS * sizeof( PSZ));
   apszCurrentAclPtr = apszAclPtr;
   memset( apszAclPtr, 0, ulBuflen);

   // read ACL file
   ulBytesRead = _read( hfileAcl, pszCurrentAclText, ulFileSize);
   if (!ulBytesRead)
      {
      rc = ERROR_READ_FAULT;
      printf( "error: cannot read ACL file %s.\n", pszAclFile);
      break;
      }

   // now go thru all ACLs
   while (*pszCurrentAclText)
      {
      // find end of line == end of acl
      pszNextAclText = strstr( pszCurrentAclText, "\r\n");
      if (!pszNextAclText)
         // no end of line, put ptr to end of text
         pszNextAclText = pszCurrentAclText + strlen( pszCurrentAclText);
      else
         {
         // skip crlf and replace with double zero
         *pszNextAclText        = 0;
         *(pszNextAclText + 1)  = 0;
         pszNextAclText        += 2;
         }

      // check for limit
      ulAclCount++;
      if (ulAclCount > MAXACLS)
         {
         printf( "error: limit of %u ACLs exceeded\n", MAXACLS);
         rc = ERROR_NOT_ENOUGH_MEMORY;
         break;
         }

      // separate ACL name and usernames
      p = pszCurrentAclText;
      while (*p > 0x20)
         {
         p++;
         }
      *p = 0;
      strupr(dosslash( pszCurrentAclText));

      // tokenize userdefs, transforms string to MULTISZZ string
      // Note: this requires two zero bytes at end of parameter
      //       This is accomplished by overwriting CR LF with zeroes !
      makestrlist( p + 1, USERDEF_DELIMITER);

      // store address
      *apszCurrentAclPtr = pszCurrentAclText;
      apszCurrentAclPtr++;

      // address next one
      pszCurrentAclText = pszNextAclText;
      }

   // sort result
   qsort( apszAclPtr, ulAclCount, sizeof( PSZ), _compareAcl);


#if COMPILE_DEBUGPRINTACL
   // print sorted version
   printf( "ACLs fo writeinfo\n"
           "-----------------\n");
   for (i = 0, apszCurrentAclPtr = apszAclPtr;
        i < ulAclCount;
        i++, apszCurrentAclPtr++)
      {
      // print ACL directory
      printf( "  %-30s",
              *apszCurrentAclPtr);

      // print all userids
      pszCurrentAclText = (*apszCurrentAclPtr) + strlen( *apszCurrentAclPtr) + 1;
      do {
         printf( "%s ", pszCurrentAclText);
         pszCurrentAclText += strlen( pszCurrentAclText) + 1;
         } while ( *pszCurrentAclText);
      printf( "\n");


      }
#endif

   // hand over result
   *papszAclList = apszAclPtr;
   *pulAclCount  = ulAclCount;

   } while (FALSE);

// cleanup
if (hfileAcl)
   _close( hfileAcl);

if ((rc != NO_ERROR) && (apszAclPtr))
   free( apszAclPtr);

return rc;

}
  
