/*
** CBLM v3
** $Id: prefs.c,v 1.16 2006/05/22 00:20:03 sella Exp $
** Copyright (c) 2004 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

#include "prefs.h"
#include "cblm.h"
#include "db.h"
#include <getopt.h>
#include <pwd.h>
#include <grp.h>
#include <mysql/mysql.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <syslog.h>

/* Global Variables */
struct prefs_t prefs;

/* External Global Variables */
extern struct args_t args;
extern pthread_mutex_t probe_mutex, data_mutex, db_mutex;
extern MYSQL mysql;

/* Getopt Variables */
char const short_options[] = "s:p:I:e:r:Q:M:N:m:a:k:z:x:fl:H:S:D:U:P:CX:WEu:g:t:o::n:d::vh";
struct option const long_options[] = {
	{"source", required_argument, NULL, 's'},
	{"port", required_argument, NULL, 'p'},
	{"interface", required_argument, NULL, 'I'},
	{"name", required_argument, NULL, 'e'},
	{"rate", required_argument, NULL, 'r'},
	{"tos", required_argument, NULL, 'Q'},
	{"dscp", required_argument, NULL, 'M'},
	{"precedence", required_argument, NULL, 'N'},
	{"maxott", required_argument, NULL, 'm'},
	{"maxage", required_argument, NULL, 'a'},
	{"key", required_argument, NULL, 'k'},
	{"size", required_argument, NULL, 'z'},
	{"pattern", required_argument, NULL, 'x'},
	{"fragment", no_argument, NULL, 'f'},
	{"ttl", required_argument, NULL, 'l'},
	{"dbhost", required_argument, NULL, 'H'},
	{"dbsock", required_argument, NULL, 'S'},
	{"dbname", required_argument, NULL, 'D'},
	{"dbuser", required_argument, NULL, 'U'},
	{"dbpass", required_argument, NULL, 'P'},
	{"dbcomp", no_argument, NULL, 'C'},
	{"disable", required_argument, NULL, 'X'},
	{"write", no_argument, NULL, 'W'},
	{"print", no_argument, NULL, 'E'},
	{"user", required_argument, NULL, 'u'},
	{"group", required_argument, NULL, 'g'},
	{"chroot", required_argument, NULL, 't'},
	{"stats", optional_argument, NULL, 'o'},
	{"nice", required_argument, NULL, 'n'},
	{"debug", optional_argument, NULL, 'd'},
	{"version", no_argument, NULL, 'v'},
	{"help", no_argument, NULL, 'h'},
	{NULL, 0, NULL, 0}
};

