/* upsd.c - watches ups state files and answers queries 

   Copyright (C) 1999  Russell Kroll <rkroll@exploits.org>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public 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.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include "upsd.h"
#include "upstype.h"
#include "conf.h"

#include "netcmds.h"
#include "upsconf.h"

#include <sys/un.h>
#include <sys/socket.h>
#include <netdb.h>

#ifdef HAVE_SSL
#include <openssl/err.h>
#include <openssl/ssl.h>
#endif

#include "user.h"
#include "access.h"
#include "ctype.h"
#include "stype.h"
#include "ssl.h"
#include "sstate.h"
#include "desc.h"
#include "neterr.h"

	/* externally-visible settings and pointers */

	upstype_t	*firstups = NULL;

	/* default 15 seconds before data is marked stale */
	int	maxage = 15;

	/* preloaded to STATEPATH in main, can be overridden via upsd.conf */
	char	*statepath = NULL;

	/* preloaded to DATADIR in main, can be overridden via upsd.conf */
	char	*datapath = NULL;

	/* everything else */

static ctype_t	*firstclient = NULL;

	/* default is to listen on all local interfaces */
static stype_t	*firstaddr = NULL;

#ifdef	HAVE_IPV6
static int 	opt_af = AF_UNSPEC;
#endif

	/* signal handlers */
static struct sigaction	sa;
static sigset_t	nut_upsd_sigmask;

	/* pid file */
static	char	pidfn[SMALLBUF];

	/* set by signal handlers */
static	int	reload_flag = 0, exit_flag = 0;

#ifdef	HAVE_IPV6
static const char *inet_ntopW (struct sockaddr_storage *s) {
	static char str[40];

	switch (s->ss_family) {
	case AF_INET:
		return inet_ntop (AF_INET, &(((struct sockaddr_in *)s)->sin_addr), str, 16);
	case AF_INET6:
		return inet_ntop (AF_INET6, &(((struct sockaddr_in6 *)s)->sin6_addr), str, 40);
	default:
		errno = EAFNOSUPPORT;
		return NULL;
	}
}
#endif

/* return a pointer to the named ups if possible */
upstype_t *get_ups_ptr(const char *name)
{
	upstype_t	*tmp;

	if (!name) {
		return NULL;
	}

	for (tmp = firstups; tmp != NULL; tmp = tmp->next) {
		if (!strcasecmp(tmp->name, name)) {
			return tmp;
		}
	}

	return NULL;
}

/* mark the data stale if this is new, otherwise cleanup any remaining junk */
static void ups_data_stale(upstype_t *ups)
{
	/* don't complain again if it's already known to be stale */
	if (ups->stale == 1) {
		return;
	}

	ups->stale = 1;

	upslogx(LOG_NOTICE, "Data for UPS [%s] is stale - check driver", ups->name);
}

/* mark the data ok if this is new, otherwise do nothing */
static void ups_data_ok(upstype_t *ups)
{
	if (ups->stale == 0) {
		return;
	}

	upslogx(LOG_NOTICE, "UPS [%s] data is no longer stale", ups->name);
	ups->stale = 0;
}

/* make sure this UPS is connected and has fresh data */
static void check_ups(upstype_t *ups)
{
	/* sanity checks */
	if ((!ups) || (!ups->fn)) {
		return;
	}

	/* see if we need to (re)connect to the socket */
	if (ups->sock_fd == -1) {
		ups->sock_fd = sstate_connect(ups);
	}

	/* throw some warnings if it's not feeding us data any more */
	if (sstate_dead(ups, maxage)) {
		ups_data_stale(ups);
	} else {
		ups_data_ok(ups);
	}
}

