/*

    File: ntfs.c

    Copyright (C) 1998-2007 Christophe GRENIER <grenier@cgsecurity.org>
  
    This software 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; either version 2 of the License, or
    (at your option) any later version.
  
    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 the Free Software Foundation, Inc., 51
    Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 */
/*
#define NTFS_DEBUG 1
 */


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
 
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#include <ctype.h>
#include "types.h"
#include "common.h"
#include "intrf.h"
#include "ntfs.h"
#include "fnctdsk.h"
#include "lang.h"
#include "log.h"

static int ntfs_read_MFT(disk_t *disk_car, partition_t *partition, const struct ntfs_boot_sector*ntfs_header,const int my_type,const int debug, const int dump_ind);
static int ntfs_get_attr_aux(const char *attr_record, const int my_type, partition_t *partition, const char *end, const int debug, const int dump_ind, const char*file_name_to_find);

unsigned int ntfs_sector_size(const struct ntfs_boot_sector *ntfs_header)
{ return (ntfs_header->sector_size[1]<<8)+ntfs_header->sector_size[0]; }

int check_NTFS(disk_t *disk_car,partition_t *partition,const int debug,const int dump_ind)
{
  unsigned char *buffer=(unsigned char*)MALLOC(DEFAULT_SECTOR_SIZE);
/*  log_trace("check_NTFS part_offset=%llu\n",(long long unsigned)partition->part_offset); */
  if(disk_car->read(disk_car,DEFAULT_SECTOR_SIZE, buffer, partition->part_offset)!=0)
  {
    free(buffer);
    return 1;
  }
  if(test_NTFS(disk_car,(struct ntfs_boot_sector*)buffer,partition,debug,dump_ind)!=0)
  {
    free(buffer);
    return 1;
  }
  set_NTFS_info(disk_car,(struct ntfs_boot_sector*)buffer,partition,debug,dump_ind);
  free(buffer);
  return 0;
}

int recover_NTFS(disk_t *disk_car, const struct ntfs_boot_sector*ntfs_header,partition_t *partition,const int debug, const int dump_ind, const int backup)
{
  uint64_t part_size;
  if(test_NTFS(disk_car,ntfs_header,partition,debug,dump_ind)!=0)
    return 1;
  if(debug>0)
  {
    log_ntfs_dump(ntfs_header);
  }
  part_size=(uint64_t)(le64(ntfs_header->sectors_nbr)+1)*ntfs_sector_size(ntfs_header);
  if(backup>0)
  {
    if(partition->part_offset+disk_car->sector_size<part_size)
    {
      log_warning("NTFS part_offset=%llu, part_size=%llu, sector_size=%u\n",
	  (long long unsigned)partition->part_offset, (long long unsigned)part_size,
	  disk_car->sector_size);
      log_warning("NTFS partition cannot be added (part_offset<part_size).\n");
      return 1;
    }
    if(debug>1)
      log_info("NTFS part_offset=%llu, part_size=%llu, sector_size=%u\n",
	  (long long unsigned)partition->part_offset, (long long unsigned)part_size,
	  disk_car->sector_size);
    partition->boot_sector=part_size/disk_car->sector_size-1;
    partition->part_offset=partition->part_offset+disk_car->sector_size-part_size;
    if(debug>1)
      log_info("part_offset=%llu\n",(long long unsigned)partition->part_offset);
  }
  partition->part_size=part_size;
  partition->part_type_i386=P_NTFS;
  set_NTFS_info(disk_car,ntfs_header,partition,debug,dump_ind);
  return 0;
}

int set_NTFS_info(disk_t *disk_car, const struct ntfs_boot_sector*ntfs_header,partition_t *partition,const int debug, const int dump_ind)
{
  partition->name[0]='\0';
  if(partition->boot_sector==0)
    strncpy(partition->info, "NTFS", sizeof(partition->info));
  else
    strncpy(partition->info, "NTFS found using backup sector!", sizeof(partition->info));
  return ntfs_read_MFT(disk_car, partition, ntfs_header,0x60,debug,dump_ind);
}

