// $Id: util.cc,v 1.46 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include <sys/types.h>
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdarg.h>

#include "input.h"
#include "util.h"
#include "Obj.h"
#include "md5.h"

char* copy_string(const char* s)
	{
	char* c = new char[strlen(s)+1];
	strcpy(c, s);
	return c;
	}

int streq(const char* s1, const char* s2)
	{
	return ! strcmp(s1, s2);
	}

int expand_escape(const char*& s)
	{
	switch ( *(s++) ) {
	case 'b': return '\b';
	case 'f': return '\f';
	case 'n': return '\n';
	case 'r': return '\r';
	case 't': return '\t';
	case 'a': return '\a';
	case 'v': return '\v';

	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7':
		{ // \<octal>{1,3}
		--s;	// put back the first octal digit
		const char* start = s;

		// Don't increment inside loop control
		// because if isdigit() is a macro it might
		// expand into multiple increments ...

		// Here we define a maximum length for escape sequence
		// to allow easy handling of string like: "^H0" as
		// "\0100".

		for ( int len = 0; len < 3 && isascii(*s) && isdigit(*s);
		      ++s, ++len)
			;

		int result;
		if ( sscanf(start, "%3o", &result) != 1 )
			{
			warn("bad octal escape: ", start);
			result = 0;
			}

		return result;
		}

	case 'x':
		{ /* \x<hex> */
		const char* start = s;

		// Look at most 2 characters, so that "\x0ddir" -> "^Mdir".
		for ( int len = 0; len < 2 && isascii(*s) && isxdigit(*s);
		      ++s, ++len);

		int result;
		if ( sscanf(start, "%2x", &result) != 1 )
			{
			warn("bad hexadecimal escape: ", start);
			result = 0;
			}

		return result;
		}

	default:
		return s[-1];
	}
	}

char* skip_whitespace(char* s)
	{
	while ( *s == ' ' || *s == '\t' )
		++s;
	return s;
	}

const char* skip_whitespace(const char* s)
	{
	while ( *s == ' ' || *s == '\t' )
		++s;
	return s;
	}

char* skip_whitespace(char* s, char* end_of_s)
	{
	while ( s < end_of_s && (*s == ' ' || *s == '\t') )
		++s;
	return s;
	}

const char* skip_whitespace(const char* s, const char* end_of_s)
	{
	while ( s < end_of_s && (*s == ' ' || *s == '\t') )
		++s;
	return s;
	}

char* skip_digits(char* s)
	{
	while ( *s && isdigit(*s) )
		++s;
	return s;
	}

char* get_word(char*& s)
	{
	char* w = s;
	while ( *s && ! isspace(*s) )
		++s;

	if ( *s )
		{
		*s = '\0';	// terminate the word
		s = skip_whitespace(s+1);
		}

	return w;
	}

void get_word(int length, const char* s, int& pwlen, const char*& pw)
	{
	pw = s;

	int len = 0;
	while ( len < length && *s && ! isspace(*s) )
		{
		++s;
		++len;
		}

	pwlen = len;
	}

void to_upper(char* s)
	{
	while ( *s )
		{
		if ( islower(*s) )
			*s = toupper(*s);
		++s;
		}
	}

const char* strchr_n(const char* s, const char* end_of_s, char ch)
	{
	for ( ; s < end_of_s; ++s )
		if ( *s == ch )
			return s;

	return 0;
	}

const char* strrchr_n(const char* s, const char* end_of_s, char ch)
	{
	for ( --end_of_s; end_of_s >= s; --end_of_s )
		if ( *end_of_s == ch )
			return end_of_s;

	return 0;
	}

int decode_hex(char ch)
	{
	if ( ch >= '0' && ch <= '9' )
		return ch - '0';

	if ( ch >= 'A' && ch <= 'F' )
		return ch - 'A' + 10;

	if ( ch >= 'a' && ch <= 'f' )
		return ch - 'a' + 10;

	return -1;
	}

unsigned char encode_hex(int h)
	{
	static const char hex[16] = {
		'0', '1', '2', '3', '4', '5', '6', '7', '8',
		'9', 'A', 'B', 'C', 'D', 'E', 'F'
	};

	if  ( h < 0 || h >= 16 )
		{
		internal_error("illegal value for encode_hex: %d", h);
		return 'X';
		}

	return hex[h];
	}

// Same as strpbrk except that s is not NUL-terminated, but limited by
// len. Note that '\0' is always implicitly contained in charset.
const char* strpbrk_n(size_t len, const char* s, const char* charset)
	{
	for ( const char* p = s; p < s + len; ++p )
		if ( strchr(charset, *p) )
			return p;

	return 0;
	}

int strcasecmp_n(int b_len, const char* b, const char* t)
	{
	if ( ! b )
		return -1;

	int i;
	for ( i = 0; i < b_len; ++i )
		{
		char c1 = islower(b[i]) ? toupper(b[i]) : b[i];
		char c2 = islower(t[i]) ? toupper(t[i]) : t[i];

		if ( c1 < c2 )
			return -1;

		if ( c1 > c2 )
			return 1;
		}

	return t[i] != '\0';
	}

