/*

   snowdrop - text watermarking and watermark recovery
   ---------------------------------------------------
 
   Copyright (C) 2002 by Michal Zalewski <lcamtuf@coredump.cx>
 
   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; either version 2 of the License, or
   (at your option) any later version.

   As a special exception, this program may be linked with the
   OpenSSL library, despite that library's more restrictive license.

   C code language backend. 

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>

#include "language.h"

static char* orig_data;
static char* water_data;
static int orig_off;
static int water_off;

static char lfcmt,	// We're inside a comment that runs to \n
            rgcmt,	// We're inside a block comment
	    instr,	// We're inside a string
	    isaname,	// Last read thing is a name
	    lastterm,	// Last possible terminator?
	    ismacro,	// Is a macro?
            prevwasslash; // Got slashed?

static char subst,didchange;

struct changeT {
  char* from,*to;
  char implied;
};

#define MAXVAR 10240

struct changeT change[MAXVAR+1];
static int chgtop=0;
static int just_testing;

#define MOD_NONE		0
#define MOD_CHGNAME		1	// Change variable name
#define MOD_PSPACE		2	// Change the amount of spaces


void  set_original(const char* buf) {
  if (!buf) fatal("set_original(NULL)");
  orig_data=(char*)buf;
  orig_off=0;
  water_off=0;
  instr=0;
  chgtop=0;
  isaname=0;
  didchange=0;
  lfcmt=0;
  ismacro=0;
  prevwasslash=0;
  rgcmt=0;
  lastterm=0;
  subst=0;
}


void  set_watermarked(const char* buf) {
  if (!buf) fatal("set_watermarked(NULL)");
  if (!orig_data) fatal("set_watermarked before set_original");
  water_data=(char*) buf;
  water_off=0;
}


char orig_buf[MAXBUF+1];


// Cos z _ w srodku jest jednym atomem.
char* get_orig_atom(void) {
  int i,nspaces=0;
  char* now=orig_data+orig_off;
  char* misio;
  char tmp[2]={0,0};
  int gotret=0;

    if (!orig_data) fatal("get_orig_atom before set_original");

  orig_buf[0]=0;

  while (isspace(*now)) {
    if (*now==' ') nspaces++; else
    if (*now=='\t') nspaces+=8; else
    if (*now=='\n') { lfcmt=0; gotret++; }
    now++;
  }

  if (!*now) return 0;

  while (gotret--) strcat(orig_buf,"\n");
  for (i=0;i<nspaces;i++) strcat(orig_buf," ");

  misio=orig_buf+strlen(orig_buf);

  // Get an entity - essentially anything that isn't a space or tab.
  tmp[0]=*now;
  strcat(orig_buf,tmp);
  now++;

  if (tmp[0]=='#') tmp[0]='A';

  if (isalnum(tmp[0]) || tmp[0]=='_') {
    while (isalnum(*now) || *now=='_') { tmp[0]=*now; strcat(orig_buf,tmp); now++; }
  } else if (tmp[0]=='\\' && !ismacro) {
    if (*now=='\'' || *now=='"' || *now=='\\') { tmp[0]=*now; strcat(orig_buf,tmp); now++; }
  } else if (tmp[0]=='/') {
    if (*now=='/') { tmp[0]=*now; strcat(orig_buf,tmp); now++; }
    if (*now=='*') { tmp[0]=*now; strcat(orig_buf,tmp); now++; }
  } else if (tmp[0]=='*') {
    if (*now=='/') { tmp[0]=*now; strcat(orig_buf,tmp); now++; }
  }

  if (!strcmp(misio,"/*") && !instr && !lfcmt) rgcmt=1; else
  if (!strcmp(misio,"*/") && !instr && !lfcmt) rgcmt=0; else
  if (!strcmp(misio,"'") && !instr && !lfcmt && !rgcmt) instr='\''; else
  if (!strcmp(misio,"\"") && !instr && !lfcmt && !rgcmt) instr='"'; else
  if (instr && !lfcmt && !rgcmt && *misio==instr) instr=0; else
  if (!strcmp(misio,"//") && !instr && !rgcmt) lfcmt=1;

  isaname=0; subst=0;
  didchange=0;

  // TODO: Also handle #define's somehow.
  if (!(lfcmt  || rgcmt || instr)) {

    if (ismacro) {
      if (*misio=='\\') prevwasslash=1;
      if (strchr(orig_buf,'\n')) {
        if (!prevwasslash) {
          ismacro=0;
          lastterm=0;
        }
        prevwasslash=0;
      }
    }

    if (isalpha(*misio) || *misio=='_' || *misio=='*') {
      if (strcmp(misio,"return") && strcmp(misio,"sizeof") &&
          strcmp(misio,"static") && strcmp(misio,"signed") &&
          strcmp(misio,"signed") &&
          strcmp(misio,"const") &&
          strcmp(misio,"unsigned") && strcmp(misio,"short") &&
          strcmp(misio,"long") && strcmp(misio,"register") &&
          strcmp(misio,"inline") && strcmp(misio,"*") && 
          strcmp(misio,"struct") && strcmp(misio,"typeof")) lastterm++;
      if (!strcmp(misio,"extern")) lastterm=30; // Do not touch.
      if (!strcmp(misio,"goto")) lastterm=50; // Do not touch.
      if (!strcmp(misio,"else")) lastterm=20; // Do not touch.
      if (!strcmp(misio,"case")) lastterm=20; // Do not touch.
      if (!strcmp(misio,"main")) lastterm=20; // Do not touch.
      if (lastterm==2) isaname=1;
    } else if (isdigit(*misio) || *misio==';' || *misio==',' ||
               *misio=='}' || *misio=='{' || *misio=='(' || *misio=='[') lastterm=0;
      else lastterm=40;

    if (strchr(orig_buf,'#')) ismacro=1;

  }

  //debug("%s:",orig_buf);
  //debug("Flags: instr %d lfcmt %d rgcmt %d lastterm %d isaname %d [%s].\n",instr,lfcmt,rgcmt,lastterm,isaname,misio);
 
  // Substitute names. If substituted, reset isaname.
  if (!(instr || rgcmt || lfcmt))
  for (i=0;i<chgtop;i++) {
    if (!strcmp(change[i].from,misio)) {
      // debug("Applied a conversion [%s] -> [%s] (subst %d)\n",misio,change[i].to,change[i].implied);
      if (!change[i].implied) strcpy(misio,change[i].to); else subst=1;
      didchange=1;
      isaname=0;
      break;
    }
  }

  // debug("%d/%d ",isaname,subst);

  orig_off=now-orig_data;
  return orig_buf;

}