int test_NTFS(const disk_t *disk_car,const struct ntfs_boot_sector*ntfs_header, partition_t *partition,const int debug, const int dump_ind)
{
  const char*buffer=(const char*)ntfs_header;
  if(le16(ntfs_header->marker)==0xAA55)
  {
    if(memcmp(ntfs_header->system_id,"NTFS",4)==0)
    {
      if((le16(ntfs_header->reserved)>0) || (ntfs_header->fats>0) ||
	  (ntfs_header->dir_entries[0]!=0) || (ntfs_header->dir_entries[1]!=0) ||
	  (ntfs_header->sectors[0]!=0) || (ntfs_header->sectors[1]!=0) ||
	  (le16(ntfs_header->fat_length)!=0) || (le32(ntfs_header->total_sect)!=0))
	return 1;
      switch(ntfs_header->sectors_per_cluster)
      {
	case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128:
	  break;
	default:
	  return 1;
      }
      if((debug>0) || (dump_ind!=0))
      {
	log_info("NTFS at %u/%u/%u\n", offset2cylinder(disk_car,partition->part_offset),offset2head(disk_car,partition->part_offset),offset2sector(disk_car,partition->part_offset));
	if(dump_ind!=0)
	  dump_ncurses(buffer,DEFAULT_SECTOR_SIZE);
      }
      if(le16(ntfs_header->heads)!=disk_car->CHS.head+1)
      {
	aff_buffer(BUFFER_ADD,"Warning: Incorrect number of heads/cylinder %u (NTFS) != %u (HD)\n",le16(ntfs_header->heads),disk_car->CHS.head+1);
	log_warning("heads/cylinder %u (NTFS) != %u (HD)\n",le16(ntfs_header->heads),disk_car->CHS.head+1);
      }
      if(le16(ntfs_header->secs_track)!=disk_car->CHS.sector)
      {
	aff_buffer(BUFFER_ADD,"Warning: Incorrect number of sectors per track %u (NTFS) != %u (HD)\n",le16(ntfs_header->secs_track),disk_car->CHS.sector);
	log_warning("sect/track %u (NTFS) != %u (HD)\n",le16(ntfs_header->secs_track),disk_car->CHS.sector);
      }
      if(ntfs_sector_size(ntfs_header)!=disk_car->sector_size)
      {
	aff_buffer(BUFFER_ADD,"Warning: Incorrect number of bytes per sector %u (NTFS) != %u (HD)\n",ntfs_sector_size(ntfs_header),disk_car->sector_size);
	log_warning("Warning: Incorrect number of bytes per sector %u (NTFS) != %u (HD)\n",ntfs_sector_size(ntfs_header),disk_car->sector_size);
      }

      if(partition->part_size>0)
      {
	uint64_t part_size;
	part_size=le64(ntfs_header->sectors_nbr)+1;

	if(part_size*ntfs_sector_size(ntfs_header)>partition->part_size)
	{
	  aff_buffer(BUFFER_ADD,"Error: size boot_sector %lu > partition %lu\n",(long unsigned)part_size,(long unsigned)(partition->part_size/disk_car->sector_size));
	  log_error("Error: size boot_sector %lu > partition %lu\n",(long unsigned)part_size,(long unsigned)(partition->part_size/disk_car->sector_size));
	  return 1;
	}
	if((debug>0) && (part_size!=partition->part_size/disk_car->sector_size))
	{
	    log_info("Info: size boot_sector %lu, partition %lu\n",(long unsigned)part_size,(long unsigned)(partition->part_size/disk_car->sector_size));
	}
      }
      partition->upart_type=UP_NTFS;
      return 0;
    }
  }     /* fin marqueur de fin :)) */
  return 1;
}

