www.pudn.com > chntpw-source-000607.zip > ntreg.c


/*
 * ntreg.c - NT Registry Hive access library (read only for now)
 * 
 * Copyright (c) 1997-2000 Petter Nordahl-Hagen.
 * Freely distributable in source or binary for noncommercial purposes.
 * 
 * Please see the COPYING file for more details on
 * copyrights & credits.
 *  
 * THIS SOFTWARE IS PROVIDED BY PETTER NORDAHL-HAGEN `AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */ 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "ntreg.h"

const char ntreg_version[] = "ntreg lib routines, v0.81 000607 , (c) Petter N Hagen";

char *val_types[REG_MAX+1] = {
  "REG_NONE", "REG_SZ", "REG_EXPAND_SZ", "REG_BINARY", "REG_DWORD",       /* 0 - 4 */
  "REG_DWORD_BIG_ENDIAN", "REG_LINK",                                     /* 5 - 6 */
  "REG_MULTI_SZ", "REG_RESOUCE_LIST", "REG_FULL_RES_DESC", "REG_RES_REQ", /* 7 - 10 */
};

/* Utility routines */
char *str_dup( const char *str )
{
    char *str_new;

    if (!str)
        return 0 ;

    CREATE( str_new, char, strlen(str) + 1 );
    strcpy( str_new, str );
    return str_new;
}

int fmyinput(char *prmpt, char *ibuf, int maxlen)
{
   
   printf("%s",prmpt);
   
   fgets(ibuf,maxlen+1,stdin);
   
   ibuf[strlen(ibuf)-1] = 0;
   
   return(strlen(ibuf));
}

/* Print len number of hexbytes */

void hexprnt(char *s, unsigned char *bytes, int len)
{
int i;

   printf("%s",s);
   for (i = 0; i < len; i++) {
      printf("%02x ",bytes[i]);
   }
   printf("\n");
}

/* HexDump all or a part of some buffer */

void hexdump(char *hbuf, int start, int stop, int ascii)
{
   char c;
   int diff,i;
   
   while (start < stop ) {
      
      diff = stop - start;
      if (diff > 16) diff = 16;
      
      printf(":%05X  ",start);

      for (i = 0; i < diff; i++) {
	 printf("%02X ",(unsigned char)*(hbuf+start+i));
      }
      if (ascii) {
	for (i = diff; i < 16; i++) printf("   ");
	for (i = 0; i < diff; i++) {
	  c = *(hbuf+start+i);
	  printf("%c", isprint(c) ? c : '.');
	}
      }
      printf("\n");
      start += 16;
   }
}

/* General search routine, find something in something else */
int find_in_buf(char *buf, char *what, int sz, int len, int start)
{
   int i;
   
   for (; start < sz; start++) {
      for (i = 0; i < len; i++) {
	if (*(buf+start+i) != *(what+i)) break;
      }
      if (i == len) return(start);
   }
   return(0);
}

/* Get INTEGER from memory. This is probably low-endian specific? */
int get_int( char *array )
{
	return ((array[0]&0xff) + ((array[1]<<8)&0xff00) +
		   ((array[2]<<16)&0xff0000) +
		   ((array[3]<<24)&0xff000000));
}


/* Quick and dirty UNICODE to std. ascii */

void cheap_uni2ascii(char *src, char *dest, int l)
{
   
   for (; l > 0; l -=2) {
      *dest = *src;
      dest++; src +=2;
   }
   *dest = 0;
}


/* Quick and dirty ascii to unicode */

void cheap_ascii2uni(char *src, char *dest, int l)
{
   for (; l > 0; l--) {
      *dest++ = *src++;
      *dest++ = 0;

   }
}

void skipspace(char **c)
{
   while( **c == ' ' ) (*c)++;
}

int gethex(char **c)
{
   int value;
   
   skipspace(c);
   
   if (!(**c)) return(0);

   sscanf(*c,"%x",&value);

   while( **c != ' ' && (**c)) (*c)++;

   return(value);
}
   
/* Get a string of HEX bytes (space separated),
 * or if first char is ' get an ASCII string instead.
 */

int gethexorstr(char **c, char *wb)
{
   int l = 0;
   
   skipspace(c);
   
   if ( **c == '\'') {
      (*c)++;
      while ( **c ) {
	 *(wb++) = *((*c)++);
	 l++;
      }
   } else {
      do {
	 *(wb++) = gethex(c);
	 l++;
	 skipspace(c);
      } while ( **c );
   }
   return(l);
}

/* Simple buffer debugger, returns 1 if buffer dirty/edited */