/* add another listening address */
void listen_add(const char *addr, const char *port)
{
	stype_t	*stmp, *last;

	/* don't change listening addresses on reload */
	if (reload_flag) {
		return;
	}

	stmp = last = firstaddr;

	/* find end of linked list */
	while (stmp != NULL) {
		last = stmp;
		stmp = stmp->next;
	}

	/* grab some memory and add the info */
	stmp = xmalloc(sizeof(stype_t));
	stmp->addr = xstrdup(addr);
	stmp->port = xstrdup(port);
	stmp->sock_fd = -1;
	stmp->next = NULL;

	if (last == NULL) {
		firstaddr = stmp;
	} else {
		last->next = stmp;
	}

	upsdebugx(3, "listen_add: added %s:%s", stmp->addr, stmp->port);
}

/* create a listening socket for tcp connections */
static void setuptcp(stype_t *serv)
{
#ifndef	HAVE_IPV6
	struct hostent		*host;
	struct sockaddr_in	server;
	int	res, one = 1;

	host = gethostbyname(serv->addr);

	if (host == NULL) {
		struct  in_addr	listenaddr;

		if (!inet_aton(serv->addr, &listenaddr)) {
			fatal_with_errno(EXIT_FAILURE, "inet_aton");
		}

		host = gethostbyaddr(&listenaddr, sizeof(listenaddr), AF_INET);

		if (host == NULL) {
			fatal_with_errno(EXIT_FAILURE, "gethostbyaddr");
		}
	}

	if ((serv->sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		fatal_with_errno(EXIT_FAILURE, "socket");
	}

	res = setsockopt(serv->sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *) &one, sizeof(one));

	if (res != 0) {
		fatal_with_errno(EXIT_FAILURE, "setsockopt(SO_REUSEADDR)");
	}

	memset(&server, '\0', sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(atoi(serv->port));

	memcpy(&server.sin_addr, host->h_addr, host->h_length);

	if (bind(serv->sock_fd, (struct sockaddr *) &server, sizeof(server)) == -1) {
		fatal_with_errno(EXIT_FAILURE, "Can't bind TCP port %s", serv->port);
	}

	if ((res = fcntl(serv->sock_fd, F_GETFL, 0)) == -1) {
		fatal_with_errno(EXIT_FAILURE, "fcntl(get)");
	}

	if (fcntl(serv->sock_fd, F_SETFL, res | O_NDELAY) == -1) {
		fatal_with_errno(EXIT_FAILURE, "fcntl(set)");
	}

	if (listen(serv->sock_fd, 16)) {
		fatal_with_errno(EXIT_FAILURE, "listen");
	}
#else
	struct addrinfo		hints, *res, *ai;
	int	v = 0, one = 1;

	upsdebugx(3, "setuptcp: try to bind to %s port %s", serv->addr, serv->port);

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags		= AI_PASSIVE;
	hints.ai_family		= opt_af;
	hints.ai_socktype	= SOCK_STREAM;
	hints.ai_protocol	= IPPROTO_TCP;

        if ((v = getaddrinfo(serv->addr, serv->port, &hints, &res)) != 0) {
		if (v == EAI_SYSTEM) {
                        fatal_with_errno(EXIT_FAILURE, "getaddrinfo");
		}

                fatalx(EXIT_FAILURE, "getaddrinfo: %s", gai_strerror(v));
        }

       for (ai = res; ai != NULL; ai = ai->ai_next) {
		int sock_fd;

		if ((sock_fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
			upsdebug_with_errno(3, "setuptcp: socket");
			continue;
		}
		
		if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)) != 0) {
			fatal_with_errno(EXIT_FAILURE, "setuptcp: setsockopt");
		}

		if (bind(sock_fd, ai->ai_addr, ai->ai_addrlen) < 0) {
			upsdebug_with_errno(3, "setuptcp: bind");
			close(sock_fd);
			continue;
		}

		if ((v = fcntl(sock_fd, F_GETFL, 0)) == -1) {
			fatal_with_errno(EXIT_FAILURE, "setuptcp: fcntl(get)");
		}

		if (fcntl(sock_fd, F_SETFL, v | O_NDELAY) == -1) {
			fatal_with_errno(EXIT_FAILURE, "setuptcp: fcntl(set)");
		}

		if (listen(sock_fd, 16) < 0) {
			upsdebug_with_errno(3, "setuptcp: listen");
			close(sock_fd);
			continue;
		}

		serv->sock_fd = sock_fd;
		break;
	}

	freeaddrinfo(res);
