/*
 * stegosauro.c: Steganographic tool to store hidden information in wave file
 *
 *
 * Copyright (c) 2003, eazy <eazy@ondaquadra.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``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 COPYRIGHT HOLDERS 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <openssl/evp.h>

#define HIDE 1
#define EXTRACT 2
#define SIZE 255
#define COMPRESS "compress"
#define UNCOMPRESS "uncompress"

typedef struct {
	unsigned long   chunkID;
	unsigned long   chunkSize;
	unsigned short  wFormatTag;
	unsigned short  wChannels;
	unsigned long   dwSamplesPerSec;
	unsigned long   dwAvgBytesPerSec;
	unsigned short  wBlockAlign;
	unsigned short  wBitsPerSample;
}               FormatChunk;

typedef struct {
	unsigned long   chunkID;
	unsigned long   chunkSize;
	unsigned char  *waveformData;
}               DataChunk;

typedef struct {
	unsigned long   chunkID;
	unsigned long   chunkSize;
	unsigned long   waveID;
	FormatChunk     format;
	DataChunk       data;
}               RiffChunk;

int             BytesPerSample;
FILE           *fpinput, *fpoutput, *fphide, *fptmp;
RiffChunk       wave;

void
err_quit(void)
{
	perror("error");
	exit(-1);
}

void
usage(char *prog)
{
	fprintf(stderr, "Usage:\n"
		"\t%s -h -p password -i wavein -o waveout -f file_to_hide\n"
		"\t%s -e -p password -i wavein -f file_to_extract\n"
		"\t-h\thide a file in a wave file\n"
		"\t-e\textract a file from a wave file\n"
		"\t-p\tpassword to extract the hidden file\n"
		"\t-i\tspecify input file\n"
		"\t-o\tspecify output file\n"
		"\t-f\tspecify file to hide or where to extract another file\n", prog, prog);
	exit(-1);
}

unsigned long 
hash(unsigned char *data, int len)
{
	EVP_MD_CTX      context;
	unsigned char   digest[16];

	if (!data || !len)
		return 0;

	EVP_DigestInit(&context, EVP_md5());
	EVP_DigestUpdate(&context, data, len);
	EVP_DigestFinal(&context, digest, &len);

	if (len == sizeof(digest))
		return *(unsigned long *) digest;
	else
		return 0;
}

int 
log_base2(unsigned long num)
{
	int             i = 0;

	while (num > 1) {
		num >>= 1;
		i++;
	}
	return i;
}

unsigned long 
luby_rackoff(char *key, unsigned long index, unsigned long max)
{
	int             nbitR, nbitL;
	unsigned long   number, K, Kleft, Kright, R, L, arg;

	if (key == NULL)
		exit(-1);

	nbitL = log_base2(max) >> 1;
	nbitR = log_base2(max) - nbitL;

	R = index & (0xffffffff >> (32 - nbitR));
	L = (index >> nbitR) & (0xffffffff >> (32 - nbitL));

	K = hash(key, strlen(key));

	Kright = K & (0xffffffff >> (32 - nbitR));
	Kleft = (K >> nbitR) & (0xffffffff >> (32 - nbitL));

	arg = (Kleft << nbitL) | L;
	R = R ^ (hash((char *) &arg, 4) >> (32 - nbitR));

	arg = (Kright << nbitR) | R;
	L = L ^ (hash((char *) &arg, 4) >> (32 - nbitL));

	arg = (Kleft << nbitL) | L;
	R = R ^ (hash((char *) &arg, 4) >> (32 - nbitR));

	number = (L << nbitR) | R;
	return number;
}

void 
parse_header()
{
	/*
          Legge l'intestazione del file wave

	   offset    0  1  2  3   4  5  6  7   8  9  a  b   c  d  e  f  0123456789abcdef
	  00000000 <52>49 46 46  c6 2d 00 00  57 41 56 45  66 6d 74 20  RIFFÆ-..WAVEfmt
	  00000010  10 00 00 00  01 00 01 00  44 ac 00 00  88 58 01 00  ........D¬...X..
	  00000020  02 00 10 00  64 61 74 61  a2 2d 00 00  ab ff 76 ff  ....data¢-..«ÿvÿ
         */
	fseek(fpinput, 0, SEEK_SET);
	if (fread(&wave, 44, 1, fpinput) != 1) {
		if (feof(fpinput)) {
			fprintf(stderr, "EOF\n");
			exit(-1);
		} else
			err_quit();
	}
	if (memcmp(&wave.chunkID, "RIFF", 4) != 0) {
		fprintf(stderr, "No RIFF file\n");
		exit(-1);
	}
	if (memcmp(&wave.waveID, "WAVE", 4) != 0) {
		fprintf(stderr, "No WAVE file\n");
		exit(-1);
	}
	/*
          Per semplicità assume che il format chunk segua immediatamente
	  il RIFF chunk, tuttavia questo non è richiesto dallo standard
         */
	if (memcmp(&wave.format.chunkID, "fmt ", 4) != 0) {
		fprintf(stderr, "No format chunk\n");
		exit(-1);
	}
	/*
	  Viene supportato solamente il formato PCM, ovvero senza
	  compressione
	 */
	if (wave.format.wFormatTag != 1) {
		fprintf(stderr, "Format not supported\n");
		exit(-1);
	}
	/*
          Per semplicità si assume che il data chunk segua immediatamente
	  il format chunk, tuttavia questo non è richiesto dallo standard
         */
	if (memcmp(&wave.data.chunkID, "data", 4) != 0) {
		fprintf(stderr, "No data chunk\n");
		exit(-1);
	}
}