int debugit(char *buf, int sz)
{


   char inbuf[100],whatbuf[100],*bp;

   int dirty=0,to,from,l,i,j,wlen,cofs = 0;
   
   printf("Buffer debugger. '?' for help.\n");
   
   while (1) {
      l = fmyinput(".",inbuf,90);
      bp = inbuf;

      skipspace(&bp);

      if (l > 0 && *bp) {
	 switch(*bp) {
	  case 'd' :
	    bp++;
	    if (*bp) {
	       from = gethex(&bp);
	       to   = gethex(&bp);
	    } else {
	       from = cofs; to = 0;
	    }
	    if (to == 0) to = from + 0x100;
	    if (to > sz) to = sz;
	    hexdump(buf,from,to,1);
	    cofs = to;
	    break;
	  case 'a' :
	    bp++;
	    if (*bp) {
	       from = gethex(&bp);
	       to   = gethex(&bp);
	    } else {
	       from = cofs; to = 0;
	    }
	    if (to == 0) to = from + 0x100;
	    if (to > sz) to = sz;
	    hexdump(buf,from,to,0);
	    cofs = to;
	    break;
#if 0
	  case 'k' :
	    bp++;
	    if (*bp) {
	       from = gethex(&bp);
	    } else {
	       from = cofs;
	    }
	    if (to > sz) to = sz;
	    parse_block(from,1);
	    cofs = to;
	    break;
#endif
#if 0
	  case 'l' :
	    bp++;
	    if (*bp) {
	       from = gethex(&bp);
	    } else {
	       from = cofs;
	    }
	    if (to > sz) to = sz;
	    nk_ls(from+4,0);
	    cofs = to;
	    break;
#endif
	  case 'q':
	    return(0);
	    break;
	  case 's':
	    if (!dirty) printf("Buffer has not changed, no need to write..\n");
	    return(dirty);
	    break;
	  case 'h':
	    bp++;
	    if (*bp == 'a') {
	       from = 0;
	       to = sz;
	       bp++;
	    } else {
	       from = gethex(&bp);
	       to   = gethex(&bp);
	    }
	    wlen = gethexorstr(&bp,whatbuf);
	    if (to > sz) to = sz;
	    printf("from: %x, to: %x, wlen: %d\n",from,to,wlen);
	    for (i = from; i < to; i++) {
	       for (j = 0; j < wlen; j++) {
		  if ( *(buf+i+j) != *(whatbuf+j)) break;
	       }
	       if (j == wlen) printf("%06x ",i);
	    }
	    printf("\n");
	    break;
	  case ':':
	    bp++;
	    if (!*bp) break;
	    from = gethex(&bp);
	    wlen = gethexorstr(&bp,whatbuf);
	    
	    printf("from: %x, wlen: %d\n",from,wlen);

	    memcpy(buf+from,whatbuf,wlen);
	    dirty = 1;
	    break;
#if 0
	  case 'p':
	    j = 0;
	    if (*(++bp) != 0) {
	       from = gethex(&bp);
	    }
	    if (*(++bp) != 0) {
	       j = gethex(&bp);
	    }
	    printf("from: %x, rid: %x\n",from,j);
	    seek_n_destroy(from,j,500,0);
	    break;
#endif
	  case '?':
	    printf("d [] [] - dump buffer within range\n");
	    printf("a [] [] - same as d, but without ascii-part (for cut'n'paste)\n");
	    printf(":   [ ...] - change bytes\n");
	    printf("h    [ ...] - hunt (search) for bytes\n");
	    printf("ha  [ etc. you may give 'string to enter/search a string\n");
	    break;
	  default:
	    printf("?\n");
	    break;
	 }
      }
   }
}


/* ========================================================================= */

/* The following routines are mostly for debugging, I used it
 * much during discovery. the -t command line option uses it,
 * also the 'st' and 's' from the editor & hexdebugger.
 * All offsets shown in these are unadjusted (ie you must add
 * headerpage (most often 0x1000) to get file offset)
 */

/* Parse the nk datablock
 * vofs = offset into struct (after size linkage)
 */
void parse_nk(struct hive *hdesc, int vofs, int blen)
{

  struct nk_key *key;
  int i;

  printf("== nk at offset %0x\n",vofs);

#define D_OFFS(o) ( (void *)&(key->o)-(void *)hdesc->buffer-vofs )

  key = (struct nk_key *)(hdesc->buffer + vofs);
  printf("%04x   type              = 0x%02x %s\n", D_OFFS(type)  , key->type,
	                           (key->type == KEY_ROOT ? "ROOT_KEY" : "") );
  printf("%04x   timestamp skipped\n", D_OFFS(timestamp) );
  printf("%04x   parent key offset = 0x%0x\n", D_OFFS(ofs_parent) ,key->ofs_parent);
  printf("%04x   number of subkeys = %d\n", D_OFFS(no_subkeys),key->no_subkeys);
  printf("%04x   lf-record offset  = 0x%0x\n",D_OFFS(ofs_lf),key->ofs_lf);
  printf("%04x   number of values  = %d\n", D_OFFS(no_values),key->no_values);
  printf("%04x   val-list offset   = 0x%0x\n",D_OFFS(ofs_vallist),key->ofs_vallist);
  printf("%04x   sk-record offset  = 0x%0x\n",D_OFFS(ofs_sk),key->ofs_sk);
  printf("%04x   classname offset  = 0x%0x\n",D_OFFS(ofs_classnam),key->ofs_classnam);
  printf("%04x   *unused?*         = 0x%0x\n",D_OFFS(dummy4),key->dummy4);
  printf("%04x   name length       = %d\n", D_OFFS(len_name),key->len_name);
  printf("%04x   classname length  = %d\n", D_OFFS(len_classnam),key->len_classnam);

  printf("%04x   Key name: <",D_OFFS(keyname) );
  for(i = 0; i < key->len_name; i++) putchar(key->keyname[i]);
  printf(">\n== End of key info.\n");

}