#endif

	/* don't fail silently */
	if (serv->sock_fd < 0) {
		fatalx(EXIT_FAILURE, "not listening on %s port %s", serv->addr, serv->port);
	} else {
		upslogx(LOG_INFO, "listening on %s port %s", serv->addr, serv->port);
	}

	return;
}

/* decrement the login counter for this ups */
static void declogins(const char *upsname)
{
	upstype_t	*ups;

	ups = get_ups_ptr(upsname);

	if (ups == NULL) {
		upslogx(LOG_INFO, "Tried to decrement invalid ups name (%s)", upsname);
		return;
	}

	ups->numlogins--;

	if (ups->numlogins < 0) {
		upslogx(LOG_ERR, "Programming error: UPS [%s] has numlogins=%d", ups->name, ups->numlogins);
	}
}

/* disconnect a client connection and free all related memory */
static void delclient(ctype_t *dclient)
{
	ctype_t	*tmp, *last;

	if (dclient == NULL) {
		return;
	}

	last = NULL;
	tmp = firstclient;

	while (tmp != NULL) {
		if (tmp == dclient) {		/* found it */
			shutdown(dclient->fd, 2);
			close(dclient->fd);

			free(tmp->addr);

			if (tmp->loginups != NULL) {
				declogins(tmp->loginups);
				free(tmp->loginups);
			}

			free(tmp->password);

#ifdef HAVE_SSL
			if (tmp->ssl) {
				SSL_free(tmp->ssl);
			}
#endif

			pconf_finish(&tmp->ctx);

			if (last == NULL) {	/* deleting first entry */
				firstclient = tmp->next;
			} else {
				last->next = tmp->next;
			}

			free(tmp);
			return;
		}

		last = tmp;
		tmp = tmp->next;
	}

	/* not found?! */
	upslogx(LOG_WARNING, "Tried to delete client struct that doesn't exist!");

	return;
}

/* send the buffer <sendbuf> of length <sendlen> to host <dest> */
int sendback(ctype_t *client, const char *fmt, ...)
{
	int	res, len;
	char ans[NUT_NET_ANSWER_MAX+1];
	va_list ap;

	if (!client) {
		return 0;
	}

	if (client->delete) {
		return 0;
	}

	va_start(ap, fmt);
	vsnprintf(ans, sizeof(ans), fmt, ap);
	va_end(ap);

	len = strlen(ans);

	if (client->ssl) {
		res = ssl_write(client, ans, len);
	} else {
		res = write(client->fd, ans, len);
	}

	upsdebugx(2, "write: [destfd=%d] [len=%d] [%s]", client->fd, len, rtrim(ans, '\n'));

	if (len != res) {
		upslog_with_errno(LOG_NOTICE, "write() failed for %s", client->addr);
		client->delete = 1;
		return 0;	/* failed */
	}

	return 1;	/* OK */
}

/* just a simple wrapper for now */
int send_err(ctype_t *client, const char *errtype)
{
	if (!client) {
		return -1;
	}

	upsdebugx(4, "Sending error [%s] to client %s", errtype, client->addr);

	return sendback(client, "ERR %s\n", errtype);
}

/* disconnect anyone logged into this UPS */
void kick_login_clients(const char *upsname)
{
	ctype_t   *tmp, *next;

	tmp = firstclient;

	while (tmp) {
		next = tmp->next;

		/* if it's not logged in, don't check it */
		if (!tmp->loginups) {
			tmp = next;
			continue;
		}

		if (!strcmp(tmp->loginups, upsname)) {
			upslogx(LOG_INFO, "Kicking client %s (was on UPS [%s])\n", tmp->addr, upsname);
			delclient(tmp);
		}

		tmp = next;
	}
}

