/* GKrellM
|  Copyright (C) 1999-2004 Bill Wilson
|
|  Author:  Bill Wilson    bill@gkrellm.net
|  Latest versions might be found at:  http://gkrellm.net
|
|  This program is free software which I release under the GNU General Public
|  License. You may redistribute and/or modify this program under the terms
|  of that license as published by the Free Software Foundation; either
|  version 2 of the License, or (at your option) any later version.
|
|  This program is distributed in the hope that it will be useful,
|  but WITHOUT ANY WARRANTY; without even the implied warranty of
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|  GNU General Public License for more details.  Version 2 is in the
|  COPYRIGHT file in the top level directory of this distribution.
| 
|  To get a copy of the GNU General Puplic License, write to the Free Software
|  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "gkrellmd.h"
#include "gkrellmd-private.h"

#ifndef HAVE_GETADDRINFO
struct addrinfo
	{
	int		ai_flags;		/* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
	int		ai_family;		/* PF_xxx */
	int		ai_socktype;	/* SOCK_xxx */
	int		ai_protocol;	/* 0 or IPPROTO_xxx for IPv4 and IPv6 */
	size_t	ai_addrlen;		/* length of ai_addr */
	char	*ai_canonname;	/* canonical name for hostname */
	struct	sockaddr *ai_addr;	/* binary address */
	struct	addrinfo *ai_next;	/* next structure in linked list */
	};
#endif

#if !defined(IPV6_V6ONLY) && defined(IPV6_BINDV6ONLY)
#define	IPV6_V6ONLY	IPV6_BINDV6ONLY
#endif

struct GkrellmdConfig	_GK;
GkrellmdTicks			GK;

GList			*gkrellmd_client_list,
				*gkrellmd_plugin_config_list;

static GList	*allow_host_list;

static gboolean	detach_flag;

#if !defined(WIN32)
struct
	{
	uid_t	uid;
	uid_t	gid;
	}
	drop_privs = { 0, 0 };
#endif

static void
make_pidfile(void)
	{
#if !defined(WIN32)
	FILE	*f;

	if (!_GK.pidfile)
		return;
	f = fopen(_GK.pidfile, "w");
	if (f)
		{
		fprintf(f, "%d\n", getpid());
		fclose(f);
		}
	else
		fprintf(stderr, "gkrellmd: Can't create pidfile %s\n", _GK.pidfile);
#endif
	}

static void
remove_pidfile(void)
	{
#if !defined(WIN32)
	if (_GK.pidfile)
		unlink(_GK.pidfile);
#endif
	}

static void
cb_sigterm(gint sig)
	{
	remove_pidfile();
    exit(0);
	}

gint
gkrellmd_send_to_client(GkrellmdClient *client, gchar *buf)
	{
	gint	n;

	if (!client->alive)
		return 0;
#if defined(MSG_NOSIGNAL)
	n = send(client->fd, buf, strlen(buf), MSG_NOSIGNAL);
#else
	n = send(client->fd, buf, strlen(buf), 0);
#endif
	if (n < 0 && errno == EPIPE)
		{
		if (_GK.verbose)
			printf("Write on closed pipe to host %s\n", client->hostname);
		client->alive = FALSE;
		}
	return n;
	}

#if 0
static gint
getline(gint fd, gchar *buf, gint len)
	{
	fd_set			read_fds;
	struct timeval	tv;
	gchar			*s;
	gint			result, n;

	FD_ZERO(&read_fds);
	FD_SET(fd, &read_fds);
	tv.tv_usec = 0;
	tv.tv_sec = 15;
	s = buf;
	*s = '\0';
	for (n = 0; n < len - 1; ++n)
		{
		result = select(fd + 1, &read_fds, NULL, NULL, &tv);
		if (result <= 0 || read(fd, s, 1) != 1)
			break;
		if (*s == '\n')
			{
			*s = '\0';
			break;
			}
		*++s = '\0';
		}
	return n;
	}
#endif

