/*
 * Copyright (C) 2005-2006,2009-2011,2015 Timothy D. Morgan
 * Copyright (C) 1992-2005 Samba development team 
 *               (see individual files under Subversion for details.)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id$
 */

/** @file */

#include "winsec.h"


/******************************************************************************
 * Non-talloc() interface for parsing a descriptor.
 ******************************************************************************/
WINSEC_DESC* winsec_parse_descriptor(const uint8_t* buf, uint32_t buf_len)
{
  return winsec_parse_desc(NULL, buf, buf_len);
}


/******************************************************************************
 * Free a descriptor.  Not needed if using talloc and a parent context is freed.
 ******************************************************************************/
void winsec_free_descriptor(WINSEC_DESC* desc)
{
  talloc_free(desc);
}


/******************************************************************************
 * Parses a WINSEC_DESC structure and substructures.
 ******************************************************************************/
WINSEC_DESC* winsec_parse_desc(void* talloc_ctx, 
			       const uint8_t* buf, uint32_t buf_len)
{
  WINSEC_DESC* ret_val;

  if (buf == NULL || buf_len <  WINSEC_DESC_HEADER_SIZE)
    return NULL;

  if((ret_val = talloc(talloc_ctx, WINSEC_DESC)) == NULL)
    return NULL;

  ret_val->revision = buf[0];
  ret_val->sbz1 = buf[1];
  ret_val->control = SVAL(buf, 0x2);

  /* XXX: should probably reject any non-self relative */
  if(!(ret_val->control & WINSEC_DESC_SELF_RELATIVE))
    fprintf(stderr, "DEBUG: NOT self-relative!\n");

  ret_val->off_owner_sid = IVAL(buf, 0x4);
  ret_val->off_grp_sid = IVAL(buf, 0x8);
  ret_val->off_sacl = IVAL(buf, 0xC);
  ret_val->off_dacl = IVAL(buf, 0x10);

  /* A basic sanity check to ensure our offsets are within our buffer.
   * Additional length checking is done in secondary parsing functions.
   */
  if((ret_val->off_owner_sid >= buf_len)
     || (ret_val->off_grp_sid >= buf_len)
     || (ret_val->off_sacl >= buf_len)
     || (ret_val->off_dacl >= buf_len))
  {
    talloc_free(ret_val);
    return NULL;
  }

  if(ret_val->off_owner_sid == 0)
    ret_val->owner_sid = NULL;
  else
  {
    ret_val->owner_sid = winsec_parse_dom_sid(ret_val, 
					      buf + ret_val->off_owner_sid,
					      buf_len - ret_val->off_owner_sid);
    if(ret_val->owner_sid == NULL)
    {
      talloc_free(ret_val);
      return NULL;
    }
  }

  if(ret_val->off_grp_sid == 0) 
    ret_val->grp_sid = NULL;
  else
  {
    ret_val->grp_sid = winsec_parse_dom_sid(ret_val, buf + ret_val->off_grp_sid,
					    buf_len - ret_val->off_grp_sid);
    if(ret_val->grp_sid == NULL)
    {
      talloc_free(ret_val);
      return NULL;
    }
  }

  if((ret_val->control & WINSEC_DESC_SACL_PRESENT) && ret_val->off_sacl)
  {
    ret_val->sacl = winsec_parse_acl(ret_val, buf + ret_val->off_sacl,
				     buf_len - ret_val->off_sacl);
    if(ret_val->sacl == NULL)
    {
      talloc_free(ret_val);
      return NULL;
    }
  }
  else
    ret_val->sacl = NULL;

  if((ret_val->control & WINSEC_DESC_DACL_PRESENT) && ret_val->off_dacl != 0) 
  {
    ret_val->dacl = winsec_parse_acl(ret_val, buf + ret_val->off_dacl,
				     buf_len - ret_val->off_dacl);
    if(ret_val->dacl == NULL)
    {
      talloc_free(ret_val);
      return NULL;
    }
  }
  else
    ret_val->dacl = NULL;

  return ret_val;
}


/******************************************************************************
 * Parses a WINSEC_ACL structure and all substructures.
 ******************************************************************************/