/* make sure a UPS is sane - connected, with fresh data */
int ups_available(const upstype_t *ups, ctype_t *client)
{
	if (ups->sock_fd == -1) {
		send_err(client, NUT_ERR_DRIVER_NOT_CONNECTED);
		return 0;
	}

	if (ups->stale) {
		send_err(client, NUT_ERR_DATA_STALE);
		return 0;
	}

	/* must be OK */
	return 1;
}

/* check flags and access for an incoming command from the network */
static void check_command(int cmdnum, ctype_t *client, int numarg, 
	const char **arg)
{
	if (netcmds[cmdnum].flags & FLAG_USER) {
		if (!client->username) {
			send_err(client, NUT_ERR_USERNAME_REQUIRED);
			return;
		}

		if (!client->password) {
			send_err(client, NUT_ERR_PASSWORD_REQUIRED);
			return;
		}
	}

	/* looks good - call the command */
	netcmds[cmdnum].func(client, numarg - 1, &arg[1]);
}

/* parse requests from the network */
static void parse_net(ctype_t *client)
{
	int	i;

	/* see if this client is still allowed to talk to us */
	if (!access_check(&client->sock)) {
		send_err(client, NUT_ERR_ACCESS_DENIED);
		client->delete = 1;
		return;
	}

	/* paranoia */
	client->rq[client->rqpos] = '\0';

	/* by this point we should always have a usable line */
	if (pconf_line(&client->ctx, client->rq) != 1) {

		/* shouldn't happen */
		upslogx(LOG_WARNING, "pconf_line couldn't handle the input");
		send_err(client, NUT_ERR_UNKNOWN_COMMAND);
		return;
	}

	if (pconf_parse_error(&client->ctx)) {
		upsdebugx(4, "parse error on net read");
		send_err(client, NUT_ERR_UNKNOWN_COMMAND);
		return;
	}

	/* shouldn't happen */
	if (client->ctx.numargs < 1) {
		send_err(client, NUT_ERR_UNKNOWN_COMMAND);
		return;
	}

	for (i = 0; netcmds[i].name != NULL; i++) {
		if (!strcasecmp(netcmds[i].name, client->ctx.arglist[0])) {
			check_command(i, client, client->ctx.numargs, 
			(const char **) client->ctx.arglist);

			return;
		}
	}

	/* fallthrough = not matched by any entry in netcmds */

	send_err(client, NUT_ERR_UNKNOWN_COMMAND);
}

/* scan the list of UPSes for sanity */
static void check_every_ups(void)
{
	upstype_t *ups;

	ups = firstups;

	while (ups != NULL) {
		check_ups(ups);
		ups = ups->next;
	}
}

/* answer incoming tcp connections */
static void answertcp(stype_t *serv)
#ifndef	HAVE_IPV6
{
	struct	sockaddr_in csock;
#else
{
	struct	sockaddr_storage csock;
#endif
	int	acc;
	ctype_t	*tmp, *last;
	socklen_t	clen;

	clen = sizeof(csock);
	acc = accept(serv->sock_fd, (struct sockaddr *) &csock, &clen);

	if (acc < 0) {
		return;
	}

	if (!access_check(&csock)) {
		upslogx(LOG_NOTICE, "Rejecting TCP connection from %s", 
#ifndef	HAVE_IPV6
	 		inet_ntoa(csock.sin_addr));
#else
			inet_ntopW(&csock));
#endif
		shutdown(acc, shutdown_how);
		close(acc);
		return;
	}

	last = tmp = firstclient;

	while (tmp != NULL) {
		last = tmp;
		tmp = tmp->next;
	}

	tmp = xmalloc(sizeof(ctype_t));

	tmp->fd = acc;
	tmp->delete = 0;

#ifndef	HAVE_IPV6
	tmp->addr = xstrdup(inet_ntoa(csock.sin_addr));
	memcpy(&tmp->sock, &csock, sizeof(struct sockaddr_in));
#else
	tmp->addr = xstrdup(inet_ntopW(&csock));
	memcpy(&tmp->sock, &csock, sizeof(struct sockaddr_storage));
#endif

	tmp->rqpos = 0;
	memset(tmp->rq, '\0', sizeof(tmp->rq));

	pconf_init(&tmp->ctx, NULL);

	tmp->loginups = NULL;		/* for upsmon */
	tmp->username = NULL;
	tmp->password = NULL;

	tmp->ssl = NULL;
	tmp->ssl_connected = 0;

	tmp->next = NULL;

	if (last == NULL) {
 		firstclient = tmp;
	} else {
		last->next = tmp;
	}

	upslogx(LOG_DEBUG, "Connection from %s", tmp->addr);
}