int get_args(int argc, char** argv) {
	int c, i, len, opt_index = 0;
	char *ptr, buf[512];

	/* Default our settings. */
	memset(&args, 0, sizeof(struct args_t));
	memset(&prefs, 0, sizeof(struct prefs_t));
	args.source.s_addr = htonl(INADDR_ANY);
	args.interface.s_addr = htonl(INADDR_ANY);
	args.nice = DEF_NICENESS;

	/* Set passed arguments. */
	while ((c = getopt_long(argc, argv, short_options, long_options, &opt_index)) != -1) {
		switch (c) {
			case 's':
				/* Try converting as if its an IP address. */
				if (inet_aton(optarg, &args.source) == 0) {
					/* Try finding the IP, assuming its an interface name. */
					source_ip(optarg, &args.source);
				}

				break;

			case 'p':
				prefs.port = atoi(optarg);

				if (prefs.port > 0) {
					prefs.port = htons(prefs.port);
				} else {
					prefs.port = htons(DEF_PORT);
				}

				break;
				
			case 'I':
				/* Try converting as if its an IP address. */
				if (inet_aton(optarg, &args.interface) == 0) {
					/* Try finding the IP, assuming its an interface name. */
					source_ip(optarg, &args.interface);
				}

				break;

			case 'e':
				args.name = (char*) strndup(optarg, 132);

				break;

			case 'r':
				prefs.rate = atoi(optarg);

				if (prefs.rate > MAX_RATE) {
					prefs.rate = DEF_RATE;
				}

				break;
				
			case 'Q':
				add_tos(optarg, 0);

				break;

			case 'M':
				add_tos(optarg, 2);

				break;

			case 'N':
				add_tos(optarg, 5);

				break;

			case 'm':
				prefs.maxott = atoi(optarg);

				if (prefs.maxott > MAX_MAXOTT) {
					prefs.maxott = DEF_MAXOTT;
				}

				break;
				
			case 'a':
				prefs.maxage = atoi(optarg);

				if (prefs.maxage > MAX_MAXAGE || prefs.maxage < MIN_MAXAGE) {
					prefs.maxage = DEF_MAXAGE;
				}

				break;
				
			case 'k':
				strncpy(prefs.key, optarg, sizeof(prefs.key));
				memset(optarg, 'x', strlen(optarg) < sizeof(prefs.key) ? strlen(optarg) : sizeof(prefs.key));

				break;

			case 'z':
				prefs.size = atoi(optarg);

				if (prefs.size > MAX_SIZE) {
					prefs.size = DEF_SIZE;
				}

				break;
				
			case 'x':
				prefs.pattern = (char*) strndup(optarg, 255);

				break;

			case 'f':
				prefs.fragment = 1;

				break;

			case 'l':
				prefs.ttl = atoi(optarg);

				if (prefs.ttl > MAX_TTL || prefs.ttl < 1) {
					prefs.maxott = DEF_TTL;
				}

				break;

			case 'H':
				ptr = NULL;

				args.db_host = (char*) strndup(optarg, 132);

				for (i = 0; i < strlen(args.db_host); i++) {
					if (args.db_host[i] == ':') {
						args.db_host[i] = '\0';
						ptr = args.db_host + i + 1;
					}
				}

				if (ptr) {
					args.db_port = atoi(ptr);
				} else {
					args.db_port = DEF_DBPORT;
				}

				break;

			case 'S':
				args.db_sock = (char*) strndup(optarg, 255);

				break;

			case 'D':
				args.db_name = (char*) strndup(optarg, 132);

				break;

			case 'U':
				args.db_user = (char*) strndup(optarg, 132);

				break;

			case 'P':
				args.db_pass = (char*) strndup(optarg, 132);
				memset(optarg, 'x', strlen(optarg) < 132 ? strlen(optarg) : 132);

				break;

			case 'C':
				args.db_comp = 1;

				break;

			case 'X':
				if (strncasecmp(optarg, "ott", 3) == 0) {
					prefs.trk_ott = -1;
				} else if (strncasecmp(optarg, "jtr", 3) == 0) {
					prefs.trk_jtr = -1;
				} else if (strncasecmp(optarg, "tos", 3) == 0) {
					prefs.trk_tos = -1;
				} else if (strncasecmp(optarg, "pri", 3) == 0) {
					prefs.trk_pri = -1;
				} else if (strncasecmp(optarg, "ooo", 3) == 0) {
					prefs.trk_ooo = -1;
				} else if (strncasecmp(optarg, "dup", 3) == 0) {
					prefs.trk_ooo = -1;
				} else {
					fprintf(stderr, "--disable supports the following arguments; ott, jtr, tos, ooo and dup\n");
				}

				break;

			case 'W':
				args.write = 1;

				if (args.debug == 0) {
					args.debug = DEBUG_ERROR;
				}

				break;

			case 'E':
				args.print = 1;

				if (args.debug == 0) {
					args.debug = DEBUG_ERROR;
				}

				break;

			case 'u':
				args.user = (char*) strndup(optarg, 32);

				break;

			case 'g':
				args.group = (char*) strndup(optarg, 32);

				break;

			case 't':
				args.chroot = (char*) strndup(optarg, 255);

				break;

			case 'o':
				if (optarg) {
					args.stats = (char*) strndup(optarg, 255);
				} else {
					args.stats = (char*) strndup(DEF_STATS, 255);
				}

				break;

			case 'n':
				if (optarg) {
					args.nice = atoi(optarg);
				}

				if (args.nice < -19 || args.nice > 20) {
					args.nice = DEF_NICENESS;
				}

				break;
				
			case 'd':
				if (optarg) {
					args.debug = atoi(optarg);
					if (args.debug < 0 || args.debug > DEBUG_MAXVAL) {
						args.debug = DEBUG_ERROR;
					}
				} else {
					args.debug = DEBUG_ERROR;
				}

				break;

			case 'v':
				version(0);

				return EXIT_FAILURE;

			default:
			case 'h':
				usage();
				version(1);

				return EXIT_FAILURE;
		}
	}

	/* At least one output must be used. */
	if (args.db_host == NULL && args.db_sock == NULL) {
		fprintf(stderr, "Database not configured via the command line.\n");
		return EXIT_FAILURE;
	}

	if (args.db_name == NULL || strlen(args.db_name) < 1) {
		if (args.db_name) free(args.db_name);
		args.db_name = (char*) strndup(DEF_DBNAME, 132);
	}

	if (args.db_user == NULL || strlen(args.db_user) < 1) {
		if (args.db_user) free(args.db_pass);
		args.db_user = (char*) strndup(DEF_DBUSER, 132);
	}

	if (args.db_pass == NULL || strlen(args.db_pass) < 1) {
		if (args.db_pass) free(args.db_pass);
		args.db_pass = (char*) strndup(DEF_DBPASS, 132);
	}

	if (args.source.s_addr == htonl(INADDR_ANY)) {
		source_ip("eth0", &args.source);
		if (args.source.s_addr == htonl(INADDR_ANY)) {
			fprintf(stderr, "FIXME: We should use the IP that the kernel would source from. Getting eth0 address for now.\n");
			fprintf(stderr, "Failed to find a source address to transmit packets with. Please specify with -s.\n");
			return EXIT_FAILURE;
		}
	}

	if (args.name == NULL || strlen(args.name) < 1) {
		if (args.name) free(args.name);

		/* FIXME: Should do a reverse DNS lookup and use that before the default name. */
		args.name = (char*) strndup(inet_ntoa(args.source), 132);
	}

	return EXIT_SUCCESS;
}