WINSEC_ACL* winsec_parse_acl(void* talloc_ctx,
			     const uint8_t* buf, uint32_t buf_len)
{
  uint32_t i, offset;
  WINSEC_ACL* ret_val;

  /*
   * Note that the size is always a multiple of 4 bytes due to the
   * nature of the data structure.
   */
  if (buf == NULL || buf_len < 8)
    return NULL;

  if((ret_val = talloc(talloc_ctx, WINSEC_ACL)) == NULL)
    return NULL;
  
  ret_val->revision = SVAL(buf, 0x0);
  ret_val->size     = SVAL(buf, 0x2);
  ret_val->num_aces = IVAL(buf, 0x4);

  /* The num_aces can be at most around 4k because anything greater
   * wouldn't fit in the 16 bit size even if every ace was as small as
   * possible. 
   */
  if((ret_val->size > buf_len) || (ret_val->num_aces > 4095))
  {
    talloc_free(ret_val);
    return NULL;
  }

  /* Even if the num_aces is zero, allocate memory as there's a difference
   * between a non-present DACL (allow all access) and a DACL with no ACE's
   * (allow no access).
   */
  if((ret_val->aces = talloc_array(ret_val, WINSEC_ACE*, 
				   ret_val->num_aces+1)) == NULL)
  {
    talloc_free(ret_val);
    return NULL;
  }

  offset = 8;
  for(i=0; i < ret_val->num_aces; i++)
  {
    ret_val->aces[i] = winsec_parse_ace(ret_val->aces, 
					buf+offset, buf_len-offset);
    if(ret_val->aces[i] == NULL)
    {
      talloc_free(ret_val);
      return NULL;
    }

    offset += ret_val->aces[i]->size;
    if(offset > buf_len)
    {
      talloc_free(ret_val);
      return NULL;
    }
  }
  ret_val->aces[ret_val->num_aces] = NULL;

  return ret_val;
}


/******************************************************************************
 * Parses a WINSEC_ACE structure and all substructures.
 ******************************************************************************/
WINSEC_ACE* winsec_parse_ace(void* talloc_ctx,
			     const uint8_t* buf, uint32_t buf_len)
{
  uint32_t offset;
  WINSEC_ACE* ret_val;

  if(buf == NULL || buf_len < WINSEC_ACE_MIN_SIZE)
    return NULL;

  if((ret_val = talloc(talloc_ctx, WINSEC_ACE)) == NULL)
    return NULL;

  ret_val->type = buf[0];
  ret_val->flags = buf[1];
  ret_val->size = SVAL(buf, 0x2);
  ret_val->access_mask = IVAL(buf, 0x4);
  ret_val->obj_guid = NULL;
  ret_val->inh_guid = NULL;
  
  offset = 0x8;

  /* check whether object access is present */
  if (winsec_ace_object(ret_val->type))
  {
    ret_val->obj_flags = IVAL(buf, offset);
    offset += 4;

    if(ret_val->obj_flags & WINSEC_ACE_OBJECT_PRESENT)
    {
      ret_val->obj_guid = winsec_parse_uuid(ret_val, 
					    buf+offset, buf_len-offset);
      if(ret_val->obj_guid == NULL)
      {
	talloc_free(ret_val);
	return NULL;
      }
      offset += sizeof(WINSEC_UUID);
    }

    if(ret_val->obj_flags & WINSEC_ACE_OBJECT_INHERITED_PRESENT)
    {
      ret_val->inh_guid = winsec_parse_uuid(ret_val, 
					    buf+offset, buf_len-offset);
      if(ret_val->inh_guid == NULL)
      {
	talloc_free(ret_val);
	return NULL;
      }
      offset += sizeof(WINSEC_UUID);
    }
  }

  ret_val->trustee = winsec_parse_dom_sid(ret_val, buf+offset, buf_len-offset);
  if(ret_val->trustee == NULL)
  {
    talloc_free(ret_val);
    return NULL;
  }
  
  return ret_val;
}


/******************************************************************************
 * Parses a WINSEC_DOM_SID structure.
 ******************************************************************************/