/* read tcp messages and handle them */
static void readtcp(ctype_t *client)
{
	char	buf[SMALLBUF];
	int	i, ret;

	memset(buf, '\0', sizeof(buf));

	if (client->ssl) {
		ret = ssl_read(client, buf, sizeof(buf));
	} else {
		ret = read(client->fd, buf, sizeof(buf));
	}

	if (ret < 1) {
		upslogx(LOG_INFO, "Host %s disconnected (read failure)", client->addr);
		delclient(client);
		return;
	}

	/* if an overflow will happen, then clear the queue */
	if ((ret + client->rqpos) >= sizeof(client->rq)) {
		memset(client->rq, '\0', sizeof(client->rq));
		client->rqpos = 0;
	}

	/* fragment handling code */

	for (i = 0; i < ret; i++) {
		/* add to the receive queue one by one */
		client->rq[client->rqpos++] = buf[i];

		/* parse on linefeed ("blah blah\n") */
		if (buf[i] == 10) {	/* LF */
			parse_net(client);

			/* bail out if the connection closed in parse_net */
			if (client->delete) {
				delclient(client);
				return;
			}
			
			/* reset queue */
			client->rqpos = 0;
			memset(client->rq, '\0', sizeof(client->rq));
		}
	}

	return;
}

void server_load(void)
{
	stype_t	*serv;

	/* default behaviour if no LISTEN addres has been specified */
	if (firstaddr == NULL) {
		listen_add("0.0.0.0", string_const(PORT));
	}

	for (serv = firstaddr; serv != NULL; serv = serv->next) {
		setuptcp(serv);
	}
}

void server_free(void)
{
	stype_t	*stmp, *snext;

	/* cleanup server fds */
	stmp = firstaddr;

	while (stmp) {
		snext = stmp->next;

		if (stmp->sock_fd != -1)
			close(stmp->sock_fd);

		free(stmp->addr);
		free(stmp->port);
		free(stmp);

		stmp = snext;
	}

	firstaddr = NULL;
}

static void upsd_cleanup(void)
{
	ctype_t	*tmpcli, *tmpnext;
	upstype_t	*ups, *unext;

	/* cleanup client fds */
	tmpcli = firstclient;

	while (tmpcli) {
		tmpnext = tmpcli->next;
		delclient(tmpcli);
		tmpcli = tmpnext;
	}

	if (strcmp(pidfn, "") != 0) {
		unlink(pidfn);
	}

	ups = firstups;

	while (ups) {
		unext = ups->next;

		if (ups->sock_fd != -1) {
			close(ups->sock_fd);
		}

		sstate_infofree(ups);
		sstate_cmdfree(ups);
		pconf_finish(&ups->sock_ctx);

		free(ups->fn);
		free(ups->name);
		free(ups->desc);
		free(ups);

		ups = unext;
	}

	/* dump everything */

	acl_free();
	access_free();
	user_flush();
	desc_free();
	server_free();

	free(statepath);
	free(datapath);
	free(certfile);
}