char water_buf[MAXBUF+1];

char* get_water_atom(void) {
  int i,nspaces=0;
  char* now=water_data+water_off;
  char* misio;
  char tmp[2]={0,0};
  int gotret=0;
  if (!water_data) fatal("get_water_atom before set_watermarked");

  water_buf[0]=0;

  while (isspace(*now)) {
    if (*now==' ') nspaces++; else
    if (*now=='\t') nspaces+=8; else
    if (*now=='\n') gotret++;
    now++;
  }

  if (!*now) return 0;

  while (gotret--) strcat(water_buf,"\n");
  for (i=0;i<nspaces;i++) strcat(water_buf," ");

  misio=water_buf+strlen(water_buf);

  // Get an entity - essentially anything that isn't a space or tab.
  tmp[0]=*now;
  strcat(water_buf,tmp);
  now++;

  if (tmp[0]=='#') tmp[0]='A';

  if (isalnum(tmp[0]) || tmp[0]=='_') {
    while (isalnum(*now) || *now=='_') { tmp[0]=*now; strcat(water_buf,tmp); now++; }
  } else if (tmp[0]=='\\' && instr) {
    if (*now=='\'' || *now=='"') { tmp[0]=*now; strcat(water_buf,tmp); now++; }
  } else if (tmp[0]=='/') {
    if (*now=='/') { tmp[0]=*now; strcat(water_buf,tmp); now++; }
    if (*now=='*') { tmp[0]=*now; strcat(water_buf,tmp); now++; }
  } else if (tmp[0]=='*') {
    if (*now=='/') { tmp[0]=*now; strcat(water_buf,tmp); now++; }
  }

  water_off=now-water_data;
  return water_buf;
}


static int mod_type;

#define CHECK_STOR(siz,id) if ((siz) >= top_storage) { \
                             mod_type=(id); top_storage=(siz); }

int   get_storage(const char* orig, const int domain) {
  const char* foo=orig;
  int top_storage=0;
  switch (domain) {
    case DOMAIN_WHITE:
      while (isspace(*foo)) foo++;
      if (!strchr(orig,'\n')) 
      if (!(instr || rgcmt || lfcmt || ismacro)) {
        switch (*foo) {
          case '*':	case ';':
          case '!':	case '/':	case '^':
          case '%':	case '[':
          case ']':	case '(':	case ')':	case '}':
          case '{':	case ':':
          case '?': CHECK_STOR(1,MOD_PSPACE); break;
        }
      }
      break;
       
/*

- indentation
  3 bits

  (regular handling)

*/
    case DOMAIN_SYNONYMS:
      if (isaname) { CHECK_STOR(5*2,MOD_CHGNAME); }
      break;
  }
  return top_storage;
}