WINSEC_DOM_SID* winsec_parse_dom_sid(void* talloc_ctx,
				     const uint8_t* buf, uint32_t buf_len)
{
  uint32_t i;
  WINSEC_DOM_SID* ret_val;

  if(buf == NULL || buf_len < 8)
    return NULL;

  /*  if((ret_val = (WINSEC_DOM_SID*)zalloc(sizeof(WINSEC_DOM_SID))) == NULL)*/
  if((ret_val = talloc(talloc_ctx, WINSEC_DOM_SID)) == NULL)
    return NULL;

  ret_val->sid_rev_num = buf[0];
  ret_val->num_auths = buf[1];
  memcpy(ret_val->id_auth, buf+2, 6);

  /* XXX: should really issue a warning here... */
  if (ret_val->num_auths > WINSEC_MAX_SUBAUTHS)
    ret_val->num_auths = WINSEC_MAX_SUBAUTHS;

  if(buf_len < ret_val->num_auths*sizeof(uint32_t)+8)
  {
    talloc_free(ret_val);
    return NULL;
  }
  
  for(i=0; i < ret_val->num_auths; i++)
    ret_val->sub_auths[i] = IVAL(buf, 8+i*sizeof(uint32_t));

  return ret_val;
}


/******************************************************************************
 * Parses a WINSEC_UUID struct.
 ******************************************************************************/
WINSEC_UUID* winsec_parse_uuid(void* talloc_ctx,
			       const uint8_t* buf, uint32_t buf_len)
{
  WINSEC_UUID* ret_val;

  if(buf == NULL || buf_len < sizeof(WINSEC_UUID))
    return false;

  if((ret_val = talloc(talloc_ctx, WINSEC_UUID)) == NULL)
    return NULL;
  
  ret_val->time_low = IVAL(buf, 0x0);
  ret_val->time_mid = SVAL(buf, 0x4);
  ret_val->time_hi_and_version = SVAL(buf, 0x6);
  
  memcpy(ret_val->clock_seq, buf+0x8, 2);
  memcpy(ret_val->node, buf+0xB, 6);

  return ret_val;
}


/******************************************************************************
 * Calculates the size of a SID.
 ******************************************************************************/
size_t winsec_sid_size(const WINSEC_DOM_SID* sid)
{
  if (sid == NULL)
    return 0;

  return sid->num_auths * sizeof(uint32_t) + 8;
}


/******************************************************************************
 * Compare the auth portion of two SIDs.
 ******************************************************************************/
int winsec_sid_compare_auth(const WINSEC_DOM_SID* sid1, const WINSEC_DOM_SID* sid2)
{
  int i;

  if (sid1 == sid2)
    return 0;
  if (!sid1)
    return -1;
  if (!sid2)
    return 1;

  if (sid1->sid_rev_num != sid2->sid_rev_num)
    return sid1->sid_rev_num - sid2->sid_rev_num;

  for (i = 0; i < 6; i++)
    if (sid1->id_auth[i] != sid2->id_auth[i])
      return sid1->id_auth[i] - sid2->id_auth[i];

  return 0;
}


/******************************************************************************
 * Compare two SIDs.
 ******************************************************************************/
int winsec_sid_compare(const WINSEC_DOM_SID* sid1, const WINSEC_DOM_SID* sid2)
{
  int i;

  if (sid1 == sid2)
    return 0;
  if (!sid1)
    return -1;
  if (!sid2)
    return 1;

  /* Compare most likely different rids, first: i.e start at end */
  if (sid1->num_auths != sid2->num_auths)
    return sid1->num_auths - sid2->num_auths;

  for (i = sid1->num_auths-1; i >= 0; --i)
    if (sid1->sub_auths[i] != sid2->sub_auths[i])
      return sid1->sub_auths[i] - sid2->sub_auths[i];

  return winsec_sid_compare_auth(sid1, sid2);
}


/******************************************************************************
 * Compare two SIDs.
 ******************************************************************************/
bool winsec_sid_equal(const WINSEC_DOM_SID* sid1, const WINSEC_DOM_SID* sid2)
{
  return winsec_sid_compare(sid1, sid2) == 0;
}