/* Parse the vk datablock
 * vofs = offset into struct (after size linkage)
 */
void parse_vk(struct hive *hdesc, int vofs, int blen)
{
  struct vk_key *key;
  int i;

  printf("== vk at offset %0x\n",vofs);


  key = (struct vk_key *)(hdesc->buffer + vofs);
  printf("%04x   name length       = %d (0x%0x)\n", D_OFFS(len_name),
	                             key->len_name, key->len_name  );
  printf("%04x   length of data    = %d (0x%0x)\n", D_OFFS(len_data),
	                             key->len_data, key->len_data  );
  printf("%04x   data offset       = 0x%0x\n",D_OFFS(ofs_data),key->ofs_data);
  printf("%04x   value type        = 0x%0x  %s\n", D_OFFS(val_type), key->val_type,
                 (key->val_type <= REG_MAX ? val_types[key->val_type] : "(unknown)") ) ;

  printf("%04x   flag              = 0x%0x\n",D_OFFS(flag),key->flag);
  printf("%04x   *unused?*         = 0x%0x\n",D_OFFS(dummy1),key->dummy1);

  printf("%04x   Key name: <",D_OFFS(keyname) );
  for(i = 0; i < key->len_name; i++) putchar(key->keyname[i]);
  printf(">\n== End of key info.\n");

}

/* Parse the sk datablock
 * Gee, this is the security info. Who cares? *evil grin*
 * vofs = offset into struct (after size linkage)
 */
void parse_sk(struct hive *hdesc, int vofs, int blen)
{
  struct sk_key *key;
  int i;

  printf("== sk at offset %0x\n",vofs);

  key = (struct sk_key *)(hdesc->buffer + vofs);
  printf("%04x   *unused?*         = %d\n"   , D_OFFS(dummy1),     key->dummy1    );
  printf("%04x   Offset to prev sk = 0x%0x\n", D_OFFS(ofs_prevsk), key->ofs_prevsk);
  printf("%04x   Offset to next sk = 0x%0x\n", D_OFFS(ofs_nextsk), key->ofs_nextsk);
  printf("%04x   Usage counter     = %d (0x%0x)\n", D_OFFS(no_usage),
	                                            key->no_usage,key->no_usage);
  printf("%04x   Security data len = %d (0x%0x)\n", D_OFFS(len_sk),
	                                            key->len_sk,key->len_sk);

  printf("== End of key info.\n");

}


/* Parse the lf datablock (>4.0 'nk' offsets lookuptable)
 * vofs = offset into struct (after size linkage)
 */
void parse_lf(struct hive *hdesc, int vofs, int blen)
{
  struct lf_key *key;
  int i;

  printf("== lf at offset %0x\n",vofs);

  key = (struct lf_key *)(hdesc->buffer + vofs);
  printf("%04x   number of keys    = %d\n", D_OFFS(no_keys), key->no_keys  );

  for(i = 0; i < key->no_keys; i++) {
    printf("%04x      %3d   Offset: 0x%0x  - <%c%c%c%c>\n", 
	   D_OFFS(hash[i].ofs_nk), i,
	   key->hash[i].ofs_nk,
           key->hash[i].name[0],
           key->hash[i].name[1],
           key->hash[i].name[2],
           key->hash[i].name[3] );
  }

  printf("== End of key info.\n");

}

/* Parse the li datablock (3.x 'nk' offsets list)
 * vofs = offset into struct (after size linkage)
 */
void parse_li(struct hive *hdesc, int vofs, int blen)
{
  struct li_key *key;
  int i;

  printf("== li at offset %0x\n",vofs);

#define D_OFFS(o) ( (void *)&(key->o)-(void *)hdesc->buffer-vofs )

  key = (struct li_key *)(hdesc->buffer + vofs);
  printf("%04x   number of keys    = %d\n", D_OFFS(no_keys), key->no_keys  );

  for(i = 0; i < key->no_keys; i++) {
    printf("%04x      %3d   Offset: 0x%0x\n", 
	   D_OFFS(hash[i].ofs_nk), i,
	   key->hash[i].ofs_nk);
  }
  printf("== End of key info.\n");

}


/* Parse the datablock
 * vofs = offset into struct (after size linkage)
 */

int parse_block(struct hive *hdesc, int vofs,int verbose)
{
  unsigned short id;
  int seglen;

  seglen = get_int(hdesc->buffer+vofs);  

  if (verbose || seglen == 0) {
    printf("** Block at offset %0x\n",vofs);
    printf("seglen: %d, %u, 0x%0x\n",seglen,seglen,seglen);
  }
  if (seglen == 0) {
    printf("Whoops! FATAL! Zero data block size! (not registry or corrupt file?)\n");
    debugit(hdesc->buffer,hdesc->size);
    return(0);
  }
  
  if (seglen < 0) {
    seglen = -seglen;
    hdesc->usetot += seglen;
    hdesc->useblk++;
    if (verbose) {
      printf("USED BLOCK: %d, 0x%0x\n",seglen,seglen);
      hexdump(hdesc->buffer,vofs,vofs+seglen+4,1);
    }
  } else {
    hdesc->unusetot += seglen;
    hdesc->unuseblk++;
    if (verbose) {
      printf("FREE BLOCK!\n"); 
      hexdump(hdesc->buffer,vofs,vofs+seglen+4,1);
    }
  }
  vofs += 4;
  id = (*(hdesc->buffer + vofs)<<8) + *(hdesc->buffer+vofs+1);

  if (verbose) {
    switch (id) {
    case 0x6e6b: /* nk */
      parse_nk(hdesc, vofs, seglen);
      break;
    case 0x766b: /* vk */
      parse_vk(hdesc, vofs, seglen);
      break;
    case 0x6c66: /* lf */
      parse_lf(hdesc, vofs, seglen);
      break;
    case 0x6c69: /* li */
      parse_li(hdesc, vofs, seglen);
      break;
    case 0x736b: /* sk */
      parse_sk(hdesc, vofs, seglen);
      break;
    default:
      printf("Not handeled yet!\n");
      break;
    }
  }
  return(seglen);
}

