/*
 * Distributed under the terms of the GNU General Public License v2
 * $Header: $
 */

#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <unistd.h>
#include <malloc.h>

/*

	libaudit is a passive auditing utility, designed to identify
	format string vulnerability candidates for further 
	investigation.

	created by members of the Gentoo Linux Auditing Team, 
	
		Tavis Ormandy, <taviso@gentoo.org>
		Rob Holland, <tigger@gentoo.org>
		and Ned Ludd, <solar@gentoo.org>

	$ gcc -O2 -fPIC -shared -ldl -o libaudit.so libaudit.c
	# cp libaudit.so /lib
	# echo /lib/libaudit.so >> /etc/ld.so.preload
        
	Additionally the /etc/libaudit.conf may be used to filter out
	undesired programs. Syntax is one entry per line, all entries
	use the base name of the executable as returned by readlink
	/proc/self/exe. /etc/libaudit.conf should be mode 644

*/

/*
 *	You can also set up audit filtering into logfiles with syslog-ng
 *	A working example might look like
 *
 * - /etc/syslog-ng/syslog-ng.conf
 *	 source src { unix-stream("/dev/log"); internal(); };
 *	 destination audit { file("/var/log/audit.log"); };
 *	 filter f_audit { match("libaudit:.*"); };
 *	 log { source(src); filter(f_audit); destination(audit); };
 */

/* static definitions */
#define LIBAUDIT_CONF "/etc/libaudit.conf"

#ifndef AUDIT_PREFIX
 #define AUDIT_PREFIX	"libaudit: "
#endif

#define AUDIT_INFO	AUDIT_PREFIX "info: format string is non-literal: "
#define AUDIT_WARN	AUDIT_PREFIX "warn: non-literal format string contains no specifiers: "
#define AUDIT_DEBUG	AUDIT_PREFIX "debug: "

/* format string definitions */
#define AUDIT_PRINTF	"%s(\"%s\");"
#define AUDIT_FPRINTF	"%s(%s, \"%s\");"
#define AUDIT_SPRINTF	"%s(%p, \"%s\");"
#define AUDIT_SNPRINTF	"%s(%p, %i, \"%s\");"
#define AUDIT_VPRINTF	AUDIT_PRINTF
#define AUDIT_VFPRINTF	AUDIT_FPRINTF
#define AUDIT_VSPRINTF	AUDIT_SPRINTF
#define AUDIT_VSNPRINTF	AUDIT_SNPRINTF

#define AUDIT_SYSTEM	AUDIT_PRINTF
#define AUDIT_POPEN	AUDIT_FPRINTF

/* function pointers... */
static int (* vsprintfptr)	(char *str, const char *format, va_list ap) = 0x00;
static int (* vsnprintfptr)	(char *str, size_t size, const char *format, va_list ap) = 0x00;

static int (* vfprintfptr)	(FILE *stream, const char *format, va_list ap) = 0x00;
static int (* vprintfptr)	(const char *format, va_list ap) = 0x00;

static FILE* (* popenptr)	(const char *command, const char *type) = 0x00;
static int (* systemptr)	(const char *command) = 0x00;

/* name of executable */
static char exename[1024];

/* ignore flag */
static int logging = 0;
static int threshold = 5;

static void *heap_end;
static void *heap_start;

static void __attribute__ ((constructor)) reentrantinit(void)
{
	char buf[1024];
	char *base, *p;
	FILE *fp;
	struct mallinfo malloc_info;

	/* find function pointers using dlsym */
	vsprintfptr = dlsym(RTLD_NEXT, "vsprintf");
	vsnprintfptr = dlsym(RTLD_NEXT, "vsnprintf");
	vfprintfptr = dlsym(RTLD_NEXT, "vfprintf");
	vprintfptr = dlsym(RTLD_NEXT, "vprintf");
	popenptr = dlsym(RTLD_NEXT, "popen");
	systemptr = dlsym(RTLD_NEXT, "system");

	/* enable logging now */
	logging = 1;

	/* try to find name of binary */
	if ((readlink("/proc/self/exe", exename, sizeof(exename) -1)) <= 0)
		return;

	/* check if this program should not be monitored */
	if ((fp = fopen(LIBAUDIT_CONF, "r")) != NULL) {
		base = basename(exename);
		while (fgets(buf, sizeof(buf), fp) != NULL) {
			if ((p = strrchr(buf, '\n')) != NULL)
				*p = 0;
			if ((strcmp(buf, base)) == 0) {
				logging = 0;
				break;
			}
		}
		fclose(fp);
	}
	/* initialise malloc() */
	malloc(0);
	
	/* retrieve statistics */
	malloc_info = mallinfo();

	/* locate heap */
	heap_end = sbrk(0); 
	heap_start = heap_end - malloc_info.arena;

	return;
}