#ifdef HAVE_GETADDRINFO
static gboolean
is_valid_reverse(char *addr, char *host, sa_family_t family)
	{
	struct addrinfo	hints, *res, *r;
	int		error, good;
	char		addrbuf[NI_MAXHOST];

	/* Reject numeric addresses */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = family;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
	if (getaddrinfo(host, NULL, &hints, &res) == 0)
		{
		freeaddrinfo(res);
		return 0;
		}

	/* Check for spoof */
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = family;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;
	if (getaddrinfo(host, NULL, &hints, &res) != 0)
		return 0;
	good = 0;
	for (r = res; good == 0 && r; r = r->ai_next)
		{
		error = getnameinfo(r->ai_addr, r->ai_addrlen,
				    addrbuf, sizeof(addrbuf), NULL, 0,
				    NI_NUMERICHOST | NI_WITHSCOPEID);
		if (error == 0 && strcmp(addr, addrbuf) == 0)
			{
			good = 1;
			break;
			}
		}
	freeaddrinfo(res);
	return good;
	}
#endif

/* Check for CIDR match.
 */
static gboolean
cidr_match(struct sockaddr *sa, socklen_t salen, char *allowed)
{
#ifdef HAVE_GETADDRINFO
	struct addrinfo hints, *res;
	struct sockaddr_storage	ss;
	char		*buf;
	char		*p, *ep;
	guchar		*addr, *pat;
	u_int32_t	mask;
	int			plen, i;
	gboolean	result;

	buf = g_strdup(allowed);
	plen = -1;
	if ((p = strchr(buf, '/')) != NULL)
		{
		plen = strtoul(p + 1, &ep, 10);
		if (errno != 0 || ep == NULL || *ep != '\0' || plen < 0)
			{
			g_free(buf);
			return FALSE;
			}
		*p = '\0';
		allowed = buf;
		}

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
	result = getaddrinfo(allowed, NULL, &hints, &res);
	g_free(buf);
	if (result != 0)
		return FALSE;
	memcpy(&ss, res->ai_addr, res->ai_addrlen);
	freeaddrinfo(res);

	if (sa->sa_family != ((struct sockaddr *)&ss)->sa_family)
		return FALSE;
	switch (sa->sa_family)
		{
		case AF_INET6:
			if (plen < 0)
				plen = 128;
			if (plen > 128)
				return FALSE;
			if (((struct sockaddr_in6 *)&ss)->sin6_scope_id != 0 &&
			    ((struct sockaddr_in6 *)&ss)->sin6_scope_id !=
			    ((struct sockaddr_in6 *)sa)->sin6_scope_id)
				return FALSE;
			addr = (guchar *)&((struct sockaddr_in6 *)sa)->sin6_addr;
			pat = (guchar *)&((struct sockaddr_in6 *)&ss)->sin6_addr;
			i = 0;
			while (plen > 0)
				{
				if (plen < 32)
					{
					mask = htonl(~(0xffffffff >> plen));
					if ((*(u_int32_t *)&addr[i] & mask) !=
					    (*(u_int32_t *)&pat[i] & mask))
						return FALSE;
					break;
					}
				if (*(u_int32_t *)&addr[i] !=
				    *(u_int32_t *)&pat[i])
					return FALSE;
				i += 4;
				plen -= 32;
				}
			break;
		case AF_INET:
			if (plen < 0)
				plen = 32;
			if (plen > 32)
				return FALSE;
			addr = (guchar *)&((struct sockaddr_in *)sa)->sin_addr;
			pat = (guchar *)&((struct sockaddr_in *)&ss)->sin_addr;
			mask = htonl(~(0xffffffff >> plen));
			if ((*(u_int32_t *)addr & mask) !=
			    (*(u_int32_t *)pat & mask))
				return FALSE;
			break;
		default:
			return FALSE;
		}
	return TRUE;
#else
	return FALSE;
#endif
}