/* ================================================================ */

/* ** Registry manipulation routines ** */



/* "directory scan", return next name/pointer of a subkey on each call
 * nkofs = offset to directory to scan
 * lfofs = pointer to int to hold the current scan position,
 *         set position to 0 to start.
 * sptr  = pointer to struct to hold a single result
 * returns: -1 = error. 0 = end of key. 1 = more subkeys to scan
 * NOTE: caller must free the name-buffer (struct ex_data *name)
 */
int ex_next_n(struct hive *hdesc, int nkofs, int *count, int *countri, struct ex_data *sptr)
{
  struct nk_key *key, *newnkkey;
  int newnkofs;
  struct lf_key *lfkey;
  struct li_key *likey;
  struct ri_key *rikey;


  if (!nkofs) return(-1);
  key = (struct nk_key *)(hdesc->buffer + nkofs);
  if (key->id != 0x6b6e) {
    printf("ex_next error: Not a 'nk' node at 0x%0x\n",nkofs);
    return(-1);
  }

#undef EXNDEBUG

  lfkey = (struct lf_key *)(hdesc->buffer + key->ofs_lf + 0x1004);
  rikey = (struct ri_key *)(hdesc->buffer + key->ofs_lf + 0x1004);

  if (rikey->id == 0x6972) {   /* Is it extended 'ri'-block? */
#if EXNDEBUG
    printf("%d , %d\n",*countri,*count);
#endif
    if (*countri < 0 || *countri >= rikey->no_lis) { /* End of ri's? */
      return(0);
    }
    /* Get the li-struct that's current based on countri */
    likey = (struct li_key *)( hdesc->buffer + rikey->hash[*countri].ofs_li + 0x1004 ) ;
    newnkofs = likey->hash[*count].ofs_nk + 0x1000;
    /* Check if current li is exhausted */
#if EXNDEBUG
    printf("likey->no_keys = %d\n",likey->no_keys);
#endif
    if (*count >= likey->no_keys-1) { /* Last legal entry in li list? */
      (*countri)++;  /* Bump up ri count so we take next ri entry next time */
      (*count) = -1;  /* Reset li traverse counter for next round, not used later here */
    }
  } else { /* Plain handler */
    if (key->no_subkeys <= 0 || *count >= key->no_subkeys) {
      return(0);
    }
    if (lfkey->id == 0x696c) {   /* Is it 3.x 'li' instead? */
      likey = (struct li_key *)(hdesc->buffer + key->ofs_lf + 0x1004);
      newnkofs = likey->hash[*count].ofs_nk + 0x1000;
    } else {
      newnkofs = lfkey->hash[*count].ofs_nk + 0x1000;
    }
  }

  sptr->nkoffs = newnkofs;
  newnkkey = (struct nk_key *)(hdesc->buffer + newnkofs + 4);
  sptr->nk = newnkkey;

  if (newnkkey->id != 0x6b6e) {
    printf("ex_next: ERROR: not 'nk' node at 0x%0x\n",newnkofs);

    return(-1);
  } else {
    if (newnkkey->len_name <= 0) {
      printf("ex_next: nk at 0x%0x has no name!\n",newnkofs);
    } else {
      sptr->name = (char *)malloc(newnkkey->len_name+1);
      if (!sptr->name) {
	printf("FATAL! ex_next: malloc() failed! Out of memory?\n");
	abort();
      }
      strncpy(sptr->name,newnkkey->keyname,newnkkey->len_name);
      sptr->name[newnkkey->len_name] = 0;
    }
  } /* if */
  (*count)++;
  return(1);
  /*  return( *count <= key->no_subkeys); */
}

/* "directory scan" for VALUES, return next name/pointer of a value on each call
 * nkofs = offset to directory to scan
 * lfofs = pointer to int to hold the current scan position,
 *         set position to 0 to start.
 * sptr  = pointer to struct to hold a single result
 * returns: -1 = error. 0 = end of key. 1 = more values to scan
 * NOTE: caller must free the name-buffer (struct vex_data *name)
 */