static int format_check(const char *format)
{
	/* is the format string on the heap? */
	if (logging && threshold && (void *) format > heap_start && (void *) format < heap_end) {
		threshold--;
		/* update the heap_end? */
		// heap_end = sbrk(0);
		return (1 + (strchr(format, '%') ? 0 : 1));
	}
	return 0;
}

int printf(const char *format, ...)
{
	int ret;
	va_list ap;
	
	va_start(ap, format);
	ret = vprintfptr(format, ap);
	if (format_check(format)) {
		char *msg = strchr(format, '%') ? 
			AUDIT_INFO AUDIT_PRINTF :
			AUDIT_WARN AUDIT_PRINTF;
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, format);
		closelog();
	}
	va_end(ap);
	return ret;
}

int fprintf(FILE *stream, const char *format, ...)
{
	char fstream[1024], path[1024];
	int ret, cnt;
	va_list ap;

	va_start(ap, format);

	/* save the return value from real function */
	ret = vfprintfptr(stream, format, ap);
	if (format_check(format)) {
		char *msg = strchr(format, '%') ? 
			AUDIT_INFO AUDIT_FPRINTF :
			AUDIT_WARN AUDIT_FPRINTF;
		/* try to identify stream... */
		snprintf(fstream, sizeof(fstream), "/proc/self/fd/%i", fileno(stream));

		/* if not possible, just use a generic "FILE *" */
		if ((cnt = readlink(fstream, path, sizeof(path) - 1)) > 0)
			path[cnt] = 0x00;
		else strncpy(path, "FILE *", sizeof(path));
				
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, path, format);
		closelog();
	}
	va_end(ap);
	return ret;
}

int sprintf(char *str, const char *format, ...)
{
	va_list ap;
	int ret;

	va_start(ap, format);

	ret = vsprintfptr(str, format, ap);
	if (format_check(format)) {
               	char *msg = strchr(format, '%') ? 
			AUDIT_INFO AUDIT_SPRINTF :
			AUDIT_WARN AUDIT_SPRINTF;
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, str, format);
		closelog();
	}
	va_end(ap);
	return ret;
}

int snprintf(char *str, size_t size, const char *format, ...)
{
	va_list ap;
	int ret;

	va_start(ap, format);
	ret = vsnprintfptr(str, size, format, ap);
	if (format_check(format)) {
		char *msg = strchr(format, '%') ?
			 AUDIT_INFO AUDIT_SNPRINTF :
			 AUDIT_WARN AUDIT_SNPRINTF;
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, str, size, format);
		closelog();
	}
	va_end(ap);
	return ret;
}

int vprintf(const char *format, va_list ap)
{
	if (format_check(format)) {
		char *msg = strchr(format, '%') ? 
				AUDIT_INFO AUDIT_VPRINTF :
				AUDIT_WARN AUDIT_VPRINTF;
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, format);
		closelog();
	}
	return vprintfptr(format, ap);
}

int vfprintf(FILE *stream, const char *format, va_list ap)
{
	char fstream[1024], path[1024];
	int cnt;
	
	if (format_check(format)) {
		char *msg = strchr(format, '%') ? 
			AUDIT_INFO AUDIT_VFPRINTF :
			AUDIT_WARN AUDIT_VFPRINTF;

		snprintf(fstream, sizeof(fstream), "/proc/self/fd/%i", fileno(stream));

		if ((cnt = readlink(fstream, path, sizeof(path) - 1)) > 0)
			path[cnt] = 0x00;
		else strncpy(path, "FILE *", sizeof(path));
		
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, path, format);
		closelog();
	}

	return vfprintfptr(stream, format, ap);
}

int vsprintf(char *str, const char *format, va_list ap)
{
	if (format_check(format)) {
		char *msg = strchr(format, '%') ? 
			AUDIT_INFO AUDIT_VSPRINTF :
			AUDIT_WARN AUDIT_VSPRINTF;
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, str, format);
		closelog();
	}
	return vsprintfptr(str, format, ap);
}

int vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
	if (format_check(format)) {
		char *msg = strchr(format, '%') ? 
			AUDIT_INFO AUDIT_VSNPRINTF :
			AUDIT_WARN AUDIT_VSNPRINTF;
		syslog(LOG_DEBUG|LOG_USER, msg, __FUNCTION__, str, size, format);
		closelog();
	}
	return vsnprintfptr(str, size, format, ap);
}

int system(const char *command)
{
	if (logging) { 
		syslog(LOG_DEBUG|LOG_USER, AUDIT_SYSTEM, __FUNCTION__, command);
		closelog();
	}
	return systemptr(command);
}

FILE *popen(const char *command, const char *type)
{
	if (logging) {
		syslog(LOG_DEBUG|LOG_USER, AUDIT_POPEN, __FUNCTION__, command, type);
		closelog();
	}
	return popenptr(command, type);
}