static gboolean
allow_host(GkrellmdClient *client, struct sockaddr *sa, socklen_t salen)
	{
	GList			*list;
#ifdef HAVE_GETADDRINFO
	int error;
	char hostbuf[NI_MAXHOST], addrbuf[NI_MAXHOST];
#else
	struct hostent	*hostent;
#endif
	gchar			buf[128];
	gchar			*hostname = NULL,
					*addr = NULL;
	gchar			*s, *allowed;

#ifdef HAVE_GETADDRINFO
	error = getnameinfo(sa, salen, addrbuf, sizeof(addrbuf),
			    NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID);
	if (error == 0)
		{
		addr = addrbuf;
		error = getnameinfo(sa, salen, hostbuf, sizeof(hostbuf),
				    NULL, 0, NI_NAMEREQD);
		if (error == 0 &&
		    is_valid_reverse(addrbuf, hostbuf, sa->sa_family))
			hostname = hostbuf;
		}
#else
	hostent = gethostbyaddr((gchar *)&((struct sockaddr_in *)sa)->sin_addr,
				sizeof(struct in_addr), AF_INET);
	if (hostent)
		hostname = hostent->h_name;
	addr = inet_ntoa(((struct sockaddr_in *)sa)->sin_addr);
#endif

	client->hostname = g_strdup(hostname ? hostname : addr);

	if (!allow_host_list)
		return TRUE;

	for (list = allow_host_list; list; list = list->next)
		{
		allowed = (gchar *) list->data;
		if (   (hostname && !strcmp(hostname, allowed))
			|| (addr && !strcmp(addr, allowed))
			|| !strcmp("ALL", allowed)
		   )
			return TRUE;

		if (addr && cidr_match(sa, salen, allowed))
			return TRUE;

		/* Check for simple IPv4 subnet match.  Worry later about ranges and
		|  other hosts_access type patterns.
		*/
		if (   addr
			&& (s = strrchr(allowed, (int) '.')) != NULL
			&& *(s + 1) == '*' && *(s + 2) == '\0'
			&& !strncmp(addr, allowed, (gint) (s - allowed + 1))
		   )
				return TRUE;
		}

	snprintf(buf, sizeof(buf), _("Connection not allowed from %s\n"),
			hostname ? hostname : addr);
	fprintf(stderr, "gkrellmd: %s", buf),
	gkrellmd_send_to_client(client, "<error>\n");
	gkrellmd_send_to_client(client, buf);
	return FALSE;
	}

  /* client sends line: gkrellm x.y.z
  */
static GkrellmdClient *
accept_client(gint fd, struct sockaddr *sa, socklen_t salen)
	{
	GkrellmdClient	*client;
	gchar			buf[64], name[32];
	gboolean		client_limit;
	gint			err;

	client = g_new0(GkrellmdClient, 1);
	client->fd = fd;
	client->alive = TRUE;
	client_limit = (g_list_length(gkrellmd_client_list) >= _GK.max_clients);

	if (!allow_host(client, sa, salen) || client_limit)
		{
		if (client_limit)
			{
			fprintf(stderr, _("gkrellmd: too many clients, rejecting %s\n"),
					client->hostname);
			gkrellmd_send_to_client(client,
						"<error>\nClient limit exceeded.\n");
			}
		g_free(client->hostname);
		g_free(client);
		return NULL;
		}
	err = recv(fd, buf, sizeof(buf), 0);
	if (err > 0)
		buf[err] = '\0';
	else
		buf[0] = '\0';
	//getline(fd, buf, sizeof(buf));

	if (_GK.verbose)
		printf(_("connect string from client: %s\n"), buf);

	if (   sscanf(buf, "%31s %d.%d.%d", name, &client->major_version,
					&client->minor_version, &client->rev_version) == 4
		&& !strcmp(name, "gkrellm")
	   )
		{
		gkrellmd_client_list = g_list_append(gkrellmd_client_list, client);
		return client;
		}
	fprintf(stderr, _("gkrellmd: bad connect line from %s: %s\n"),
				client->hostname, buf);
	gkrellmd_send_to_client(client, "<error>\nBad connect string!");

	g_free(client->hostname);
	g_free(client);
	return NULL;
	}