void usage() {
	printf("Usage: %s [OPTION] <-H HOST[:PORT]>\n", PROGRAM_NAME);
	printf("A high performance active latency, loss and jitter probe.\n");
	printf("\nPreferences:\n");
	printf("  -s, --source=IP             Source traffic from specified IP (default: any)\n");
	printf("  -p, --port=PORT             Listen on specified port (default: %d)\n", DEF_PORT);
	printf("  -I, --interface=INTERFACE   Listen on specified interface (default: all)\n");
	printf("  -e, --name=STRING           Probe descriptive name. (default: IP)\n");
	printf("  -r, --rate=NUM              Rate to transmit packets at (NUM/min) (default: %lu/min)\n", DEF_RATE);
	printf("  -Q, --tos=TOS[:PRI]         Transmit with specified ToS and priority (default: %s)\n", strlen(DEF_TOS) > 1 ? DEF_TOS : "0:0");
	printf("  -M, --dscp=DSCP[:PRI]       Transmit with specified DSCP and priority (default: %s)\n", strlen(DEF_TOS) > 1 ? DEF_TOS : "0:0");
	printf("  -N, --precedence=PREC[:PRI] Transmit with specified precedence and priority (default: %s)\n", strlen(DEF_TOS) > 1 ? DEF_TOS : "0:0");
	printf("  -m, --maxott=MSEC           Maximum allowed one way trip time (ms) between probes. (default: %lu)\n", DEF_MAXOTT);
	printf("  -a, --maxage=SEC            Maximum age (sec) of probe before it is assumed dead. (default: %lu)\n", DEF_MAXAGE);
	printf("  -k, --key=STRING            Key used for authorizing packets (%lu char max). (default: %s)\n", sizeof(prefs.key), strlen(DEF_KEY) > 1 ? DEF_KEY : "none");
	printf("  -z, --size=BYTES            Size of packet payload. (default: %lu)\n", DEF_SIZE);
	printf("  -x, --pattern=STRING        Pattern to load into packets. (default: %s)\n", strlen(DEF_PATTERN) > 1 ? DEF_PATTERN : "none");
	printf("  -f, --fragment              Allow packet fragementation (disable DF). (default: off)\n");
	printf("  -l, --ttl=NUM               Set time to live (TTL) for outgoing packets. (default: %u)\n", DEF_TTL);
	printf("  -X, --disable=FEATURE       Disable feature OTT, JTR, TOS, PRI, OOO or DUP\n");
	printf("\nDatabase:\n");
	printf("  -H, --dbhost=HOST[:PORT]    MySQL host for data storage and communication. (default: %s)\n", strlen(DEF_DBHOST) > 1 ? DEF_DBHOST : "none");
	printf("  -S, --dbsock=SOCKET         MySQL socket for data storage and communication. (default: %s)\n", strlen(DEF_DBSOCK) > 1 ? DEF_DBSOCK : "none");
	printf("  -D, --dbname=DATABASE       MySQL database to store data within. (default: %s)\n", strlen(DEF_DBNAME) > 1 ? DEF_DBNAME : "none");
	printf("  -U, --dbuser=USER           Use specified username for connecting to the database. (default: %s)\n", strlen(DEF_DBUSER) > 1 ? DEF_DBUSER : "none");
	printf("  -P, --dbpass=PASSWORD       Use specified password for connecting to the database. (default: %s)\n", strlen(DEF_DBPASS) > 1 ? DEF_DBPASS : "none");
	printf("  -C, --dbcomp                Use compression protocol to communicate with the database.\n");
	printf("  -W, --write                 Write locally specified preferences into the database and exit.\n");
	printf("  -E, --print                 Print probe preferences stored within the database and exit.\n");
	printf("\nProcess:\n");
	printf("  -u, --user=USER             Run process as USER (default: current)\n");
	printf("  -g, --group=GROUP           Run process as GROUP (default: current)\n");
	printf("  -t, --chroot=DIR            Change root to DIR (default: none)\n");
	printf("  -o, --stats[=FILE]          Write stats to FILE (default: %s)\n", strlen(DEF_STATS) > 1 ? DEF_STATS : "none");
	printf("  -n, --nice=+/-NUM           Niceness at which to run process (default: %d)\n", DEF_NICENESS);
	printf("  -d, --debug[=LEVEL]         Enable debug and remain in foreground\n");
	printf("\nGeneral:\n");
	printf("  -v, --version               Displays version information\n");
	printf("  -h, --help                  Displays this help information\n\n");
	printf("Examples:\n\n");
	printf("  %s -H sql.domain.com -P mypass\n", PROGRAM_NAME);
	printf("  %s -H sql.domain.com:3306 -U cblm -P mypass\n", PROGRAM_NAME);
	printf("  %s -p 1337 -n 10 -H sql.domain.com -P mypass\n", PROGRAM_NAME);
	printf("  %s --set -M 0:0 -M 40:5 -x '%s' -H sql.domain.com -P mypass\n", PROGRAM_NAME, DEF_PATTERN);
	printf("\n");
}