int ex_next_v(struct hive *hdesc, int nkofs, int *count, struct vex_data *sptr)
{
  struct nk_key *key, *newnkkey;
  int vkofs,vlistofs;
  int *vlistkey;
  struct vk_key *vkkey;


  if (!nkofs) return(-1);
  key = (struct nk_key *)(hdesc->buffer + nkofs);
  if (key->id != 0x6b6e) {
    printf("ex_next_v error: Not a 'nk' node at 0x%0x\n",nkofs);
    return(-1);
  }

  if (key->no_values <= 0 || *count >= key->no_values) {
    return(0);
  }

  vlistofs = key->ofs_vallist + 0x1004;
  vlistkey = (int *)(hdesc->buffer + vlistofs);

  vkofs = vlistkey[*count] + 0x1004;
  vkkey = (struct vk_key *)(hdesc->buffer + vkofs);
  if (vkkey->id != 0x6b76) {
    printf("ex_next_v: hit non valuekey (vk) node during scan at offs 0x%0x\n",vkofs);
    return(-1);
  }

  /*  parse_vk(hdesc, vkofs, 4); */

  sptr->vk = vkkey;
  sptr->vkoffs = vkofs;
  sptr->name = 0;
  sptr->size = (vkkey->len_data & 0x7fffffff);

  if (vkkey->len_name >0) {
    CREATE(sptr->name,char,vkkey->len_name+1);
    memcpy(sptr->name,vkkey->keyname,vkkey->len_name);
    sptr->name[vkkey->len_name] = 0;
  } else {
    sptr->name = str_dup("@");
  }

  sptr->type = vkkey->val_type;
  if (sptr->size) {
    if (vkkey->val_type == REG_DWORD) {
      if (vkkey->len_data & 0x80000000) {
	sptr->val = (int)(vkkey->ofs_data);
      }
    }
  } else { 
    /* Data SIZE is 0, special inline case, data is DWORD and in TYPE field!! */
    sptr->val = sptr->type;
    sptr->type = REG_DWORD;
    sptr->size = 4;
  }

  (*count)++;
  return( *count <= key->no_values );
}

/* traceback - trace nk's back to root,
 * building path string as we go.
 * nkofs  = offset to nk-node
 * path   = pointer to pathstring-buffer
 * maxlen = max length of path-buffer
 * return: length of path string
 */

int get_abs_path(struct hive *hdesc, int nkofs, char *path, int maxlen)
{
  int newnkofs;
  struct nk_key *key;
  char tmp[ABSPATHLEN+1];

  maxlen = (maxlen < ABSPATHLEN ? maxlen : ABSPATHLEN);

  key = (struct nk_key *)(hdesc->buffer + nkofs);
  
  if (key->id != 0x6b6e) {
    printf("get_abs_path: Not a 'nk' node!\n");
    return(0);
  }

  if (key->type == KEY_ROOT) {   /* We're at the root */
    return(strlen(path));
  }

  strncpy(tmp,path,ABSPATHLEN-1);

  if ( (strlen(path) + key->len_name) >= maxlen-6) {
    snprintf(path,maxlen,"(...)%s",tmp);
    return(strlen(path));   /* Stop trace when string exhausted */
  }
  *path = '\\';
  memcpy(path+1,key->keyname,key->len_name);
  strncpy(path+key->len_name+1,tmp,maxlen);
  return(get_abs_path(hdesc, key->ofs_parent+0x1004, path, maxlen)); /* go back one more */
}


/* Recursevely follow 'nk'-nodes based on a path-string,
 * returning offset of last 'nk' or 'vk'
 * vofs - offset to start node
 * path - null-terminated pathname (relative to vofs, \ is separator)
 * type - type to return 0=nk 1=vk
 * return: offset to nk or vk (or NULL if not found)
 */