int atoi_n(int len, const char* s, const char** end, int base, int& result)
	{
	int n = 0;
	int neg = 0;

	if ( len > 0 && *s == '-' )
		{
		neg = 1;
		--len; ++s;
		}

	int i;
	for ( i = 0; i < len; ++i )
		{
		unsigned int d;

		if ( isdigit(s[i]) )
			d = s[i] - '0';

		else if ( s[i] >= 'a' && s[i] < 'a' - 10 + base )
			d = s[i] - 'a' + 10;

		else if ( s[i] >= 'A' && s[i] < 'A' - 10 + base )
			d = s[i] - 'A' + 10;

		else if ( i > 0 )
			break;

		else
			return 0;

		n = n * base + d;
		}

	if ( neg )
		result = -n;
	else
		result = n;

	if ( end )
		*end = s + i;

	return 1;
	}

int fputs(int len, const char* s, FILE* fp)
	{
	for ( int i = 0; i < len; ++i )
		if ( fputc(s[i], fp) == EOF )
			return EOF;
	return 0;
	}

const char* fmt(const char* format, ...)
	{
	static char buf[1024];

	va_list al;
	va_start(al, format);
	vsnprintf(buf, sizeof(buf), format, al);
	va_end(al);

	return buf;
	}

void fmt(int& len, const char*& b, const char* format, ...)
	{
	static char buf[1024];

	va_list al;
	va_start(al, format);

	len = vsnprintf(buf, sizeof(buf), format, al);
	va_end(al);
	if ( len >= (int) sizeof(buf) )
		len = sizeof(buf) - 1;

	b = buf;
	}

void hash_md5(size_t size, const unsigned char* bytes, unsigned char digest[16])
	{
	md5_state_s h;
	md5_init(&h);
	md5_append(&h, bytes, size);
	md5_finish(&h, digest);
	}

const char* md5_digest_print(const unsigned char digest[16])
	{
	static char digest_print[256];

	for ( int i = 0; i < 16; ++i )
		snprintf(digest_print + i * 2, 3, "%02x", digest[i]);

	return digest_print;
	}

int hmac_key_set = 0;
uint8 shared_hmac_md5_key[16];

void hmac_md5(size_t size, const unsigned char* bytes, unsigned char digest[16])
	{
	if ( ! hmac_key_set )
		internal_error("HMAC-MD5 invoked before the HMAC key is set");

	hash_md5(size, bytes, digest);

	for ( int i = 0; i < 16; ++i )
		digest[i] ^= shared_hmac_md5_key[i];

	hash_md5(16, digest, digest);
	}

void init_random_seed()
	{
	static const int bufsiz = 16;
	uint32 buf[bufsiz];
	int pos = 0;	// accumulates entropy

	// Gather up some entropy.
	gettimeofday((struct timeval *)(buf + pos), 0);
	pos += sizeof(struct timeval) / sizeof(uint32);

#if defined(O_NONBLOCK)
	int fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
#elif defined(O_NDELAY)
	int fd = open("/dev/random", O_RDONLY | O_NDELAY);
#else
	int fd = open("/dev/random", O_RDONLY);
#endif

	if ( fd >= 0 )
		{
		int amt = read(fd, buf + pos, sizeof(uint32) * (bufsiz - pos));
		close(fd);

		if ( amt > 0 )
			pos += (amt / sizeof(uint32));
		}

	if ( pos < bufsiz )
		{
		buf[pos++] = getpid();

		if ( pos < bufsiz )
			buf[pos++] = getuid();
		}

	uint32 result = 0;
	for ( int i = 0; i < pos; ++i )
		{
		result ^= buf[i];
		result = (result << 1) | (result >> 31);
		}

	srandom(result);

#ifdef DEBUG_BRO
	if ( ! hmac_key_set )
#else
	if ( 1 ) // otherwise always select a random key
#endif
		{
		hash_md5(sizeof(buf), (u_char*) buf, shared_hmac_md5_key);
		hmac_key_set = 1;
		}
	}

void message(const char* msg)
	{
	pinpoint();
	fprintf(stderr, "%s\n", msg);
	}

void warn(const char* msg)
	{
	pinpoint();
	fprintf(stderr, "warning: %s\n", msg);
	++nwarn;
	}

void warn(const char* msg, const char* addl)
	{
	pinpoint();
	fprintf(stderr, "warning: %s %s\n", msg, addl);
	++nwarn;
	}

void error(const char* msg)
	{
	pinpoint();
	fprintf(stderr, "error: %s\n", msg);
	++nerr;
	}

void error(const char* msg, const char* addl)
	{
	pinpoint();
	fprintf(stderr, "error: %s %s\n", msg, addl);
	++nerr;
	}

void error(const char* msg, uint32 addl)
	{
	pinpoint();
	fprintf(stderr, "error: %s - %u\n", msg, addl);
	++nerr;
	}

void run_time(const char* msg)
	{
	pinpoint();
	fprintf(stderr, "run-time error: %s\n", msg);
	++nruntime;
	}