void version(int flag) {
	if (flag == 0 || flag == 2) {
		printf("%s v%s\n", PROGRAM_NAME, PROGRAM_VERS);
	}
	printf("Written by James M. Sella <sella@digitalgenesis.com>\n");
	if (!flag) {
		printf("\nCopyright (c) 2004-2006 James M. Sella. All rights reserved.\n");
		printf("This is free software; see the source for copying conditions. There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n");
	}
}

void use_args() {
	struct passwd *ent;
	struct group *gent;

	/* Set our niceness */
	setpriority(PRIO_PROCESS, 0, args.nice);

	/* If euid is root, we can change the owner, group and root dir. */
	if (geteuid() != 0 && (args.group || args.user || args.chroot)) {
		fprintf(stderr, "Only root may change the user, group or root directory of a process.\n");
	} else {
		ent = NULL;
		gent = NULL;

		/* Read in the group, if needed. */
		if (args.group) {
			if (!(gent = getgrnam(args.group))) {
				fprintf(stderr, "Group '%s' was not found.\n", args.group);
			}
		}

		/* Read in the user, if needed. */
		if (args.user) {
			if (!(ent = getpwnam(args.user))) {
				fprintf(stderr, "User '%s' was not found.\n", args.user);
			}
		}

		/* Lock ourselves into a chroot jail, if requested. */
		if (args.chroot) {
			if (chroot(args.chroot) == -1) {
				fprintf(stderr, "Failed to chroot to '%s'\n", args.chroot);
			}
		}

		/* Set our new group. */
		if (gent && setegid(gent->gr_gid)) {
			fprintf(stderr, "Failed to set GID to '%d'.\n", gent->gr_gid);
		}

		/* Set our new user. */
		if (ent && seteuid(ent->pw_uid)) {
			fprintf(stderr, "Failed to set GID to '%d'.\n", ent->pw_uid);
		}
	}
}