static void
remove_client(gint fd)
	{
	GList			*list;
	GkrellmdClient	*client;

	for (list = gkrellmd_client_list; list; list = list->next)
		{
		client = (GkrellmdClient *) list->data;
		if (client->fd == fd)
			{
			if (_GK.verbose)
				printf("Removing client %s\n", client->hostname);
			close(fd);
			g_free(client->hostname);
			g_free(client);
			gkrellmd_client_list = g_list_remove(gkrellmd_client_list, client);
			break;
			}
		}
	}

static gint
parse_config(gchar *config, gchar *arg)
	{
	if (!strcmp(config, "clear-hosts") || !strcmp(config, "c"))
		{
		gkrellm_free_glist_and_data(&allow_host_list);
		return 0;
		}
	if (!strcmp(config, "detach") || !strcmp(config, "d"))
		{
		detach_flag = TRUE;
		return 0;
		}
	if (!arg || !*arg)
		return -1;
	if (!strcmp(config, "update-hz") || !strcmp(config, "u"))
		_GK.update_HZ = atoi(arg);
	else if (!strcmp(config, "port") || !strcmp(config, "P"))
		_GK.server_port = atoi(arg);
	else if (!strcmp(config, "max-clients") || !strcmp(config, "m"))
		_GK.max_clients = atoi(arg);
	else if (!strcmp(config, "allow-host") || !strcmp(config, "a"))
		allow_host_list = g_list_append(allow_host_list, g_strdup(arg));
	else if (!strcmp(config, "plugin-enable") || !strcmp(config, "pe"))
		gkrellmd_plugin_enable_list
				= g_list_append(gkrellmd_plugin_enable_list, g_strdup(arg));
	else if (!strcmp(config, "plugin") || !strcmp(config, "p"))
		_GK.command_line_plugin = g_strdup(arg);
	else if (!strcmp(config, "io-timeout"))
		_GK.io_timeout = atoi(arg);
	else if (!strcmp(config, "reconnect-timeout"))
		_GK.reconnect_timeout = atoi(arg);
	else if (!strcmp(config, "fs-interval"))
		_GK.fs_interval = atoi(arg);
	else if (!strcmp(config, "nfs-interval"))
		_GK.nfs_interval = atoi(arg);
	else if (!strcmp(config, "inet-interval"))
		_GK.inet_interval = atoi(arg);
	else if (!strcmp(config, "mbmon-port"))
		_GK.mbmon_port = atoi(arg);
#if !defined(WIN32)
	else if (!strcmp(config, "pidfile"))
		_GK.pidfile = g_strdup(arg);
	else if (!strcmp(config, "mailbox"))
		gkrellmd_add_mailbox(arg);
	else if (!strcmp(config, "user") || !strcmp(config, "U"))
		{
		struct passwd *tmp;

		if ((tmp = getpwnam(arg)) != (struct passwd*) 0)
			drop_privs.uid = tmp->pw_uid;
		else
			return -1;
		}
	else if (!strcmp(config, "group") || !strcmp(config, "G"))
		{
		struct group *tmp;

		if ((tmp = getgrnam(arg)) != (struct group*) 0)
			drop_privs.gid = tmp->gr_gid;
		else
			return -1;
		}
#endif
	else
		return -1;
	return 1;
	}

static void
load_config(gchar *path)
	{
	FILE			*f;
	PluginConfigRec	*cfg;
	gchar			buf[128], config[32], arg[128];
	gchar			*s, *plugin_config_block = NULL;

	f = fopen(path, "r");
	if (!f)
		return;
	while (fgets(buf, sizeof(buf), f))
		{
		if (!buf[0] || buf[0] == '#')
			continue;
		if (buf[0] == '[' || buf[0] == '<')
			{
			if (buf[1] == '/')
				{
				g_free(plugin_config_block);
				plugin_config_block = NULL;
				}
			else
				{
				if (   (s = strchr(buf, ']')) != NULL
					|| (s = strchr(buf, '>')) != NULL
				   )
					*s = '\0';
				plugin_config_block = g_strdup(&buf[1]);
				}
			continue;
			}
		if (plugin_config_block)
			{
			cfg = g_new0(PluginConfigRec, 1);
			cfg->name = g_strdup(plugin_config_block);
			if ((s = strchr(buf, '\n')) != NULL)
				*s = '\0';
			cfg->line = g_strdup(buf);
			gkrellmd_plugin_config_list
						= g_list_append(gkrellmd_plugin_config_list, cfg);
			}
		else	/* main gkrellmd config line */
			{
			arg[0] = '\0';
			sscanf(buf, "%31s %127s", config, arg);
			parse_config(config, arg);
			}
		}
	fclose(f);
	}