int trav_path(struct hive *hdesc, int vofs, char *path, int type)
{
  struct nk_key *key, *newnkkey;
  struct lf_key *lfkey;
  struct li_key *likey;
  struct vk_key *vkkey;
  struct ri_key *rikey;

  long *vlistkey;
  int newnkofs, plen, i, lfofs, vkofs, vlistofs, adjust, r, ricnt, subs;
  char *buf;

  buf = hdesc->buffer;

  if (*path == '\\') {      /* Start from root if path starts with \ */
    path++;
    vofs = hdesc->rootofs+4;
  }

  key = (struct nk_key *)(buf + vofs);
  /*  printf("check of nk at offset: 0x%0x\n",vofs); */

  if (key->id != 0x6b6e) {
    printf("trav_path: Error: Not a 'nk' node!\n");
    return(0);
  }

  for(plen = 0; path[plen] && path[plen] != '\\'; plen++) ;
  adjust = path[plen] == '\\' ? 1 : 0;
  /* printf("Checking for <%s> with len %d\n",path,plen); */
  if (!plen) return(vofs-4);     /* Path has no lenght - we're there! */
  if ( (plen == 1) && (*path == '.')) {     /* Handle '.' current dir */
    return(trav_path(hdesc,vofs,path+plen+adjust,type));
  }
  if ( (plen == 2) && !strncmp("..",path,2) ) { /* Get parent key */
    newnkofs = key->ofs_parent + 0x1004;
    /* Return parent (or only root if at the root) */
    return(trav_path(hdesc, (key->type == KEY_ROOT ? vofs : newnkofs), path+plen+adjust, type));
  }

  /* at last name of path, and we want vk, and the nk has values */
  if (!path[plen] && type == 1 && key->no_values) {   
    vlistofs = key->ofs_vallist + 0x1004;
    vlistkey = (long *)(buf + vlistofs);
    for (i = 0; i < key->no_values; i++) {
      vkofs = vlistkey[i] + 0x1004;
      vkkey = (struct vk_key *)(buf + vkofs);
      if (vkkey->len_name == 0 && *path == '@') { /* @ is alias for nameless value */
	return(vkofs-4);
      }
      if (!strncmp(path, vkkey->keyname, plen)) { /* name match? */
	return(vkofs-4);
      }
    }
  }

  if (key->no_subkeys > 0) {    /* If it has subkeys, loop through the hash */
    lfofs = key->ofs_lf + 0x1004;    /* lf (hash) record */
    lfkey = (struct lf_key *)(buf + lfofs);

    if (lfkey->id == 0x6972) { /* ri struct need special parsing */
      /* Prime loop state */

      rikey = (struct ri_key *)lfkey;
      ricnt = rikey->no_lis;
      r = 0;
      likey = (struct li_key *)( hdesc->buffer + rikey->hash[r].ofs_li + 0x1004 ) ;
      subs = likey->no_keys;
    } else {
      if (lfkey->id == 0x696c) { /* li? */
	likey = (struct li_key *)(buf + lfofs);
      } else {
	likey = NULL;
      }
      ricnt = 0; r = 0; subs = key->no_subkeys;
    }

    do {
      for(i = 0; i < subs; i++) {
	if (likey) newnkofs = likey->hash[i].ofs_nk + 0x1004;
	else newnkofs = lfkey->hash[i].ofs_nk + 0x1004;
	newnkkey = (struct nk_key *)(buf + newnkofs);
	if (newnkkey->id != 0x6b6e) {
	  printf("ERROR: not 'nk' node! (strange?)\n");
	} else {
	  if (newnkkey->len_name <= 0) {
	    printf("[No name]\n");
	  } else {
	    if (!strncmp(path,newnkkey->keyname,plen)) {
	      /*	    printf("Key at 0x%0x matches! recursing!\n",newnkofs); */
	      return(trav_path(hdesc, newnkofs, path+plen+adjust, type));
	    }
	  }
	} /* if id OK */
      } /* hash loop */
      r++;
      if (ricnt && r < ricnt) {
	newnkofs = rikey->hash[r].ofs_li;
	likey = (struct li_key *)( hdesc->buffer + newnkofs + 0x1004 ) ;
	subs = likey->no_keys;
      }
    } while (r < ricnt && ricnt);

  } /* if subkeys */
  /* Not found */
  return(0);
}

/* ls - list a 'nk' nodes subkeys and values
 * vofs - offset to start of data (skipping block linkage)
 * type - 0 = full, 1 = keys only. 2 = values only
 */
void nk_ls(struct hive *hdesc, char *path, int vofs, int type)
{
  struct nk_key *key;
  int nkofs;
  struct ex_data ex;
  struct vex_data vex;
  int count = 0, countri = 0;
  

  nkofs = trav_path(hdesc, vofs, path, 0);

  if(!nkofs) {
    printf("nk_ls: Key <%s> not found\n",path);
    abort();
    return;
  }
  nkofs += 4;

  key = (struct nk_key *)(hdesc->buffer + nkofs);
  printf("ls of node at offset 0x%0x\n",nkofs);

  if (key->id != 0x6b6e) {
    printf("Error: Not a 'nk' node!\n");

    debugit(hdesc->buffer,hdesc->size);
    
  }
  
  printf("Node has %d subkeys and %d values\n",key->no_subkeys,key->no_values);

  if (key->no_subkeys) {
    printf("offs          key name\n");
    while ((ex_next_n(hdesc, nkofs, &count, &countri, &ex) > 0)) {
      printf("[%6x]   <%s>\n", ex.nkoffs, ex.name);
      FREE(ex.name);
    }
  }
  count = 0;
  if (key->no_values) {
    printf("offs        size      type   value name                    [value if type DWORD]\n");
    while ((ex_next_v(hdesc, nkofs, &count, &vex) > 0)) {
      printf("[%6x] %6d  %-16s  <%s>", vex.vkoffs, vex.size,
	     (vex.type < REG_MAX ? val_types[vex.type] : "(unknown)"), vex.name);
      if (vex.type == REG_DWORD) printf(" %*d [0x%x]",25-strlen(vex.name),vex.val , vex.val);
      printf("\n");
      FREE(vex.name);
    }
  }
}

/* Get the type of a value */
int get_val_type(struct hive *hdesc, int vofs, char *path)
{
  struct vk_key *vkkey;
  int vkofs;

  vkofs = trav_path(hdesc, vofs,path,1);
  if (!vkofs) {
    return -1;
  }
  vkofs +=4;
  vkkey = (struct vk_key *)(hdesc->buffer + vkofs);
  if (vkkey->len_data & 0x80000000) return(REG_DWORD); /* Special case of INLINE storage */
  return(vkkey->val_type);
}


/* Get len of a value, given current key + path */
int get_val_len(struct hive *hdesc, int vofs, char *path)
{
  struct vk_key *vkkey;
  int vkofs;
  int len;

  vkofs = trav_path(hdesc, vofs,path,1);
  if (!vkofs) {
    return -1;
  }
  vkofs +=4;
  vkkey = (struct vk_key *)(hdesc->buffer + vkofs);

  len = vkkey->len_data & 0x7fffffff;
  if ( !len ) {  /* Special inline case, return size of 4 (dword) */
    len = 4;
  }
  return(len);
}