int read_prefs(int overwrite) {
	int first = 1;
	MYSQL_RES *res;
	MYSQL_ROW row;

	pthread_mutex_lock(&probe_mutex);
	pthread_mutex_lock(&db_mutex);

	prefs.last = 0; /* Matches last_prefs() return value if no 'last' set. */

	if (db_query(&res, "SELECT var, val FROM prefs") == 0) {
		while (row = mysql_fetch_row(res)) {
			if (args.debug >= DEBUG_DUMP) {
				syslog(LOG_DEBUG, "read val: %s, var: %s", row[0], row[1]);
			}

			if (overwrite == 1) {
				if (!strncmp(row[0], "port", 4)) {
					prefs.port = htons(atoi(row[1]));
				} else if (!strncmp(row[0], "rate", 4)) {
					prefs.rate = atoi(row[1]);
				} else if (!strncmp(row[0], "size", 4)) {
					prefs.size = atoi(row[1]);
				} else if (!strncmp(row[0], "fragment", 8)) {
					prefs.fragment = atoi(row[1]);
				} else if (!strncmp(row[0], "ttl", 3)) {
					prefs.ttl = atoi(row[1]);
				} else if (!strncmp(row[0], "maxott", 6)) {
					prefs.maxott = atoi(row[1]);
				} else if (!strncmp(row[0], "maxage", 6)) {
					prefs.maxage = atoi(row[1]);
				} else if (!strncmp(row[0], "key", 3)) {
					strncpy(prefs.key, row[1], sizeof(prefs.key));
				} else if (!strncmp(row[0], "tos", 3)) {
					if (first) {
						free_tos();
						first = 0;
					}
					add_tos(row[1], 0);
				} else if (!strncmp(row[0], "pattern", 7)) {
					prefs.pattern = (char*) strdup(row[1]);
				} else if (!strncmp(row[0], "last", 4)) {
					prefs.last = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_ott", 7)) {
					prefs.trk_ott = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_jtr", 7)) {
					prefs.trk_jtr = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_tos", 7)) {
					prefs.trk_tos = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_pri", 7)) {
					prefs.trk_pri = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_ooo", 7)) {
					prefs.trk_ooo = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_dup", 7)) {
					prefs.trk_dup = atoi(row[1]);
				}
			} else {
				if (!strncmp(row[0], "port", 4) && prefs.port == 0) {
					prefs.port = htons(atoi(row[1]));
				} else if (!strncmp(row[0], "rate", 4) && prefs.rate == 0) {
					prefs.rate = atoi(row[1]);
				} else if (!strncmp(row[0], "size", 4) && prefs.size == 0) {
					prefs.size = atoi(row[1]);
				} else if (!strncmp(row[0], "fragment", 8) && prefs.fragment == 0) {
					prefs.fragment = atoi(row[1]);
				} else if (!strncmp(row[0], "ttl", 3) && prefs.ttl == 0) {
					prefs.ttl = atoi(row[1]);
				} else if (!strncmp(row[0], "maxott", 6) && prefs.maxott == 0) {
					prefs.maxott = atoi(row[1]);
				} else if (!strncmp(row[0], "maxage", 6) && prefs.maxage == 0) {
					prefs.maxage = atoi(row[1]);
				} else if (!strncmp(row[0], "key", 3) && strlen(prefs.key) < 1) {
					strncpy(prefs.key, row[1], sizeof(prefs.key));
				} else if (!strncmp(row[0], "tos", 3) && (prefs.tos == NULL || !first)) {
					if (first) {
						free_tos();
						first = 0;
					}
					add_tos(row[1], 0);
				} else if (!strncmp(row[0], "pattern", 7) && prefs.pattern == NULL) {
					prefs.pattern = (char*) strdup(row[1]);
				} else if (!strncmp(row[0], "last", 4) && prefs.last == 0) {
					prefs.last = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_ott", 7) && prefs.trk_ott == 0) {
					prefs.trk_ott = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_jtr", 7) && prefs.trk_jtr == 0) {
					prefs.trk_jtr = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_tos", 7) && prefs.trk_tos == 0) {
					prefs.trk_tos = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_pri", 7) && prefs.trk_pri == 0) {
					prefs.trk_pri = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_ooo", 7) && prefs.trk_ooo == 0) {
					prefs.trk_ooo = atoi(row[1]);
				} else if (!strncmp(row[0], "trk_dup", 7) && prefs.trk_dup == 0) {
					prefs.trk_dup = atoi(row[1]);
				}
			}
		}
		mysql_free_result(res);
	}

	pthread_mutex_unlock(&db_mutex);
	pthread_mutex_unlock(&probe_mutex);

	/* Set defaults if nothing is set. */
	if (prefs.port == 0) {
		prefs.port = htons(DEF_PORT);
	}

	if (prefs.rate == 0) {
		prefs.rate = DEF_RATE;
	}

	if (prefs.size == 0) {
		prefs.size = DEF_SIZE;
	}

	if (prefs.ttl == 0) {
		prefs.ttl = DEF_TTL;
	}

	if (prefs.maxott == 0) {
		prefs.maxott = DEF_MAXOTT;
	}

	if (prefs.maxage == 0) {
		prefs.maxage = DEF_MAXAGE;
	}

	if (strlen(prefs.key) < 1) {
		strncpy(prefs.key, DEF_KEY, sizeof(prefs.key));
	}

	if (prefs.tos == NULL) {
		add_tos(DEF_TOS, 0);
	}

	if (prefs.pattern == NULL) {
		prefs.pattern = (char*) malloc(strlen(DEF_PATTERN) + 1);
		strcpy(prefs.pattern, DEF_PATTERN);
	}

	if (prefs.fragment == 0) {
		prefs.fragment = -1;
	}

	if (prefs.trk_ott == 0) {
		prefs.trk_ott = 1;
	}

	if (prefs.trk_jtr == 0) {
		prefs.trk_jtr = 1;
	}

	if (prefs.trk_tos == 0) {
		prefs.trk_tos = 1;
	}

	if (prefs.trk_pri == 0) {
		prefs.trk_pri = 1;
	}

	if (prefs.trk_ooo == 0) {
		prefs.trk_ooo = 1;
	}

	if (prefs.trk_dup == 0) {
		prefs.trk_dup = 1;
	}

	return 0;
}