const gchar *
gkrellmd_config_getline(GkrellmdMonitor *mon)
	{
	GList			*list;
	PluginConfigRec	*cfg;

	if (!mon->privat)
		{
		mon->privat = g_new0(GkrellmdMonitorPrivate, 1);
		mon->privat->config_list = gkrellmd_plugin_config_list;
		}

	for (list = mon->privat->config_list; list; list = list->next)
		{
		cfg = (PluginConfigRec *) list->data;
		if (!strcmp(cfg->name, mon->name))
			{
			mon->privat->config_list = list->next;
			return cfg->line;
			}
		}
	return NULL;
	}

static void
read_config(void)
	{
	gchar	*path;

	_GK.update_HZ = 3;
	_GK.max_clients = 2;
	_GK.server_port = GKRELLMD_SERVER_PORT;

	_GK.fs_interval = 2;
	_GK.nfs_interval = 16;
	_GK.inet_interval = 1;

#if defined(GKRELLMD_SYS_ETC)
	path = g_strdup_printf("%s/%s", GKRELLMD_SYS_ETC, GKRELLMD_CONFIG);
	load_config(path);
	g_free(path);
#endif

#if defined(GKRELLMD_LOCAL_ETC)
	path = g_strdup_printf("%s/%s", GKRELLMD_LOCAL_ETC, GKRELLMD_CONFIG);
	load_config(path);
	g_free(path);
#endif

	_GK.homedir = (gchar *) g_get_home_dir();
	if (!_GK.homedir)
		_GK.homedir = ".";
#if !defined(WIN32)
	path = g_strdup_printf("%s/.%s", _GK.homedir, GKRELLMD_CONFIG);
#else
	path = g_strdup_printf("%s/%s", _GK.homedir, GKRELLMD_CONFIG);
#endif
	load_config(path);
	g_free(path);
	}

static void
usage(void)
	{
	printf(_("usage: gkrellmd [options]\n"));
	printf(_("options:\n"));
	printf(_("   -u, --update-hz F         Monitor update frequency\n"));
	printf(_("   -m, --max-clients N       Number of simultaneous clients\n"));
	printf(_("   -P, --port P              Server port to listen on\n"));
	printf(_("   -a, --allow-host host     Allow connections from specified hosts\n"));
	printf(_("   -c, --clear-hosts         Clears the current list of allowed hosts\n"));
	printf(_("       --io-timeout N        Close connection after N seconds of no I/O\n"));
	printf(_("       --reconnect-timeout N Try to connect every N seconds after\n"
	         "                             a disconnect\n"));
#if !defined(WIN32)
	printf(_("       --mailbox path        Send local mailbox counts to gkrellm clients.\n"));
	printf(_("   -d, --detach              Run in background and detach from terminal\n"));
	printf(_("   -U, --user username       Change to this username after startup\n"));
	printf(_("   -G, --group groupname     Change to this group after startup\n"));
#endif
	printf(_("   -p,  --plugin name        Enable a command line plugin\n"));
	printf(_("   -pe, --plugin-enable name Enable an installed plugin\n"));
	printf(_("       --plist               List plugins and exit\n"));
	printf(_("       --plog                Print plugin install log\n"));
	printf(_("   -V, --verbose\n"));
	printf(_("   -v, --version\n"));
	}