/* Get void-pointer to value-data, also if inline.
 * If val_type != 0 a check for correct value type is done
 * Caller must keep track of value's length (call function above to get it)
 */
void *get_val_data(struct hive *hdesc, int vofs, char *path, int val_type)
{
  struct vk_key *vkkey;
  int vkofs;

  vkofs = trav_path(hdesc,vofs,path,1);
  if (!vkofs) {
    return NULL;
  }
  vkofs +=4;
  vkkey = (struct vk_key *)(hdesc->buffer + vkofs);


  if (vkkey->len_data == 0) return NULL;
  if (!(vkkey->len_data & 0x7fffffff)) {  /* Special inline case (len = 0x80000000) */
    return(&vkkey->val_type); /* Data (4 bytes?) in type field */
  }    

  if (val_type && vkkey->val_type && (vkkey->val_type) != val_type) {
    printf("Value <%s> is not of correct type!\n",path);
    return NULL;
  }


  /* Negative len is inline, return ptr to offset-field which in
   * this case contains the data itself
   */
  if (vkkey->len_data & 0x80000000) return(&vkkey->ofs_data);
  /* Normal return, return data pointer */
  return(hdesc->buffer + vkkey->ofs_data + 0x1004);
}


/* Get and copy key data (if any) to buffer
 * if kv==NULL will allocate needed return struct & buffer
 * else will use buffer allocated for it (if it fits)
 * return len+data or NULL if not found (or other error)
 * NOTE: caller must deallocate buffer! a simple free(keyval) will suffice.
 */
struct keyval *get_val2buf(struct hive *hdesc, struct keyval *kv,
			    int vofs, char *path, int type )
{
  int l;
  struct keyval *kr;
  void *keydataptr;

  l = get_val_len(hdesc, vofs, path);
  if (l == -1) return(NULL);  /* error */
  if (kv && (kv->len < l)) return(NULL); /* Check for overflow of supplied buffer */

  keydataptr = get_val_data(hdesc, vofs, path, type);
  if (!keydataptr) return(NULL); /* error */

  /* Allocate space for data + header, or use supplied buffer */
  if (kv) {
    kr = kv;
  } else {
    ALLOC(kr,1,l+sizeof(int)+4);
  }

  kr->len = l;
  memcpy(&(kr->data), keydataptr, l);

  return(kr);
}

/* DWORDs are so common that I make a small function to get it easily */

int get_dword(struct hive *hdesc, int vofs, char *path)
{
  struct keyval *v;
  int dword;

  v = get_val2buf(hdesc, NULL, vofs, path, REG_DWORD);
  if (!v) return(-1); /* well... -1 COULD BE THE STORED VALUE TOO */

  dword = (int)v->data;

  FREE(v);

  return(dword);
  
}

/* Write to registry value. Only permits writes in-place, ie
 * excact writes to overwrite an existing value, since allocation
 * of new stuff not implemented yet
 * Pass inn buffer with data len as first DWORD (as routines above)
 * returns: 0 - error, len - OK (len of data)
 */

int put_buf2val(struct hive *hdesc, struct keyval *kv,
			    int vofs, char *path, int type )
{
  int l;
  void *keydataptr;

  l = get_val_len(hdesc, vofs, path);
  if (l == -1) return(0);  /* error */
  if (!kv || (kv->len != l)) return(0); /* Check for exact match of supplied buffer */

  keydataptr = get_val_data(hdesc, vofs, path, type);
  if (!keydataptr) return(0); /* error */

  memcpy(keydataptr, &kv->data, l);

  hdesc->state |= HMODE_DIRTY;

  return(l);
}

/* And, yer basic DWORD write */

int put_dword(struct hive *hdesc, int vofs, char *path, int dword)
{
  struct keyval *kr;
  int r;

  ALLOC(kr,1,sizeof(int)+sizeof(int));
  
  kr->len = sizeof(int);
  (int)kr->data = dword;

  r = put_buf2val(hdesc, kr, vofs, path, REG_DWORD);

  FREE(kr);

  return(r);
}


/* ================================================================ */

/* Hive control (load/save/close) etc */

void closeHive(struct hive *hdesc)
{

  printf("closing hive %s\n",hdesc->filename);
  FREE(hdesc->filename);
  FREE(hdesc->buffer);
  FREE(hdesc);

}

/* Write the hive back to disk (only if dirty & not readonly */
int writeHive(struct hive *hdesc)
{
  int len;

  if (hdesc->state & HMODE_RO) return(0);
  if ( !(hdesc->state & HMODE_DIRTY)) return(0);

  if ( !(hdesc->state & HMODE_OPEN)) { /* File has been closed */
    if (!(hdesc->filedesc = open(hdesc->filename,O_RDWR))) {
      fprintf(stderr,"writeHive: open(%s) failed: %s, FILE NOT WRITTEN!\n",hdesc->filename,strerror(errno));
      return(1);
    }
    hdesc->state |= HMODE_OPEN;
  }  
  /* Seek back to begginning of file (in case it's already open) */
  lseek(hdesc->filedesc, 0, SEEK_SET);

  len = write(hdesc->filedesc, hdesc->buffer, hdesc->size);
  if (len != hdesc->size) {
    fprintf(stderr,"writeHive: write of %s failed: %s.\n",hdesc->filename,strerror(errno));
    return(1);
  }

  hdesc->state &= (~HMODE_DIRTY);
  return(0);
}