int write_prefs() {
	struct tos_t* cur;
	int ret_val = 1;
	char sql[132], front[] = "INSERT INTO prefs (var,val) VALUES(";
	MYSQL_RES *res;
	MYSQL_ROW row;

	pthread_mutex_lock(&db_mutex);

	if (db_query(&res, "LOCK TABLE prefs WRITE") != 0) {
		goto exit;
	}
	mysql_free_result(res);

	if (db_query(&res, "DELETE FROM prefs") != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'port',%d)", front, ntohs(prefs.port));
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'rate',%lu)", front, prefs.rate);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'size',%lu)", front, prefs.size);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'fragment',%lu)", front, prefs.fragment);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'ttl',%lu)", front, prefs.ttl);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'maxott',%lu)", front, prefs.maxott);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'maxage',%lu)", front, prefs.maxage);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'key','%s')", front, prefs.key);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	cur = prefs.tos;
	while (cur != NULL) {
		snprintf(sql, sizeof(sql), "%s'tos','%d:%d')", front, cur->tos, cur->pri);
		if (db_query(&res, sql) != 0) {
			goto unlock;
		}
		mysql_free_result(res);
		cur = cur->next;
	}

	snprintf(sql, sizeof(sql), "%s'pattern','%s')", front, prefs.pattern);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	prefs.last = time(NULL);
	snprintf(sql, sizeof(sql), "%s'last',%lu)", front, prefs.last);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'trk_ott','%d')", front, prefs.trk_ott);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'trk_jtr','%d')", front, prefs.trk_jtr);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'trk_tos','%d')", front, prefs.trk_tos);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'trk_pri','%d')", front, prefs.trk_pri);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'trk_ooo','%d')", front, prefs.trk_ooo);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	snprintf(sql, sizeof(sql), "%s'trk_dup','%d')", front, prefs.trk_dup);
	if (db_query(&res, sql) != 0) {
		goto unlock;
	}
	mysql_free_result(res);

	/* Successful. */
	ret_val = 0;

unlock:
	db_query(&res, "UNLOCK TABLES");
	mysql_free_result(res);

exit:
	pthread_mutex_unlock(&db_mutex);

	return ret_val;
}