/* */
int ntfs_get_attr(const char *mft_record, const int my_type, partition_t *partition, const char *end, const int debug, const int dump_ind, const char*file_name_to_find)
{
  const char *attr_record;
  /* Only check for magic DWORD here, fixup should have happened before */
  if(memcmp(mft_record,"FILE",4)) return 2;	/* NTFS_RECORD_TYPES == magic_FILE ?*/
  if(NTFS_GETU16(mft_record + 0x14)%8!=0)
    return 2;
  if(NTFS_GETU16(mft_record + 0x14)<42)		/* sizeof(MFT_RECORD)>=42 */
    return 2;
  /*	aff_buffer(BUFFER_ADD,"FILE\n"); */
  /*	aff_buffer(BUFFER_ADD,"seq nbr %lu ",NTFS_GETU16(mft_record+0x10)); */
  /*	aff_buffer(BUFFER_ADD,"main MFT record %lu ",NTFS_GETU64(mft_record+0x20)); */
  /* location of first attribute */
  attr_record= mft_record + NTFS_GETU16(mft_record + 0x14);
  return ntfs_get_attr_aux(attr_record, my_type, partition, end, debug, dump_ind, file_name_to_find);
}

static int ntfs_get_attr_aux(const char *attr_record, const int my_type, partition_t *partition, const char *end, const int debug, const int dump_ind, const char*file_name_to_find)
{
  int attr_type;
  while(1)
  {
    /* Resident attributes attr_len>=24(0x18), non resident is bigger */
    unsigned int attr_len;
    if(attr_record+0x18>=end)
    {
      if(debug>1)
      {
	log_error("ntfs_get_attr attr_record+0x18>=end\n");
      }
      return 2;
    }
    attr_type=NTFS_GETU32(attr_record);
    if(attr_type==-1) /* attribute list end with type -1 */
      return 0;
    attr_len=NTFS_GETU16(attr_record+4);
    if((attr_len%8!=0)||(attr_len<0x18))
    {
      if(debug>1)
      {
	log_error("ntfs_get_attr attr_type=%x attr_len=%u (attr_len%%8!0)||(attr_len<0x18)\n",attr_type,attr_len);
      }
      return 2;
    }
    if(debug>1)
    {
      log_info("attr_type=%x %s\n",attr_type,(NTFS_GETU8(attr_record+8)==0?"resident":"non resident"));
      dump_log(attr_record,attr_len);
    }
    if(dump_ind)
      dump_ncurses(attr_record, attr_len);
    if(NTFS_GETU8(attr_record+8)==0)	/* attribute is resident */
    {
      unsigned int attr_value_length=NTFS_GETU16(attr_record+0x10);
      unsigned int attr_value_offset=NTFS_GETU16(attr_record+0x14);
      const char *attr_list_entry=attr_record+attr_value_offset;
      if(attr_value_offset%8!=0)
      {
#ifdef NTFS_DEBUG
	log_debug("ntfs_get_attr attr_value_offset=%u (%%8!=0)\n",attr_value_offset);
#endif
	return 2;
      }
      if(attr_list_entry+26>=end)
      {
#ifdef NTFS_DEBUG
	log_debug("ntfs_get_attr attr_list_entry+26=%p, end=%p\n",attr_list_entry+26,end);
#endif
	return 2;
      }
      /* We found the attribute type. Is the name correct, too? */
      if((attr_value_offset+attr_value_length>attr_len) || (attr_list_entry+attr_len >= end))
      {
#ifdef NTFS_DEBUG
	// log_debug("ntfs_get_attr \n");
#endif
	return 2;
      }
      if((attr_type==my_type)&&(attr_value_offset!=0))
      {
	switch(attr_type)
	{
	  case 0x30:	/* AT_FILE_NAME */
	    {
	      const char *file_name_attr=attr_list_entry;
	      unsigned int file_name_length;
	      const char *name_it;
	      if(file_name_attr+0x42>=end)
		return 2;
	      file_name_length=NTFS_GETU8(file_name_attr+0x40);	/* size in unicode char */
	      if(file_name_attr+0x42+2*file_name_length>=end)
		return 2;
	      {
		char file_name[256+1];	/* used size is file_name_length+1 */
		unsigned int i;
		/*		aff_buffer(BUFFER_ADD,"MFT record nbr %lu ",NTFS_GETU64(file_name_attr)); */
		for(name_it=file_name_attr+0x42,i=0;i<file_name_length; name_it+=2,i++)
		  file_name[i]=*name_it;
		file_name[i]='\0';
		if(debug>1)
		{
		  log_debug("file_name=%s\n",file_name);
		}
		if(file_name_to_find!=NULL)
		{
		  if(attr_type==my_type)
		  {
		    if(strcmp(file_name_to_find,file_name)==0)
		      return 1;
		    else
		      return 2;
		  }
		} else
		  aff_buffer(BUFFER_ADD,"%s\n",file_name);
	      }
	    }
	    break;
	  case 0x60:	/* AT_VOLUME_NAME */
	    {
	      unsigned int volume_name_length=attr_value_length;
	      const char *name_it;
	      char *dest=partition->name;
	      volume_name_length/=2;	/* Unicode */
	      if(volume_name_length>sizeof(partition->name)-1)
		volume_name_length=sizeof(partition->name)-1;
	      for(name_it=attr_list_entry;(volume_name_length>0) && (*name_it!='\0') && (name_it[1]=='\0'); name_it+=2,volume_name_length--)
		*dest++=*name_it;
	      *dest++='\0'; /* 27 january 2003: Correct a bug found by Andreas du Plessis-Denz */
	    }
	    return 1;
	  case 0x90:	/* AT_INDEX_ROOT */
	    return NTFS_GETU32(attr_list_entry+8);	/* index_block_size */
	}
      }
    }
    else
    {	/* attribute is not resident */
      if(attr_type==my_type)
      {
	switch(attr_type)
	{
	  case 0x80:	/* AT_DATA */
	    {
	      /* buf must be unsigned! */
	      const unsigned char *buf;
	      uint8_t b;                   	/* Current byte offset in buf. */
	      uint16_t mapping_pairs_offset;
	      const unsigned char*attr_end;     /* End of attribute. */
	      long lcn;
	      int64_t deltaxcn = (int64_t)-1;	/* Change in [vl]cn. */
	      mapping_pairs_offset=NTFS_GETU16(attr_record+32);
	      buf=attr_record+mapping_pairs_offset;
	      attr_end = (const char*)attr_record + attr_len;
	      lcn = 0;
	      /* return first element of the run_list */
	      {
		b = *buf & 0xf;
		if (b){
		  if (buf + b > attr_end)
		  {
		    log_error("Attribut AT_DATA: bad size\n");
		    return 0;
		  }
		  for (deltaxcn = (int8_t)buf[b--]; b; b--)
		    deltaxcn = (deltaxcn << 8) + (uint8_t)buf[b];
		  /* Assume a negative length to indicate data corruption */
		  if (deltaxcn < 0)
		    log_error("Invalid length in mapping pairs array.\n");
		} else { /* The length entry is compulsory. */
		  log_error("Missing length entry in mapping pairs array.\n");
		}
		if (deltaxcn >= 0)
		{
		  if (!(*buf & 0xf0))
		  {
		    log_info("LCN_HOLE\n");
		  }
		  else
		  {
		    /* Get the lcn change which really can be negative. */
		    uint8_t b2 = *buf & 0xf;
		    b = b2 + ((*buf >> 4) & 0xf);
		    if (buf + b > attr_end)
		    {
		      log_error("Attribut AT_DATA: bad size\n");
		      return 0;
		    }
		    for (deltaxcn = (int8_t)buf[b--]; b > b2; b--)
		      deltaxcn = (deltaxcn << 8) + (uint8_t)buf[b];
		    /* Change the current lcn to it's new value. */
		    lcn += deltaxcn;
		    /* Check lcn is not below -1. */
		    if (lcn < -1) {
		      log_error("Invalid LCN < -1 in mapping pairs array.");
		      return 0;
		    }
		    if(debug>1)
		    {
		      log_debug("LCN %ld\n",lcn);
		    }
		    if(attr_type==my_type)
		      return lcn;
		  }
		}
	      }
	    }
	    break;
	}
      }
    }
    attr_record+=attr_len;
  }
}