struct hive *openHive(char *filename, int mode)
{

  struct hive *hdesc;
  int fmode,r,vofs;
  struct stat sbuf;
  unsigned long pofs;
  off_t l;
  char *c;
  struct hbin_page *p;
  struct regf_header *hdr;
  int verbose = (mode & HMODE_VERBOSE);

  CREATE(hdesc,struct hive,1);

  hdesc->filename = str_dup(filename);
  hdesc->state = 0;
  hdesc->size = 0;
  hdesc->buffer = NULL;

  if ( (mode & HMODE_RO) ) {
    fmode = O_RDONLY;
  } else {
    fmode = O_RDWR;
  }
  hdesc->filedesc = open(hdesc->filename,fmode);
  if (!(hdesc->filedesc)) {
    fprintf(stderr,"openHive(%s) failed: %s, trying read-only\n",hdesc->filename,strerror(errno));
    fmode = O_RDONLY;
    mode |= HMODE_RO;
    hdesc->filedesc = open(hdesc->filename,fmode);
    if (!(hdesc->filedesc)) {
      fprintf(stderr,"openHive(%s) in fallback RO-mode failed: %s\n",hdesc->filename,strerror(errno));
      closeHive(hdesc);
      return(NULL);
    }
  }

  if ( fstat(hdesc->filedesc,&sbuf) ) {
    perror("stat()");
    exit(1);
  }
  
  hdesc->size = sbuf.st_size;
  hdesc->state = mode | HMODE_OPEN;
  /*  fprintf(stderr,"hiveOpen(%s) successful\n",hdesc->filename); */
  
  /* Read the whole file */

  ALLOC(hdesc->buffer,1,hdesc->size);

  r = read(hdesc->filedesc,hdesc->buffer,hdesc->size);
  if (r < hdesc->size) {
    fprintf(stderr,"Could not read file, got %d bytes while expecting %d\n",
	    r, hdesc->size);
    closeHive(hdesc);
    return(NULL);
  }

  /* Now run through file, tallying all pages */
  /* NOTE/KLUDGE: Assume first page starts at offset 0x1000 */

   pofs = 0x1000;

   hdr = (struct regf_header *)hdesc->buffer;
   if (hdr->id != 0x66676572) {
     printf("openHive(%s): File does not seem to be a registry hive!\n",filename);
     return(hdesc);
   }
   printf("Hive's name (from header): <");
   for (c = hdr->name; *c && (c < hdr->name + 64); c += 2) putchar(*c);

   hdesc->rootofs = hdr->ofs_rootkey + 0x1000;
   /*   printf(">\nROOT KEY at offset: 0x%06x\n",hdesc->rootofs); */

   while (pofs < hdesc->size) {
     if (verbose) hexdump(hdesc->buffer,pofs,pofs+0x20,1);
     p = (struct hbin_page *)(hdesc->buffer + pofs);
     hdesc->pages++;
     if (verbose) printf("\n###### Page at 0x%0x has size 0x%0x, next at 0x%0x ######\n",pofs,p->len_page,p->ofs_next);

     if (p->len_page == 0 || p->ofs_next == 0) {
       if (verbose) printf("openhive debug: bailing out.. pagesize zero!\n");
       break;
     }
     vofs = pofs + 0x20; /* Skip page header */

     while (vofs-pofs < p->ofs_next) {
       vofs += parse_block(hdesc,vofs,verbose);

     }
     pofs += p->ofs_next;
   }
   printf(">\nFile size %d [%x] bytes, containing %d pages (+ 1 headerpage)\n",hdesc->size,hdesc->size, hdesc->pages);
   printf("Used for data: %d/%d blocks/bytes, unused: %d/%d blocks/bytes.\n",
	  hdesc->useblk,hdesc->usetot,hdesc->unuseblk,hdesc->unusetot);
  

  return(hdesc);

}

#if 0

int main(void)
{
  int d = 0;
  int val = 0;
  struct hive *hd;
  int nk;
  char p[ABSPATHLEN];

  hd = openHive("system.wrks",HMODE_RO);

  printf("Loaded..\n");
  /*
  d = debugit(hd->buffer,hd->size);

  printf("Buffer was %s\n", d ? "dirty" : "clean");
  */


  val = get_dword(hd, 0, "\\Select\\Current");
  printf("old value: %d [%x]\n",val,val);
  val = 0xabcd1234;
  printf("Changig value to %d, returned err=%d\n",val,
	 put_dword(hd,0,"\\Select\\Cgurrent",val));
  val = get_dword(hd, 0, "\\Select\\Current");
  printf("new value: %u [%x]\n",val,val);

  nk_ls(hd, "\\ControlSet001\\Services\\Xga",0,0);

  printf("Gurba\n");
  nk = trav_path(hd, 0, "\\ControlSet001\\Services\\Xga",0);
  p[0] = 0;
  printf("get_abs_path returns: %d\n",get_abs_path(hd, nk+4, p, 50));
  printf("And path = %s\n",p);

  closeHive(hd);

  return(0);
}

#endif