time_t last_prefs() {
	MYSQL_RES *res;
	MYSQL_ROW row;
	time_t last = 0;

	pthread_mutex_lock(&probe_mutex);
	pthread_mutex_lock(&db_mutex);

	if (db_query(&res, "SELECT val FROM prefs WHERE var = 'last'") == 0) {
		while (row = mysql_fetch_row(res)) {
			if (args.debug >= DEBUG_DUMP) {
				syslog(LOG_DEBUG, "read val: %s (last)", row[0]);
			}
			last = atoi(row[0]);
		}

		mysql_free_result(res);
	}

	pthread_mutex_unlock(&db_mutex);
	pthread_mutex_unlock(&probe_mutex);

	return last;
}

void add_tos(const char* str, int shift) {
	/* NOTE: Allows duplicate entries and converts garbage input to 0. */
	int i, len;
	char buf[132];
	struct tos_t *node, **cur;

	assert(str != NULL);

	/* Zero buf and copy input. */
	memset(buf, 0, sizeof(buf));
	strncpy(buf, str, sizeof(buf) - 1);

	/* Build new tos node */
	len = strlen(buf);
	for (i = 0; i <= len; i++) {
		if (buf[i] == ':' || buf[i] == '\0') {
			/* Allocate tos_t struct. */
			node = malloc(sizeof(struct tos_t));
			node->next = NULL;

			/* Set ToS and Priority */
			node->tos = atoi(buf) << shift;
			if (buf[i + 1] == '\0') {
				node->pri = node->tos >> 5;
			} else {
				node->pri = atoi(buf + i + 1);
			}

			break;
		}
	}

	/* Don't add entries with invalid tos or pri. */
	if (node->tos > 255 || node->pri > 7) {
		free(node);

		return;
	}

	/* Find end of list and check for duplicates. */
	cur = &(prefs.tos);
	while (*cur != NULL) {
		/* Don't add duplicate entries. */
		if ((*cur)->tos == node->tos && (*cur)->pri == node->pri) {
			free(node);

			return;
		}
		cur = &(*cur)->next;
	}

	/* Add node to end of list. */
	*cur = node;
}

void free_args() {
	if (args.db_host) free(args.db_host);
	if (args.db_sock) free(args.db_sock);
	if (args.db_name) free(args.db_name);
	if (args.db_user) free(args.db_user);
	if (args.db_pass) free(args.db_pass);
	if (args.name) free(args.name);
	if (args.user) free(args.user);
	if (args.group) free(args.group);
	if (args.chroot) free(args.chroot);
	if (args.stats) free(args.stats);

	memset(&args, 0, sizeof(args));
}

void free_prefs() {
	free_tos();

	if (prefs.pattern) free(prefs.pattern);
	
	memset(&prefs, 0, sizeof(prefs));
}

void free_tos() {
	struct tos_t *cur, *prev;

	cur = prefs.tos;
	while (cur != NULL) {
		prev = cur;
		cur = cur->next;
		free(prev);
	}
	prefs.tos = NULL;
}

void display_args() {
	char *tmp;

	syslog(LOG_DEBUG, "[Arguments]");

	syslog(LOG_DEBUG, " DB Host: %s:%lu", (args.db_host) ? args.db_host : "none", args.db_port);
	syslog(LOG_DEBUG, " DB Sock: %s", (args.db_sock) ? args.db_sock : "none");
	syslog(LOG_DEBUG, " DB Name: %s", (args.db_name) ? args.db_name : "none");
	syslog(LOG_DEBUG, " DB User: %s", (args.db_user) ? args.db_user : "none");
	syslog(LOG_DEBUG, " DB Pass: %s", (args.db_pass) ? args.db_pass : "none");
	syslog(LOG_DEBUG, " DB Comp: %s", (args.db_comp) ? "true" : "false");

	syslog(LOG_DEBUG, " Write: %s", (args.write) ? "true" : "false");
	syslog(LOG_DEBUG, " Print: %s", (args.print) ? "true" : "false");

	tmp = inet_ntoa(args.source);
	syslog(LOG_DEBUG, " Source: %s", tmp);

	tmp = inet_ntoa(args.interface);
	syslog(LOG_DEBUG, " Interface: %s", tmp);

	syslog(LOG_DEBUG, " Name: %s", args.name);

	if (args.user) {
		syslog(LOG_DEBUG, " User: %s", args.user);
	} else {
		syslog(LOG_DEBUG, " User: current");
	}

	if (args.group) {
		syslog(LOG_DEBUG, " Group: %s", args.group);
	} else {
		syslog(LOG_DEBUG, " Group: current");
	}

	if (args.stats) {
		syslog(LOG_DEBUG, " Stats: %s", args.stats);
	} else {
		syslog(LOG_DEBUG, " Stats: disabled");
	}

	if (args.chroot) {
		syslog(LOG_DEBUG, " Chroot: %s", args.chroot);
	} else {
		syslog(LOG_DEBUG, " Chroot: disabled");
	}

	syslog(LOG_DEBUG, " Nice: %d", args.nice);

	if (args.debug > 0) {
		syslog(LOG_DEBUG, " Debug: %d", args.debug);
	} else {
		syslog(LOG_DEBUG, " Debug: disabled");
	}
}