/*
  Legge un sample del data chunk dal file wave contenitore e provvede
  a steganografare nel bit meno significativo del sample un bit di bitmask.
  Il sample viene scelto sulla base del valore della variabile random
  ottenuta mediante l'algoritmo Luby-Rackoff che provvede a permutare
  l'ordine con cui i bit da steganografare vengono memorizzati
 */
void
inject(unsigned char bitmask, int random)
{
	unsigned char     sample;

	/*
	  Eseguo un AND logico tra il valore del byte meno significativo del
	  sample e la mask 0xfe al fine di azzerarne il bit meno significativo.
	  Il byte meno significativo del sample risulta essere il primo perchè
	  memorizzato nella forma little-endian.
	  Successivamente eseguo un OR logico tra il valore ottenuto e il bit
	  da memorizzare. Scrivo il byte così ottenuto sul wave file di output

	  (sample AND 11111110) OR 0000000<bit>
	  dove <bit> è il bit da memorizzare.

	  Esempio:

	  sample	= 11011101
	  bitmask	= 10001010
	  bitmask >> 7	= 00000001
	  0xfe		= 11111110


		11011101 AND
		11111110 =
		________
		11011100 OR
		00000001 =
		________
		11011101

	 */
	fseek(fpinput, 44 + BytesPerSample * random, SEEK_SET);
	fseek(fpoutput, 44 + BytesPerSample * random, SEEK_SET);

	if (fread(&sample, 1, 1, fpinput) != 1)
		err_quit();

	sample = (sample & 0xfe) | bitmask >> 7;
	fwrite(&sample, 1, 1, fpoutput);

}

/*
  Estrae un bit steganografato dal sample letto dal file wave che
  funge da contenitore
 */
unsigned char
extract(int random)
{
	unsigned char   sample;

	fseek(fpinput, 44 + BytesPerSample * random, SEEK_SET);

	if (fread(&sample, 1, 1, fpinput) != 1)
		err_quit();

	return sample & 0x01;
}

void 
copy_file()
{
	int             n;
	char            buf[SIZE];

	fseek(fpinput, 0, SEEK_SET);
	fseek(fpoutput, 0, SEEK_SET);

	while ((n = fread(buf, 1, SIZE, fpinput)) == SIZE)
		fwrite(buf, 1, SIZE, fpoutput);
	if (ferror(fpinput))
		err_quit();
	fwrite(buf, 1, n, fpoutput);
}