/* service requests and check on new data */
static void mainloop(void)
{
	fd_set	rfds;
	struct	timeval	tv;
	int	res, maxfd = -1;
	ctype_t	*tmpcli, *tmpnext;
	upstype_t	*utmp, *unext;
	stype_t	*stmp, *snext;

	if (reload_flag) {
		conf_reload();
		reload_flag = 0;
	}
	
	tv.tv_sec = 2;
	tv.tv_usec = 0;

	FD_ZERO(&rfds);

	/* scan through servers and add to FD_SET */
	for (stmp = firstaddr; stmp != NULL; stmp = stmp->next) {
		if (stmp->sock_fd != -1) {
			FD_SET(stmp->sock_fd, &rfds);

			if (stmp->sock_fd > maxfd) {
				maxfd = stmp->sock_fd;
			}
		}
	}

	/* scan through clients and add to FD_SET */
	for (tmpcli = firstclient; tmpcli != NULL; tmpcli = tmpcli->next) {
		FD_SET(tmpcli->fd, &rfds);
		if (tmpcli->fd > maxfd) {
			maxfd = tmpcli->fd;
		}
	}

	/* also add new driver sockets */
	for (utmp = firstups; utmp != NULL; utmp = utmp->next) {
		if (utmp->sock_fd != -1) {
			FD_SET(utmp->sock_fd, &rfds);

			if (utmp->sock_fd > maxfd) {
				maxfd = utmp->sock_fd;
			}
		}
	}
	
	res = select(maxfd + 1, &rfds, NULL, NULL, &tv);

	if (res > 0) {
		/* scan servers for activity */
		stmp = firstaddr;

		while (stmp) {
			snext = stmp->next;

			if (stmp->sock_fd != -1) {
				if (FD_ISSET(stmp->sock_fd, &rfds)) {
					answertcp(stmp);
				}
			}

			stmp = snext;
		}
		
		/* scan clients for activity */
		tmpcli = firstclient;

		while (tmpcli != NULL) {

			/* preserve for later since delclient may run */
			tmpnext = tmpcli->next;

			if (FD_ISSET(tmpcli->fd, &rfds)) {
				readtcp(tmpcli);
			}

			tmpcli = tmpnext;
		}

		/* now scan ups sockets for activity */
		utmp = firstups;

		while (utmp) {
			unext = utmp->next;

			if (utmp->sock_fd != -1) {
				if (FD_ISSET(utmp->sock_fd, &rfds)) {
					sstate_sock_read(utmp);
				}

			}

			utmp = unext;
		}
	}

	check_every_ups();
}

static void help(const char *progname) 
{
	printf("Network server for UPS data.\n\n");
	printf("usage: %s [OPTIONS]\n", progname);

	printf("\n");
	printf("  -c <command>	send <command> via signal to background process\n");
	printf("		commands:\n");
	printf("		 - reload: reread configuration files\n");
	printf("		 - stop: stop process and exit\n");
	printf("  -D		raise debugging level\n");
	printf("  -f		stay in the foreground for testing\n");
	printf("  -h		display this help\n");
	printf("  -r <dir>	chroots to <dir>\n");
	printf("  -u <user>	switch to <user> (if started as root)\n");
	printf("  -V		display the version of this software\n");
#ifdef	HAVE_IPV6
	printf("  -4		IPv4 only\n");
	printf("  -6		IPv6 only\n");
#endif

	exit(EXIT_SUCCESS);
}

static void set_reload_flag(int sig)
{
	reload_flag = 1;
}

static void set_exit_flag(int sig)
{
	exit_flag = sig;
}

/* basic signal setup to ignore SIGPIPE */
static void setupsignals(void)
{
	sigemptyset(&nut_upsd_sigmask);
	sa.sa_mask = nut_upsd_sigmask;
	sa.sa_flags = 0;

	sa.sa_handler = SIG_IGN;
	sigaction(SIGPIPE, &sa, NULL);

	/* handle shutdown signals */
	sa.sa_handler = set_exit_flag;
	sigaction(SIGINT, &sa, NULL);
	sigaction(SIGQUIT, &sa, NULL);
	sigaction(SIGTERM, &sa, NULL);

	/* handle reloading */
	sa.sa_handler = set_reload_flag;
	sigaction(SIGHUP, &sa, NULL);
}