static void
get_args(gint argc, gchar **argv)
	{
	gchar	*opt, *arg;
	gint	i, r;

	for (i = 1; i < argc; ++i)
		{
		opt = argv[i];
		arg = argv[i + 1];
		if (*opt == '-')
			{
			++opt;
			if (*opt == '-')
				++opt;
			}
		if (!strcmp(opt, "verbose") || !strcmp(opt, "V"))
			{
			_GK.verbose += 1;
			continue;
			}
		if (!strcmp(opt, "plist"))
			{
			_GK.list_plugins = TRUE;
			continue;
			}
		if (!strcmp(opt, "plog"))
			{
			_GK.log_plugins = TRUE;
			continue;
			}
		else if (!strcmp(opt, "help") || !strcmp(opt, "h"))
			{
			usage();
			exit(0);
			}
		else if (!strcmp(opt, "version") || !strcmp(opt, "v"))
			{
			printf("gkrellmd %d.%d.%d%s\n", GKRELLMD_VERSION_MAJOR,
					GKRELLMD_VERSION_MINOR, GKRELLMD_VERSION_REV,
					GKRELLMD_EXTRAVERSION);
			exit(0);
			}
		else if (   i < argc
				 && ((r = parse_config(opt, (i < argc - 1) ? arg : NULL)) >= 0)
				)
			{
			i += r;
			continue;
			}

		printf(_("Bad arg: %s\n"), argv[i]);
		usage();
		exit(0);
		}
	}

static int *
socksetup(int af)
	{
	struct addrinfo	hints, *res, *r;
	gint			maxs, *s, *socks;
#ifndef HAVE_GETADDRINFO
	struct sockaddr_in	sin;
#else
	gchar			portnumber[6];
	gint			error;
#endif

	memset(&hints, 0, sizeof(hints));
	hints.ai_socktype = SOCK_STREAM;
#ifdef HAVE_GETADDRINFO
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = af;
	snprintf(portnumber, sizeof(portnumber), "%d", _GK.server_port);
	error = getaddrinfo(NULL, portnumber, &hints, &res);
	if (error)
		{
		fprintf(stderr, "gkrellmd %s\n", gai_strerror(error));
		return NULL;
		}
#else
	/* Set up the address structure for the listen socket and bind the
	|  listen address to the socket.
	*/
	hints.ai_family = PF_INET;
	hints.ai_addrlen = sizeof(struct sockaddr_in);
	hints.ai_next = NULL;
	hints.ai_addr = (struct sockaddr *) &sin;
	sin.sin_family = PF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons(_GK.server_port);
	res = &hints;
#endif

	/* count max number of sockets we may open */
	for (maxs = 0, r = res; r; r = r->ai_next, maxs++)
		;
	socks = malloc((maxs + 1) * sizeof(int));
	if (!socks)
		{
		fprintf(stderr,
			"gkrellmd couldn't allocate memory for sockets\n");
		return NULL;
		}

	*socks = 0;   /* num of sockets counter at start of array */
	s = socks + 1;
	for (r = res; r; r = r->ai_next)
		{
		*s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
		if (*s < 0)
			continue;

		/* SO_REUSEADDR flag allows the server to restart immediately
		*/
		if (1)
			{
			const int on = 1;

			if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
				{
				fprintf(stderr, "gkrellmd setsockopt (SO_REUSEADDR) failed\n");
				close(*s);
				continue;
				}
			}

#ifdef IPV6_V6ONLY
		if (r->ai_family == AF_INET6)
			{
			const int on = 1;

			if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
				{
				fprintf(stderr, "gkrellmd setsockopt (IPV6_V6ONLY) failed\n");
				close(*s);
				continue;
				}
			}
#endif
		if (bind(*s, r->ai_addr, r->ai_addrlen) < 0)
			{
			close(*s);
			continue;
			}
		(*socks)++;
		s++;
	}

#ifdef HAVE_GETADDRINFO
	if (res)
		freeaddrinfo(res);
#endif

	if (*socks == 0)
		{
		fprintf(stderr, "gkrellmd couldn't bind to any socket\n");
		free(socks);
		return NULL;
		}
	return socks;
	}