void
wave_hide(char *passwd)
{
	pid_t           pid;
	int             i, nsample, nbit;
	unsigned long   index = 0, output_len, hide_len, random;
	unsigned char   bitmask;

	/*
	  Copia il file wave in input nel file specificato come output
	  che diverrà il contenitore delle informazioni steganografate
	 */
	copy_file();

	/*
	  Calcola il numero di sample contenuti nella traccia audio
	 */
	nsample = wave.data.chunkSize / BytesPerSample;

	/*
	  Calcola il numero di bit necessari per indirizzare nsample o
	  la potenza di due inferiore
	 */
	nbit = log_base2(nsample);

	/*
	  Calcola il numero di sample che è possibile sfruttare come contenitore
	  e che risultano indirizzabili con nbit
	 */
	output_len = 1 << nbit;

	if( (pid = fork()) == -1)
		err_quit();

	else if(pid == 0){	/* child */

		if(dup2(fileno(fphide), STDIN_FILENO) == -1)
			err_quit();
		if(dup2(fileno(fptmp), STDOUT_FILENO) == -1)
			err_quit();

		execlp(COMPRESS, COMPRESS, (char *) 0);
		err_quit();
	}

	/* parent */
	waitpid(pid, NULL, 0);
	fclose(fphide);
	fphide = fptmp;

	/*
	  Calcola la lunghezza del file da steganografare
	 */
	fseek(fphide, 0, SEEK_END);
	hide_len = ftell(fphide);

	printf("len: %d\n", hide_len);

	/*
          Controlla che il numero di bit da steganografare non superi il numero
	  di sample utilizzabili come contenitore
	 */
	if (hide_len * 8 + 32 > output_len) {
		fprintf(stderr, "Il file wave non è abbastanza grande da contenere i dati\n");
		exit(-1);
	}

	fseek(fphide, 0, SEEK_SET);

	/*
	  Scrive la lunghezza del file steganografato in modo tale che possa
	  essere ricuperato correttamente in fase di estrazione
	 */
	for(i = 0; i < 32; i++){
		random = luby_rackoff(passwd, index, output_len);
		inject((unsigned char)htonl(hide_len), random);
		hide_len <<= 1;
		index++;
	}

	/*
	  Legge un byte del file da steganografare
	 */
	while (fread(&bitmask, 1, 1, fphide) == 1) {

		/*
		  Scrive il byte del file da steganografare all'interno del
		  file wave contenitore. La scrittura viene eseguita un bit per
		  volta
		 */
		for (i = 0; i < 8; i++) {
			random = luby_rackoff(passwd, index, output_len);
			inject(bitmask, random);
			bitmask <<= 1;
			index++;
		}
	}
	if (ferror(fphide))
		err_quit();
}

void
wave_extract(char *passwd)
{
	pid_t pid;
	int i, nsample, nbit;
	unsigned char bitmask;
	unsigned long index = 0, output_len, hide_len, random;

	/*
	  Calcola il numero di sample contenuti nella traccia audio
	 */
	nsample = wave.data.chunkSize / BytesPerSample;
	
	/*
	  Calcola il numero di bit necessari per indirizzare nsample o
	  la potenza di due inferiore
	 */
	nbit = log_base2(nsample);
	
	/*
	  Calcola il numero di sample che è possibile sfruttare come contenitore
	  e che risultano indirizzabili con nbit
	 */
	output_len = 1 << nbit;

	/*
	  Estrae dal wave file contenitore la lunghezza del file steganografato
	  al suo interno
	 */
	for(i = 0; i < 32; i++){
		random = luby_rackoff(passwd, index, output_len);
		hide_len <<= 1;
		hide_len |= (unsigned long) extract(random);
		index++;
	}

	printf("len: %d\n", hide_len);

	if(hide_len * 8 + 32 > output_len){
		fprintf(stderr, "Errore nel calcolo della lunghezza del file\n");
		exit(-1);
	}

	/*
	  Esegue il ciclo while per un numero pari al numero di bit che
	  compongono il file steganografato
	 */
	while(index < hide_len * 8 + 32){

		/*
		  Legge un byte del file steganografato dal file wave contenitore
		 */
		for(i = 0; i < 8; i++){
			random = luby_rackoff(passwd, index, output_len);
			bitmask <<= 1;
			bitmask |= extract(random);
			index++;
		}
		/*
		  Scrive il file letto nel file di output
		 */
		fputc(bitmask, fptmp);
	}

	fseek(fptmp, 0, SEEK_SET);

	if( (pid = fork()) == -1)
		err_quit();

	else if(pid == 0){	/* child */

		if(dup2(fileno(fptmp), STDIN_FILENO) == -1)
			err_quit();
		if(dup2(fileno(fphide), STDOUT_FILENO) == -1)
			err_quit();

		execlp(UNCOMPRESS, UNCOMPRESS, (char *) 0);
		err_quit();
	}

	/* parent */
	waitpid(pid, NULL, 0);
}