void check_perms(const char *fn)
{
	int	ret;
	struct stat	st;

	ret = stat(fn, &st);

	if (ret != 0) {
		fatal_with_errno(EXIT_FAILURE, "stat %s", fn);
	}

	/* include the x bit here in case we check a directory */
	if (st.st_mode & (S_IROTH | S_IXOTH)) {
		upslogx(LOG_WARNING, "%s is world readable", fn);
	}
}	

int main(int argc, char **argv)
{
	int	i, cmd = 0;
	int	do_background = 1;
	char	*progname, *chroot_path = NULL;
	const char	*user = NULL;
	struct passwd	*new_uid = NULL;

	progname = argv[0];

	/* pick up a default from configure --with-user */
	user = RUN_AS_USER;

	/* yes, xstrdup - the conf handlers call free on this later */
	statepath = xstrdup(dflt_statepath());
	datapath = xstrdup(DATADIR);

	/* set up some things for later */
	snprintf(pidfn, sizeof(pidfn), "%s/upsd.pid", altpidpath());

	printf("Network UPS Tools upsd %s\n", UPS_VERSION);

	while ((i = getopt(argc, argv, "+h46p:r:i:fu:Vc:D")) != -1) {
		switch (i) {
			case 'h':
				help(progname);
				break;
			case 'p':
			case 'i':
				fatalx(EXIT_FAILURE, "Specifying a listening addresses with '-i <address>' and '-p <port>'\n"
					"is deprecated. Use 'LISTEN <address> [<port>]' in 'upsd.conf' instead.\n"
					"See 'man 8 upsd.conf' for more information.");
			case 'r':
				chroot_path = optarg;
				break;
			case 'f':
				do_background = 0;
				break;
			case 'u':
				user = optarg;
				break;
			case 'V':

				/* do nothing - we already printed the banner */
				exit(EXIT_SUCCESS);

			case 'c':
				if (!strncmp(optarg, "reload", strlen(optarg)))
					cmd = SIGCMD_RELOAD;
				if (!strncmp(optarg, "stop", strlen(optarg)))
					cmd = SIGCMD_STOP;

				/* bad command given */
				if (cmd == 0)
					help(progname);
				break;

			case 'D':
				do_background = 0;
				nut_debug_level++;
				break;

#ifdef	HAVE_IPV6
		  case '4':
				opt_af = AF_INET;
				break;

		  case '6':
				opt_af = AF_INET6;
				break;
#endif

			default:
				help(progname);
				break;
		}
	}

	if (cmd) {
		sendsignalfn(pidfn, cmd);
		exit(EXIT_SUCCESS);
	}

	argc -= optind;
	argv += optind;

	if (argc != 0) {
		help(progname);
	}

	setupsignals();

	open_syslog("upsd");

	/* send logging to the syslog pre-background for later use */
	syslogbit_set();

	/* do this here, since getpwnam() might not work in the chroot */
	new_uid = get_user_pwent(user);

	if (chroot_path) {
		chroot_start(chroot_path);
	}

	/* handle upsd.conf */
	load_upsdconf(0);	/* 0 = initial */

	/* start server */
	server_load();

	become_user(new_uid);

	if (chdir(statepath)) {
		fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", statepath);
	}

	/* check statepath perms */
	check_perms(statepath);

	/* handle ups.conf */
	read_upsconf();
	upsconf_add(0);		/* 0 = initial */

	if (num_ups == 0) {
		fatalx(EXIT_FAILURE, "Fatal error: at least one UPS must be defined in ups.conf");
	}

	ssl_init();

	/* try to bring in the var/cmd descriptions */
	desc_load();

	/* handle upsd.users */
	user_load();

	if (do_background) {
		background();

		writepid(pidfn);
	} else {
		memset(&pidfn, '\0', sizeof(pidfn));
	}

	while (exit_flag == 0) {
		mainloop();
	}

	upslogx(LOG_INFO, "Signal %d: exiting", exit_flag);
	upsd_cleanup();

	exit(EXIT_SUCCESS);
}


syntax highlighted by Code2HTML, v. 0.9.1