static char setv[MAXBUF+1];


static char namechars[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
extern int synced;

char* set_value(const char* orig,int value, const int domain) {
  int i;
  char sm[2]={0,0};
  int unic=0;
  char *bt=(char*)orig,*foo=setv;
  int bc=0;
  while (isspace(*bt)) { bt++; bc++; }
  if (!(i=get_storage(orig,domain))) fatal("set_value on a fixed atom");
  // debug("Asked to set %s to %d in domain %d (storage was %d)\n",orig,value,domain,i);
  setv[0]=0;
  switch (mod_type) {

    case MOD_PSPACE:
      for (i=0;i<value;i++) strcat(setv," ");
      strcat(setv,bt);
      return setv;
      break;    

    case MOD_CHGNAME:
      // Copy spaces
      for (i=0;i<bc;i++) { sm[0]=orig[i]; strcat(setv,sm); foo++; }
      // Substitute with two-letter thing
      if (!synced) { strcpy(foo,".implied-value-bug."); goto dontwantit; }
      *(foo)=namechars[value & 31];
      *(foo+1)=namechars[(value>>5) & 31];
      // debug("Asked to store %d (%d:%d) instead of %s.\n",value,value&31,(value >>5)&31,orig);
      *(foo+2)=0;
      // If it is not unique, try appending a number or char until it is.
recheck:
        for (i=0;i<chgtop;i++) 
          if (!strcmp(change[i].to,foo)) {
            *(foo+2)=namechars[unic];
            *(foo+3)=0;
            unic++;
            if (unic >= strlen(namechars)) fatal("too many variable names?");
            goto recheck;
          }

          if (!strcmp(foo,"int") || !strcmp(foo,"for") || !strcmp(foo,"do")
              || !strcmp(foo,"if")) {
            if (!(*(foo+2))) *(foo+2)='p'; else (*(foo+2))++;
            *(foo+3)=0;
          }

dontwantit:

      if (!just_testing) {

        for (i=0;i<chgtop;i++) 
          if (!strcmp(bt,change[i].from) && !change[i].implied) { goto foome2; }
      
        // Add the thing to conv tables.
        change[chgtop].from=strdup(bt);
        change[chgtop].to=strdup(foo);
        change[chgtop].implied=!synced;
        // debug("Added a new conversion at %d: [%s] -> [%s] (imp %d syn %d)\n",chgtop,bt,foo,!synced,synced);
        chgtop++;
      }
foome2:
      return setv;
      break;
  }
  fatal("set value with bogus MOD_*");
  return 0;
}


int strspcmp(const char* a,const char* b) {
  while (isspace(*a)) a++;
  while (isspace(*b)) b++;
  if (!*a || !*b) return 31337; // Bleh.
  return strcmp(a,b);
}


static int warned;
static char setval_copy[MAXBUF+1];

int get_value(const char* orig,const char* water,int* scr,int* va,char test) {
  int i3,i2,i1,i0;
  int sc[4];
  int cnt=0;
  int retme=0;
  const char* shortwater=water;
  // char footest[1024]={0};

  while (isspace(*shortwater)) shortwater++;

  //debug("in get_value (%s %s %d)\n",orig,water,test);

  if (!orig) fatal("get_value with orig==NULL");
  if (!water) fatal("get_value with water==NULL");
  if (!scr) fatal("get_value with sc==NULL");
  if (!va) fatal("get_value with va==NULL");

redome:

  // Enter dummy mode, save buffer.
  just_testing=1;

  sc[3]=get_storage(orig,3);
  sc[2]=get_storage(orig,2);
  sc[1]=get_storage(orig,1);
  sc[0]=get_storage(orig,0);

  for (i3=0;i3<(1<<sc[3]);i3++) {
    char b3[MAXBUF+1];    
    
    if (!sc[3]) strcpy(b3,orig); else
    {
      strcpy(b3,set_value(orig,i3,3));
    } 
    if (didchange || isaname)
    {
      char* b3short=b3;
      while (isspace(*b3short)) b3short++;
      // As a matter of fact, only first two bits of text are of any relevance.
      if (strlen(shortwater)==2) {
        // If we have a two letter water, and set_value returned three
        // letters, truncate.
        if (strlen(b3short)==3) b3short[2]=0;
      } else if (strlen(shortwater)==3) {
        // If we have three letter water, and set_value returned two letters,
        // append last letter of the water to the result. Otherwise, just
        // overwrite the last letter.
        if (strlen(b3short)==2 || strlen(b3short)==3) {
          b3short[2]=shortwater[strlen(shortwater)-1];
          b3short[3]=0;
        }
      }
      //      if (b3short[0]=='q' && b3short[1]=='g')
      //     sprintf(footest,"shortwater %s b3 %s orig %s cap %d did %d is %d\n",shortwater,b3,orig,sc[3],didchange,isaname);

    }

    for (i2=0;i2<(1<<sc[2]);i2++) {
      char b2[MAXBUF+1];    
      if (!sc[2]) strcpy(b2,b3); else strcpy(b2,set_value(b3,i2,2));

      for (i1=0;i1<(1<<sc[1]);i1++) {
        char b1[MAXBUF+1];    
        if (!sc[1]) strcpy(b1,b2); else strcpy(b1,set_value(b2,i1,1));

        for (i0=0;i0<(1<<sc[0]);i0++) {
          char b0[MAXBUF+1];    
          if (!sc[0]) strcpy(b0,b1); else strcpy(b0,set_value(b1,i0,0));

          if ((cnt==0 && !strcmp(water,b0)) ||
              (cnt==1 && !strspcmp(water,b1)) ||
              (cnt==2 && !strspcmp(water,b2)) ||
              (cnt==3 && !strspcmp(water,b3))) {

            just_testing=0;

            if (!test) {
              strcpy(setval_copy,setv);
              // Commit.
              if (sc[3]) set_value(orig,i3,3);
              if (sc[2]) set_value(orig,i2,2);
              if (sc[1]) set_value(orig,i1,1);
              if (sc[0]) set_value(orig,i0,0);
              strcpy(setv,setval_copy);
            }
            // Restore buffer...
            // Write capacities and values back...
            *(scr++)=sc[0]; *(va++)=i0;
            *(scr++)=sc[1]; *(va++)=i1;
            *(scr++)=sc[2]; *(va++)=i2;
            *(scr++)=sc[3]; *(va++)=i3;
            if (cnt && !warned) {
              // debug("[!] Data missing in certain channels (modified file?) [%d].\n",cnt);
              warned=1;
            }
            // debug("leaving get_value way 1\n");
            return 1;
	  }
        }
      }
    }
  }

  cnt++;
  if (cnt<=3) goto redome;

  strcpy(setval_copy,setv);

  { 
    char wbuf[MAXBUF+1];
    strcpy(wbuf,orig);

    if (!test && !sc[3]) {

      while (isspace(*orig)) orig++;
      while (isspace(*water)) water++;

      // debug("We got no match for [%s] [%s]\n",orig,water);
        
      if (synced && subst && ( water[2]==0 || water[3]==0 )) {
        int i;
        retme=1;
        // debug("It appears that for symbol '%s', the value '%s' is most suitable.\n",orig,water);
        for (i=0;i<chgtop;i++)
          if (change[i].implied && !strcmp(change[i].from,orig)) break;
        change[i].from=strdup(orig);
        change[i].implied=0;
        change[i].to=strdup(water);
        if (i==chgtop) chgtop++;
        goto skipthree;
      }
    }
    if (sc[3] && !test) {
      just_testing=0;
      strcpy(wbuf,set_value(wbuf,0,3));
      just_testing=1;
    }

skipthree:

    if (sc[2] && !test) {
      just_testing=0;
      strcpy(wbuf,set_value(wbuf,0,2));
      just_testing=1;
    }

    if (sc[1] && !test) {
      just_testing=0;
      strcpy(wbuf,set_value(wbuf,0,1));
      just_testing=1;
    }

    if (sc[0] && !test) {
      just_testing=0;
      strcpy(wbuf,set_value(wbuf,0,0));
      just_testing=1;
    }
  }

  strcpy(setv,setval_copy);

  just_testing=0;

  *(scr++)=sc[0];
  *(scr++)=sc[1];
  *(scr++)=sc[2];
  *(scr++)=sc[3];

  // debug("leaving get_value way 2 <did %d is %d>\n",didchange,isaname);
  // debug(">> %s << \n",footest);
  return retme;
}



int   get_water_pos(void) {
  return water_off;
}


void  set_water_pos(int x) {
  water_off=x;
}


char* get_langdesc(void) {
  return "standalone C code (**** BROKEN BETA ****)";
}

// There is no configuration.
unsigned int md5_importantstuff(void) { return 0xf00; }

// There is no help.
void  module_help(void) { return; }

// There is no configuration MD5 mismatch.
void  md5_wrong(void) { return; }