void run_time(const char* fmt, BroObj* obj)
	{
	ODesc d;
	obj->Describe(&d);
	run_time(fmt, d.Description());
	}

void run_time(const char* fmt, const char* arg)
	{
	pinpoint();
	fprintf(stderr, "run-time error: ");
	fprintf(stderr, fmt, arg);
	fprintf(stderr, "\n");
	++nruntime;
	}

void run_time(const char* fmt, const char* arg1, const char* arg2)
	{
	pinpoint();
	fprintf(stderr, "run-time error: ");
	fprintf(stderr, fmt, arg1, arg2);
	fprintf(stderr, "\n");
	++nruntime;
	}

void internal_error(const char* fmt, ...)
	{
	va_list al;

	pinpoint();
	fprintf(stderr, "internal error: ");
	va_start(al, fmt);
	vfprintf(stderr, fmt, al);
	va_end(al);
	fprintf(stderr, "\n");
	abort();
	exit(1);
	}

void pinpoint()
	{
	if ( network_time > 0.0 )
		fprintf(stderr, "%.6f ", network_time);
	else
		{
		if ( filename )
			fprintf(stderr, "%s, ", filename);
		fprintf(stderr, "line %d: ", line_number);
		}
	}

int int_list_cmp(const void* v1, const void* v2)
	{
	int i1 = *(int *) v1;
	int i2 = *(int *) v2;

	if ( i1 < i2 )
		return -1;
	else if ( i1 == i2 )
		return 0;
	else
		return 1;
	}

const char* bro_path()
	{
	const char* path = getenv("BROPATH");
	if ( ! path )
		path = ".:policy:policy/local:" LIBDEST;

	return path;
	}

const char* bro_prefixes()
	{
	int len = 1;	// room for \0
	loop_over_list(prefixes, i)
		len += strlen(prefixes[i]) + 1;

	char* p = new char[len];

	loop_over_list(prefixes, j)
		if ( j == 0 )
			strcpy(p, prefixes[j]);
		else
			{
			strcat(p, ":");
			strcat(p, prefixes[j]);
			}

	return p;
	}

FILE* open_file(const char* filename, const char** full_filename)
	{
	if ( full_filename )
		*full_filename = copy_string(filename);

	return fopen(filename, "r");
	}

FILE* search_for_file(const char* filename, const char* ext,
			const char** full_filename)
	{
	if ( filename[0] == '/' || filename[0] == '.' )
		return open_file(filename, full_filename);

	char path[1024], full_filename_buf[1024];
	strcpy(path, bro_path());

	char* dir_beginning = path;
	char* dir_ending = path;
	int more = *dir_beginning != '\0';

	while ( more )
		{
		while ( *dir_ending && *dir_ending != ':' )
			++dir_ending;

		if ( *dir_ending == ':' )
			*dir_ending = '\0';
		else
			more = 0;

		sprintf(full_filename_buf, "%s/%s", dir_beginning, filename);
		if ( access(full_filename_buf, R_OK) == 0 )
			return open_file(full_filename_buf, full_filename);

		sprintf(full_filename_buf, "%s/%s.%s", dir_beginning, filename, ext);
		if ( access(full_filename_buf, R_OK) == 0 )
			return open_file(full_filename_buf, full_filename);

		dir_beginning = ++dir_ending;
		}

	if ( full_filename )
		*full_filename = copy_string(filename);

	return 0;
	}

double current_time()
	{
	struct timeval tv;
	if ( gettimeofday(&tv, 0) < 0 )
		internal_error("gettimeofday failed in current_time()");

	return double(tv.tv_sec) + double(tv.tv_usec) / 1e6;
	}

void out_of_memory(const char* where)
	{
	fprintf( stderr, "bro: out of memory in %s.\n", where );
	abort();
	}

#ifdef malloc

#undef malloc
#undef realloc
#undef free

extern "C" {
void* malloc(size_t);
void* realloc(void*, size_t);
void free(void*);
}

static int malloc_debug = 0;

void* debug_malloc(size_t t)
	{
	void* v = malloc(t);
	if ( malloc_debug )
		printf("%.6f malloc %x %d\n", network_time, v, t);
	return v;
	}

void* debug_realloc(void* v, size_t t)
	{
	v = realloc(v, t);
	if ( malloc_debug )
		printf("%.6f realloc %x %d\n", network_time, v, t);
	return v;
	}

void debug_free(void* v)
	{
	if ( malloc_debug )
		printf("%.6f free %x\n", network_time, v);
	free(v);
	}

void* operator new(size_t t)
	{
	void* v = malloc(t);
	if ( malloc_debug )
		printf("%.6f new %x %d\n", network_time, v, t);
	return v;
	}

void* operator new[](size_t t)
	{
	void* v = malloc(t);
	if ( malloc_debug )
		printf("%.6f new[] %x %d\n", network_time, v, t);
	return v;
	}

void operator delete(void* v)
	{
	if ( malloc_debug )
		printf("%.6f delete %x\n", network_time, v);
	free(v);
	}

void operator delete[](void* v)
	{
	if ( malloc_debug )
		printf("%.6f delete %x\n", network_time, v);
	free(v);
	}

#endif