/******************************************************************************
 ******************************************************************************/
char* winsec_sid2str(const WINSEC_DOM_SID* sid)
{
  uint32_t i, size = WINSEC_MAX_SUBAUTHS*11 + 24;
  uint32_t left = size;
  uint8_t comps;
  char* ret_val;

  if(sid == NULL)
    return NULL;
  comps = sid->num_auths;

  ret_val = malloc(size);
  if(ret_val == NULL)
    return NULL;

  if(comps > WINSEC_MAX_SUBAUTHS)
    comps = WINSEC_MAX_SUBAUTHS;

  left -= sprintf(ret_val, "S-%u-%u", sid->sid_rev_num, sid->id_auth[5]);

  for (i = 0; i < comps; i++) 
    left -= snprintf(ret_val+(size-left), left, "-%u", sid->sub_auths[i]);

  return ret_val;
}


/******************************************************************************
 * Compares two WINSEC_DESC structures.
 ******************************************************************************/
bool winsec_desc_equal(WINSEC_DESC* s1, WINSEC_DESC* s2)
{
  /* Trivial cases */
  if (!s1 && !s2)
    return true;
  if (!s1 || !s2)
    return false;

  /* Check top level stuff */
  if (s1->revision != s2->revision)
    return false;

  if (s1->control != s2->control)
    return false;

  /* Check owner and group */
  if (!winsec_sid_equal(s1->owner_sid, s2->owner_sid))
    return false;

  if (!winsec_sid_equal(s1->grp_sid, s2->grp_sid)) 
    return false;

  /* Check ACLs present in one but not the other */
  if ((s1->dacl && !s2->dacl) || (!s1->dacl && s2->dacl) ||
      (s1->sacl && !s2->sacl) || (!s1->sacl && s2->sacl)) 
  { return false; }

  /* Sigh - we have to do it the hard way by iterating over all
     the ACEs in the ACLs */
  if(!winsec_acl_equal(s1->dacl, s2->dacl) || !winsec_acl_equal(s1->sacl, s2->sacl)) 
    return false;

  return true;
}



/******************************************************************************
 * Compares two WINSEC_ACL structures.
 ******************************************************************************/
bool winsec_acl_equal(WINSEC_ACL* s1, WINSEC_ACL* s2)
{
  unsigned int i, j;

  /* Trivial cases */
  if (!s1 && !s2) 
    return true;
  if (!s1 || !s2) 
    return false;

  /* Check top level stuff */
  if (s1->revision != s2->revision)
    return false;

  if (s1->num_aces != s2->num_aces)
    return false;

  /* The ACEs could be in any order so check each ACE in s1 against 
     each ACE in s2. */

  for (i = 0; i < s1->num_aces; i++)
  {
    bool found = false;

    for (j = 0; j < s2->num_aces; j++) 
    {
      if (winsec_ace_equal(s1->aces[i], s2->aces[j])) 
      {
	found = true;
	break;
      }
    }

    if (!found)
      return false;
  }

  return true;
}


/******************************************************************************
 * Compares two WINSEC_ACE structures.
 ******************************************************************************/
bool winsec_ace_equal(WINSEC_ACE* s1, WINSEC_ACE* s2)
{
  /* Trivial cases */
  if (!s1 && !s2) 
    return true;
  if (!s1 || !s2) 
    return false;

  /* Check top level stuff */
  if (s1->type != s2->type || s1->flags != s2->flags ||
      s1->access_mask != s2->access_mask)
  { return false; }

  /* Check SID */
  if (!winsec_sid_equal(s1->trustee, s2->trustee))
    return false;

  return true;
}


/******************************************************************************
 * Check if ACE has OBJECT type.
 ******************************************************************************/
bool winsec_ace_object(uint8_t type)
{
  if (type == WINSEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT ||
      type == WINSEC_ACE_TYPE_ACCESS_DENIED_OBJECT ||
      type == WINSEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT ||
      type == WINSEC_ACE_TYPE_SYSTEM_ALARM_OBJECT) 
  { return true; }

  return false;
}