static int ntfs_read_MFT(disk_t *disk_car, partition_t *partition, const struct ntfs_boot_sector*ntfs_header,const int my_type,const int debug, const int dump_ind)
{
  unsigned char *buffer;
  char *attr;
  uint64_t mft_pos;
  unsigned int mft_record_size;
  unsigned int mft_size;
  mft_pos=partition->part_offset+(uint64_t)(le16(ntfs_header->reserved)+le64(ntfs_header->mft_lcn)*ntfs_header->sectors_per_cluster)*ntfs_sector_size(ntfs_header);
  if(ntfs_header->clusters_per_mft_record>0)
    mft_record_size=ntfs_header->sectors_per_cluster*ntfs_header->clusters_per_mft_record;
  else
    mft_record_size=1<<(-ntfs_header->clusters_per_mft_record);
  /* Only need the first 4 MFT record */
  mft_size=4*mft_record_size*ntfs_sector_size(ntfs_header);
#ifdef NTFS_DEBUG
  log_debug("NTFS cluster size = %u\n",ntfs_header->sectors_per_cluster);
  log_debug("NTFS MFT cluster = %lu\n",le64(ntfs_header->mft_lcn));
  log_debug("NTFS MFT_record_size = %u\n",mft_record_size);
  log_debug("NTFS sector size= %u\n", ntfs_sector_size(ntfs_header));
#endif
  buffer=(unsigned char *)MALLOC(mft_size);
  if(disk_car->read(disk_car,mft_size, buffer, mft_pos)!=0)
  {
    log_error("NTFS: Can't read MFT\n");
    free(buffer);
    return 1;
  }
  attr=(char*)buffer;
  while(attr+0x30<=(char*)(buffer+mft_size))
  {
    int res=ntfs_get_attr(attr,my_type,partition,(char*)buffer+mft_size,debug,dump_ind,NULL);
    if((res>0)|| (NTFS_GETU32(attr + 0x1C)<0x30))
    {
      free(buffer);
      return res;
    }
    attr+= NTFS_GETU32(attr + 0x1C);
  }
  free(buffer);
  return 0;
}

int is_part_ntfs(const partition_t *partition)
{
  switch(partition->part_type_i386)
  {
    case P_NTFS:
    case P_NTFSH:
      return 1;
    default:
      return 0;
  }
}

int is_ntfs(const partition_t *partition)
{
  return(is_part_ntfs(partition) || partition->upart_type==UP_NTFS);
}

int log_ntfs_dump(const struct ntfs_boot_sector *ntfs_header)
{
  log_info("filesystem size           %llu\n", (long long unsigned)le64(ntfs_header->sectors_nbr)+1);
  log_info("sectors_per_cluster       %u\n", ntfs_header->sectors_per_cluster);
  log_info("mft_lcn                   %lu\n", (long unsigned int)le64(ntfs_header->mft_lcn));
  log_info("mftmirr_lcn               %lu\n", (long unsigned int)le64(ntfs_header->mftmirr_lcn));
  log_info("clusters_per_mft_record   %d\n", ntfs_header->clusters_per_mft_record);
  log_info("clusters_per_index_record %d\n", ntfs_header->clusters_per_index_record);
  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1