int
main(int argc, char **argv)
{
	int             opt, mode = 0;
	char           *passwd = NULL, *input = NULL, *output = NULL, *hide = NULL;

	while ((opt = getopt(argc, argv, "p:i:o:f:he")) != -1)
		switch (opt) {
		case 'p':
			passwd = optarg;
			break;
		case 'i':
			input = optarg;
			break;
		case 'o':
			output = optarg;
			break;
		case 'f':
			hide = optarg;
			break;
		case 'h':
			if (!mode) {
				mode = HIDE;
				break;
			} else
				opt = '?';
		case 'e':
			if (!mode) {
				mode = EXTRACT;
				break;
			} else
				opt = '?';
		case '?':
		default:
			usage(argv[0]);

		}

	if (optind < argc)
		usage(argv[0]);

	if (!mode)
		usage(argv[0]);

	switch (mode) {
	case HIDE:
		if (passwd && input && output && hide) {
			if ((fpinput = fopen(input, "r")) == NULL)
				err_quit();
			if ((fpoutput = fopen(output, "w")) == NULL)
				err_quit();
			if ((fphide = fopen(hide, "r")) == NULL)
				err_quit();
			if( (fptmp = tmpfile()) == NULL)
				err_quit();
		} else
			usage(argv[0]);
		break;

	case EXTRACT:
		if (passwd && input && hide) {
			if ((fpinput = fopen(input, "r")) == NULL)
				err_quit();
			if ((fphide = fopen(hide, "w")) == NULL)
				err_quit();
			if( (fptmp = tmpfile()) == NULL)
				err_quit();
		} else
			usage(argv[0]);
		break;
	}

	/*
	  Legge l'header del wave file e popala le varie strutture con
	  i dati in esso contenuti
	 */
	parse_header();

	BytesPerSample = wave.format.wBitsPerSample / 8;

	/*
	  Se wBitsPerSample non è multiplo di 8 calcola un byte in più
	  che conterrà i rimanenti bit paddati
	 */
	if (wave.format.wBitsPerSample % 8)
		BytesPerSample++;

	switch (mode) {
	case HIDE:
		wave_hide(passwd);
		break;
	case EXTRACT:
		wave_extract(passwd);
		break;
	}

	printf("Riff chunk id: %x\n"
	       "Riff chunk size: %x\n"
	       "Wave id: %x\n"
	       "Fmt chunk id: %x\n"
	       "Fmt chunk size: %x\n"
	       "Format tag: %x\n"
	       "Channels: %x\n"
	       "SamplesPerSec: %x\n"
	       "AvgBytesPerSec: %x\n"
	       "BlockAlign: %x\n"
	       "BitsPerSample: %x\n"
	       "Data chunk id: %x\n"
	       "Data chunk size: %x\n", wave.chunkID, wave.chunkSize, wave.waveID, wave.format.chunkID, wave.format.chunkSize,
	       wave.format.wFormatTag, wave.format.wChannels, wave.format.dwSamplesPerSec, wave.format.dwAvgBytesPerSec,
	       wave.format.wBlockAlign, wave.format.wBitsPerSample, wave.data.chunkID, wave.data.chunkSize);
}