/* XXX: Recent glibc seems to have daemon(), too. */
#if defined(BSD4_4)
#define HAVE_DAEMON
#endif

#if !defined(HAVE_DAEMON) && !defined(WIN32) && !defined(__solaris__)
#include <paths.h>
#endif

#if !defined(_PATH_DEVNULL)
#define _PATH_DEVNULL	"/dev/null"
#endif

static void
detach_from_terminal(void)
	{
#if !defined(WIN32)
#if !defined(HAVE_DAEMON)
	gint	i, fd;
#endif

	if (getppid() == 1)	 /* already a daemon */
		return;

#if defined(HAVE_DAEMON)
	if (daemon(0, 0))
		{
		fprintf(stderr, "gkrellmd detach failed: %s\n", strerror(errno));
		remove_pidfile();
		exit(1);
		}
#else
	i = fork();
	if (i > 0)
		exit(0);

	if (i < 0 || setsid() == -1)		/* new session process group */
		{
		fprintf(stderr, "gkrellmd detach failed: %s\n", strerror(errno));
		remove_pidfile();
		exit(1);
		}

	if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1)
		{
		dup2(fd, STDIN_FILENO);
		dup2(fd, STDOUT_FILENO);
		dup2(fd, STDERR_FILENO);
		if (fd > 2)
			close(fd);
		}
	chdir("/");
#endif

//	signal(SIGCHLD, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
#if !defined(MSG_NOSIGNAL)
	signal(SIGPIPE, SIG_IGN);
#endif
#endif
	}



static void
drop_privileges(void)
	{
#if !defined(WIN32)
	if (drop_privs.gid > (uid_t)0)
		{
		(void) setgroups((size_t)0, (gid_t*)0);
		(void) setgid(drop_privs.gid);
		}
	if (drop_privs.uid > (uid_t)0)
		(void) setuid(drop_privs.uid);
#endif
	}