void display_prefs(int tty) {
	char buf[132], buf2[32];
	struct tos_t* cur;

	if (tty == 1) {
		fprintf(stderr, "\n[Preferences]\n");

		fprintf(stderr, " Port: %d\n", ntohs(prefs.port));

		fprintf(stderr, " Rate: %d/min\n", prefs.rate);

		fprintf(stderr, " Size: %d bytes\n", prefs.size);

		fprintf(stderr, " TTL: %d\n", prefs.ttl);

		fprintf(stderr, " MaxOTT: %d msec\n", prefs.maxott);

		fprintf(stderr, " MaxAge: %d sec\n", prefs.maxage);

		fprintf(stderr, " Key: %s\n", prefs.key);

		cur = prefs.tos;
		while (cur != NULL) {
			fprintf(stderr, " ToS: %d:%d\n", cur->tos, cur->pri);
			cur = cur->next;
		}

		fprintf(stderr, " Pattern: %s\n", prefs.pattern);

		fprintf(stderr, " Fragment: %s\n", (prefs.fragment == 1) ? "true" : "false");

		fprintf(stderr, " Track OTT: %s\n", (prefs.trk_ott == 1) ? "true" : "false");

		fprintf(stderr, " Track JTR: %s\n", (prefs.trk_jtr == 1) ? "true" : "false");

		fprintf(stderr, " Track TOS %s\n", (prefs.trk_tos == 1) ? "true" : "false");

		fprintf(stderr, " Track OOO: %s\n", (prefs.trk_ooo == 1) ? "true" : "false");

		fprintf(stderr, " Track DUP: %s\n\n", (prefs.trk_dup == 1) ? "true" : "false");
	} else {
		syslog(LOG_DEBUG, "[Preferences]");

		syslog(LOG_DEBUG, " Port: %d", ntohs(prefs.port));

		syslog(LOG_DEBUG, " Rate: %d/min", prefs.rate);

		syslog(LOG_DEBUG, " Size: %d bytes", prefs.size);

		syslog(LOG_DEBUG, " TTL: %d", prefs.ttl);

		syslog(LOG_DEBUG, " MaxOTT: %d msec", prefs.maxott);

		syslog(LOG_DEBUG, " MaxAge: %d sec", prefs.maxage);

		syslog(LOG_DEBUG, " Key: %s", prefs.key);

		cur = prefs.tos;
		while (cur != NULL) {
			syslog(LOG_DEBUG, " ToS: %d:%d", cur->tos, cur->pri);
			cur = cur->next;
		}

		syslog(LOG_DEBUG, " Pattern: %s", prefs.pattern);

		syslog(LOG_DEBUG, " Fragment: %s", (prefs.fragment == 1) ? "true" : "false");

		syslog(LOG_DEBUG, " Track OTT: %s", (prefs.trk_ott == 1) ? "true" : "false");

		syslog(LOG_DEBUG, " Track JTR: %s", (prefs.trk_jtr == 1) ? "true" : "false");

		syslog(LOG_DEBUG, " Track TOS %s", (prefs.trk_tos == 1) ? "true" : "false");

		syslog(LOG_DEBUG, " Track PRI %s", (prefs.trk_pri == 1) ? "true" : "false");

		syslog(LOG_DEBUG, " Track OOO: %s", (prefs.trk_ooo == 1) ? "true" : "false");

		syslog(LOG_DEBUG, " Track DUP: %s", (prefs.trk_dup == 1) ? "true" : "false");
	}
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim: noet ts=3 sw=3
*/