#if defined(WIN32) && defined(_WINDOWS)
int APIENTRY WinMain(HINSTANCE	hInstance,
					HINSTANCE	hPrevInstance,
					LPSTR		lpCmdLine,
					int			nCmdShow)
	{
	int		argc;
	gchar	**argv;
	gchar	*cmd;

#else
gint
main(gint argc, gchar **argv)
	{
#endif
#ifdef HAVE_GETADDRINFO
	struct sockaddr_storage client_addr;
#else
	struct sockaddr_in	client_addr;
#endif
	fd_set				read_fds, test_fds;
	struct timeval		tv;
	GkrellmdClient		*client;
	size_t				addr_len;
	gint				fd, server_fd, client_fd, nbytes, i;
	gint				max_fd = -1;
	gint				listen_fds = 0;
	gint				interval, result;

#if defined(WIN32) && defined(_WINDOWS)
    setupServerWindow(hInstance);

    // need to get exe too
	cmd = GetCommandLine();
	g_shell_parse_argv(cmd, &argc, &argv, NULL);
#endif

#ifdef ENABLE_NLS
#ifdef LOCALEDIR
	bindtextdomain(PACKAGE_D, LOCALEDIR);
#endif
	textdomain(PACKAGE_D);
	bind_textdomain_codeset(PACKAGE_D, "UTF-8");
#endif	/* ENABLE_NLS */

	read_config();
	get_args(argc, argv);

	if (_GK.verbose)
		printf("update_HZ=%d\n", _GK.update_HZ);
	if (detach_flag && !_GK.log_plugins && !_GK.list_plugins)
		detach_from_terminal();
	else
		{
		signal(SIGTERM, cb_sigterm);
		signal(SIGQUIT, cb_sigterm);
		signal(SIGINT, cb_sigterm);
		signal(SIGTSTP, SIG_IGN);
		}

	make_pidfile();

	gkrellm_sys_main_init();
	drop_privileges();

#if GLIB_CHECK_VERSION(2,0,0)
	g_thread_init(NULL);
#endif

	_GK.start_time = time(0);
	if (_GK.update_HZ < 1 || _GK.update_HZ > 10)
		_GK.update_HZ = 3;
	if (_GK.fs_interval < 1 || _GK.fs_interval > 1000)
		_GK.fs_interval = 2;
	if (_GK.nfs_interval > 10000)
		_GK.nfs_interval = 16;
	if (_GK.inet_interval > 20)
		_GK.inet_interval = 20;

	gkrellmd_load_monitors();

	_GK.server_fd = socksetup(PF_UNSPEC);
	if (_GK.server_fd == NULL)
		{
		fprintf(stderr, "gkrellmd socket() failed: %s\n", strerror(errno));
		remove_pidfile();
		exit(1);
		}

	/* Listen on the socket so a client gkrellm can connect.
	*/
	FD_ZERO(&read_fds);
	for (i = 1; i <= _GK.server_fd[0]; ++i)
		{
		if (listen(_GK.server_fd[i], 5) == -1)
			{
			close(_GK.server_fd[i]);
			continue;
			}
		++listen_fds;
		FD_SET(_GK.server_fd[i], &read_fds);
		if (max_fd < _GK.server_fd[i])
			max_fd = _GK.server_fd[i];
		}
	if (listen_fds <= 0)
		{
		fprintf(stderr, "gkrellmd listen() failed: %s\n", strerror(errno));
		remove_pidfile();
		exit(1);
		}

	interval = 1000000 / _GK.update_HZ;

#if defined(WIN32) && defined(_WINDOWS)
    while (notDone == 0)
        {
            {
                MSG msg;
                while (PeekMessage(&msg, (HWND) NULL, 0, 0, PM_REMOVE)) {
                    TranslateMessage(&msg); 
                    DispatchMessage(&msg); 
                }
            }
#else
    while(1)
		{
#endif
		test_fds = read_fds;
#ifdef HAVE_GETADDRINFO
		addr_len = sizeof(struct sockaddr_storage);
#else
		addr_len = sizeof(struct sockaddr_in);
#endif
		tv.tv_usec = interval;
		tv.tv_sec = 0;
		if ((result = select(max_fd + 1, &test_fds, NULL, NULL, &tv) == -1))
			{
			if (errno == EINTR)
				continue;
			fprintf(stderr, "gkrellmd select() failed: %s\n", strerror(errno));
			remove_pidfile();
			exit(1);
			}
#if 0	/* BUG, result is 0 when test_fds has a set fd!! */
		if (result == 0)	
			{
			gkrellmd_update_monitors();
			continue;
			}
#endif
		for (fd = 0; fd <= max_fd; ++fd)
			{
			if (!FD_ISSET(fd, &test_fds))
				continue;
			server_fd = -1;
			for (i = 1; i <= _GK.server_fd[0]; ++i)
				if (fd == _GK.server_fd[i])
					{
					server_fd = fd;
					break;
					}
			if (server_fd >= 0)
				{
				client_fd = accept(server_fd,
						(struct sockaddr *) &client_addr,
						(socklen_t *) &addr_len);
				if (client_fd == -1)
					{
					fprintf(stderr, "gkrellmd accept() failed: %s\n",
							strerror(errno));
					remove_pidfile();
					exit(1);
					}
				if (client_fd > max_fd)
					max_fd = client_fd;
				client = accept_client(client_fd,
							(struct sockaddr *)&client_addr, addr_len);
				if (!client)
					{
					close(client_fd);
					continue;
					}
				FD_SET(client_fd, &read_fds);
				gkrellmd_serve_setup(client);
				if (_GK.verbose)
					printf("gkrellmd accepted client: %s:%u\n",
						client->hostname,
						ntohs(((struct sockaddr_in *)&client_addr)->sin_port));
				}
			else
				{
				ioctl(fd, FIONREAD, &nbytes);
				if (nbytes == 0)
					{
					remove_client(fd);
					FD_CLR(fd, &read_fds);
					}
				else
					gkrellmd_client_read(fd, nbytes);
				}
			}
		gkrellmd_update_monitors();
		}
	return 0;
	}
