/*	$Id: uptimec.c,v 1.57.2.13 2004/08/18 19:53:33 ola Exp $	*/

/*-
 * Copyright (c) 2003-2004, Ola Eriksson
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the mrEriksson-Network 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 BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#ifndef sbsize_t
#define sbsize_t int32_t
#endif
#ifndef bsize_t
#define bsize_t int64_t
#endif
#include <sys/socket.h>
#include <netdb.h>

/*
 * Figure out how to extract uptime
 */
#undef GETUPTIME_METHOD_FOUND

/* NetBSD, FreeBSD, OpenBSD and MacOS X */
#ifndef GETUPTIME_METHOD_FOUND
#	if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) || \
	   defined(__APPLE__) || defined(__DARWIN__)
#		define GETUPTIME_BSDLIKE
#		define GETUPTIME_METHOD_FOUND
#	endif
#endif

/* Used by most linux-systems */
#ifndef GETUPTIME_METHOD_FOUND
#	ifdef HAVE__PROC_UPTIME
#		define GETUPTIME_PROCUPTIME
#		define GETUPTIME_METHOD_FOUND
#	endif
#endif

/* Solaris, Tru64 and HP/UX */
#ifndef GETUPTIME_METHOD_FOUND
#	if defined(sun) || defined(__hpux) || defined(__hppa) || \
	   defined(HPUX) || defined(hpux)
#		define GETUPTIME_UTMPX
#		define GETUPTIME_METHOD_FOUND
#		ifndef HPUX
#			define HPUX 1
#		endif
#	endif
#endif

/* Tru64 */
#ifndef GETUPTIME_METHOD_FOUND
#	if defined(__osf__) || defined(__digital__)
#		define GETUPTIME_TBLSYSINFO
#		define GETUPTIME_METHOD_FOUND
#	endif
#endif

/* Look for Irix */
#if defined(irix) || defined(sgi) || defined(_sgi) || defined (__sgi)
#	define IRIX
#endif

/* Ultrix & Irix*/
#ifndef GETUPTIME_METHOD_FOUND
#	if defined(ultrix) || defined(IRIX)
#		define GETUPTIME_KMEM
#		define GETUPTIME_METHOD_FOUND
#		if defined(HAVE__VMUNIX)
#			define NLIST_KERNEL "/vmunix"
#		endif
#		if defined(HAVE__UNIX)
#			define NLIST_KERNEL "/unix"
#		endif
#		ifndef NLIST_KERNEL
#			error I could not find the kernel-file to use with nlist
#		endif
#		if defined(IRIX) && defined(HAVE_NLIST64)
#			define NLIST nlist64
#		else
#			define NLIST nlist
#		endif
#	endif
#endif


/* Solve problem with err() not existing everywhere */
#if defined(HAVE_ERR) && defined(HAVE_ERR_H)
#	include <err.h>
#else
#	ifdef __GNUC__
#		define err(a, b, args...) { fprintf(stderr, "uptimec: " b "\n", ##args); exit(a); }
#	else
#		define err(...) { fprintf(stderr, "uptimec: Internal error at line %i\n", __LINE__); do { abort(); } while(0); }
#	endif
#endif

/*
 * Includes used on some systems
 */
/* Pull in select.h if availible */
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#include <sys/types.h>
#endif

/* Figure out if we can use select() on this system */
/* Notes:
   HP/UX does not require select.h to be included, the
   prototype for select() comes from socket.h
   */
#if defined(HAVE_SYS_SELECT_H) || defined(HPUX)
#define USE_SELECT
#endif

/* We need some MD5-functions to use MD5-digested passwords */
#if !defined(HAVE_MD5INIT) || !defined(HAVE_MD5UPDATE) || !defined(HAVE_MD5FINAL)
#define USE_PLAINTEXT_PASSWORD
#endif

/* Figure out where to get usable md5-headers */
#ifndef USE_PLAINTEXT_PASSWORD
#	ifdef HAVE_MD5_H
#		define PASSWORD_USE_MD5
#		include <md5.h>
#	elif defined(HAVE_OPENSSL_MD5_H)
#		define PASSWORD_USE_MD5
#		include <openssl/md5.h>
#	else
#		define USE_PLAINTEXT_PASSWORD
#	endif /* HAVE_MD5_H */
#endif /* ! USE_PLAINTEXT_PASSWORD */

/* We need syslog.h and the syslog()-function to send messages to syslog */
#if defined(HAVE_SYSLOG_H) && defined(HAVE_SYSLOG)
#	include <syslog.h>
#	define DO_SYSLOG
#endif

/*
 * Pull in include-files based on OS
 */

/* Used by some of the BSD-like systems to extract uptime */
#ifdef GETUPTIME_BSDLIKE
#	include <sys/sysctl.h>
#endif

/* Get access to utmpx- and time-functions */
#ifdef GETUPTIME_UTMPX
#	include <utmpx.h>
#	include <time.h>
#endif

/* Needed to get uptime via kmem */
#ifdef GETUPTIME_KMEM
#	include <nlist.h>
#	include <fcntl.h>
#	include <time.h>
#endif

/* Needed on Tru64 */
#if defined(__osf__) || defined(__digital__)
#	include <sys/table.h>
#endif


/* Try to solve problems with getloadavg() */
#ifndef HAVE_GETLOADAVG
/*
 * Insert os-specific solutions with #ifdef's here
 */
#if defined(__osf__) || defined(__digital__)
/* Tru64 */
int getloadavg(double loadavg[], int nelem)
{
	struct tbl_loadavg tbl_la;
	int i;
	if(nelem < 0 || nelem > 3)
		return -1;
	tbl_la.tl_lscale=1000;
	if(table(TBL_LOADAVG, 0, &tbl_la, 1, sizeof(tbl_la)) == -1)
		return -1;

	for(i=0; i<nelem; i++)
		*loadavg++=(double)tbl_la.tl_avenrun.l[i] / 1000.0f;

	return nelem;
}
#elif defined(HPUX) && defined(HAVE_PSTAT_GETDYNAMIC)
/* HP/UX */
#include <sys/pstat.h>
int getloadavg(double loadavg[], int nelem)
{
	int i;
	struct pst_dynamic info;

	if( nelem < 0 || nelem > 3 )
		return -1;

	if(pstat_getdynamic(&info, sizeof(info), 0, 0) < 0)
		return -1;

	for(i=0; i<nelem; i++)
		switch(i)
		{
			case 0:
				loadavg[i] = info.psd_avg_1_min;
				break;

			case 1:
				loadavg[i] = info.psd_avg_5_min;
				break;

			case 2:
				loadavg[i] = info.psd_avg_15_min;
				break;
		}
	
	return nelem;
}
#elif defined(ultrix) || defined(IRIX)
/* Ultrix & Irix */
#ifdef ultrix
#	include <sys/fixpoint.h>
#endif
int getloadavg(double loadavg[], int nelem)
{
	/* Get average load using kmem */
	static struct NLIST nl[2];
	static int nlist_loaded = 0;
	static int f = -1;
	int i;
	int l[3];

	if(nelem < 0 || nelem > 3)
		return -1;

	/* Figure out where in /dev/kmem to fetch avg load */
	if( ! nlist_loaded )
	{
		nlist_loaded = 1;
		nl[0].n_name = "avenrun";
		nl[1].n_name = NULL;
		if(NLIST(NLIST_KERNEL, nl) != 0)
			err(EXIT_FAILURE, "Failed to get avenrun-offset from %s.", NLIST_KERNEL);
	}

	/* Open /dev/kmem */
	if(f == -1)
		if((f = open("/dev/kmem", O_RDONLY)) < 0)
			err(EXIT_FAILURE, "Failed to open /dev/kmem");

	/* Extract load from kmem */
	if(lseek(f, nl[0].n_value, SEEK_SET) == -1)
		err(EXIT_FAILURE, "Failed to access /dev/kmem");
	if(read(f, &l, sizeof(l)) == -1)
		err(EXIT_FAILURE, "Failed to read average load from /dev/kmem");

	/* Store average load in return-array */
	for(i=0; i<nelem; i++)
#ifdef ultrix
		loadavg[i] = FIX_TO_DBL(l[i]);
#else
		loadavg[i] = l[i] / 1000.0;
#endif

	return nelem;
}
#else
/* Output some warnings for various systems */
#ifdef HPUX
#	warning Missing pstat_getdynamic(), loadavg disabled
#endif

/* Last resort, disable loadavg */
int getloadavg(double loadavg[], int nelem)
{
	return -1;
}
#endif
#endif /* defined(HAVE_GETLOADAVG) */

#define COPYRIGHT_YEAR "2003-2004"

#define PROTVERSION 1
#define CLIENTID 1
#define VER_MAJOR 0
#define VER_MINOR 2 
#define VER_PATCH 5

#define SERVER_MAX 128
#define PASSWORD_MAX 16
#define BUF_SIZE 512
#define HEADER_SIZE 24

#define MAXLEN_SYSNAME 32
#define MAXLEN_RELEASE 32
#define MAXLEN_VERSION 256
#define MAXLEN_MACHINE 32

/* Commands from clients */
#define CCMD_LOGIN 0
#define CCMD_LOGOUT 6
#define CCMD_UPDATE 8
#define CCMD_VOID 255

/* Commands from server */
#define SCMD_LOGINOK 128
#define SCMD_LOGINFAILED 129
#define SCMD_UPDATEOK 136
#define SCMD_UPDATEFAILED 137
#define SCMD_REQUESTCHANGEDELAY 144
#define SCMD_REQUESTRELOGIN 152
#define SCMD_REQUESTHARDRELOGIN 153
#define SCMD_REQUESTSHUTDOWN 160
#define SCMD_MSGNOTICE 168
#define SCMD_MSGCRITICAL 169

int VALID_SCMDS[] = {	SCMD_LOGINOK,
											SCMD_LOGINFAILED,
											SCMD_UPDATEOK,
											SCMD_UPDATEFAILED,
											SCMD_REQUESTCHANGEDELAY,
											SCMD_REQUESTRELOGIN,
											SCMD_REQUESTHARDRELOGIN,
											SCMD_REQUESTSHUTDOWN,
											SCMD_MSGNOTICE,
											SCMD_MSGCRITICAL
											};

#define DEFAULTSERVER "uptimes.mreriksson.net"
#define DEFAULTSERVERPORT 2050

void usage(void);
void help(void);
void version(void);
void initbuf(unsigned char*, int, unsigned char*);
void updatebuf(unsigned char*, int cmd, unsigned char*, unsigned int);
int validatepackage(unsigned char*);
time_t get_uptime(void);
int get_load(int []);


int main(int argc, char** argv)
{
	/* Commandline parser */
	extern char *optarg;
	extern int optind;

	/* Variables for system status */
	time_t uptime;
	int loadavg[3];

	/* Buffers used to send and receive data */
	unsigned char buf[BUF_SIZE];
	unsigned char tmpbuf[BUF_SIZE];
	unsigned char recvbuf[BUF_SIZE];
	int buflen;

	/* Network related */
	int sock;
	struct hostent* srvhe;
	struct sockaddr_in srvaddr;
	struct sockaddr_in remoteaddr;
	struct sockaddr_in localaddr;
#	if defined(HPUX)
	int sl;	/* HP/UX uses int as arg 6 for recvfrom() */
#else
	socklen_t sl;
#endif /* defined(__hpux) || defined(__hppa) */
#	ifdef USE_SELECT
	fd_set readfds;
#	endif

	/* Misc variables for specific tasks */
#	ifdef PASSWORD_USE_MD5
	MD5_CTX md;
#	else
	char* s;
#	endif /* PASSWORD_USE_MD5 */
	struct utsname un;
	struct timeval tv;
	time_t tl;
	int relogin;

	char server[SERVER_MAX];	/* XXX Should use some system defined value */
	unsigned short serverport;
	long hostid;
	unsigned char password[PASSWORD_MAX];
	unsigned char md5password[16];

	int detach = 1;	/* Detach by default */
	int debug = 0; /* Default: Debug output disabled */

	/* Common variables */
	int i,j,k,l,r;
	int ch;

	/*
	 * Set some default values
	 */
	strncpy(server, DEFAULTSERVER, 128);
	hostid = 0;
	password[0] = '\0';
	serverport = DEFAULTSERVERPORT;

	/*
	 * Parse commandline-arguments
	 */
	while((ch = getopt(argc, argv, "hvdDs:i:p:P:")) != -1)
		switch(ch)
		{
			/* Display help information */
			case 'h':
				help();
				break;

			/* Display version information */
			case 'v':
				version();
				break;
			
			/* Do not detach */
			case 'd':
				detach = 0;
				break;
			
			/* Debug mode */
			case 'D':
				debug = 1;
				/* Do not detach in debug mode */
				detach = 0;
				break;
			
			/* Host ID */
			case 'i':
				hostid = atoi(optarg);
				if( hostid == 0 )
				{
					printf("Bad host id: %s\n", optarg);
					exit(EXIT_FAILURE);
				}
				break;

			/* Password */
			case 'p':
				if(strlen(optarg) > PASSWORD_MAX - 1)
				{
					printf("Enter a shorter password.\n");
					exit(EXIT_FAILURE);
				}
				strncpy((char*)password, optarg, PASSWORD_MAX - 1);

#				ifndef HAVE_SETPROCTITLE
				/* Remove password from ps-list if we have no setproctitle() */
				/* This should work on most systems */
				strncpy(argv[optind-1], "****************", strlen(optarg));
#				endif
				break;
			
			/* Specify server */
			case 's':
				if(strlen(optarg) > SERVER_MAX - 1)
				{
					printf("Enter a shorter servername.\n");
					exit(EXIT_FAILURE);
				}
				strncpy(server, optarg, SERVER_MAX - 1);
				break;

			/* Specify Server Port */
			case 'P':
				serverport = atoi(optarg);
				if( serverport == 0 )
				{
					printf("Bad server port: %s\n", optarg);
					exit(EXIT_FAILURE);
				}
				break;
			
			/* Show usage */
			case '?':
			default:
			usage();
		}
	argc -= optind;
	argc += optind;

	/* Make sure that we got all required arguments */
	if(strlen((char*)password) == 0 || hostid == 0)
		usage();

	/*
	 * Detach from console
	 */
	if( detach )
	{
		switch(fork())
		{
			case -1:
				/* Error forking */
				err(EXIT_FAILURE, "Unable to fork into background");
				break;
	
			case 0:
				/* We are the child, continue as normal after switch-block */
				break;
	
			default:
				/* We are the parent, exit and allow child to continue our
				 	work for us. Use _exit() to quit but leave resources availible
				 	for forked child. */
				_exit(EXIT_SUCCESS);
		}

#		ifdef HAVE_SETSID
		/* Create new session without a controlling terminal */
		if(setsid() == -1)
			err(EXIT_FAILURE, "Failed to create new session.");
#		endif
		
	}

	/* We do not want password etc given on commandline to be visible when
	   listing processes with the ps-command */
#	if defined(HAVE_SETPROCTITLE)
#		if defined(__FreeBSD__)
			setproctitle("-uptimec");
#		else
			setproctitle(NULL);
#		endif /* __FreeBSD__ */
#	endif /* HAVE_SETPROCTITLE */

	/* Output startup-info to syslog */
#	ifdef DO_SYSLOG
		if( VER_PATCH == 0 )
			syslog(LOG_INFO, "Launching uptimec v%i.%i", VER_MAJOR, VER_MINOR);
		else
			syslog(LOG_INFO, "Launching uptimec v%i.%ip%i", VER_MAJOR, VER_MINOR, VER_PATCH);
#	endif

	/*
	 * Digest password
	 */
#	if defined(PASSWORD_USE_MD5) && ! defined(USE_PLAINTEXT_PASSWORD)
	MD5Init(&md);
	MD5Update((MD5_CTX*)&md, password, strlen(password));
	MD5Final(md5password, &md);
#	else
	/* Use plaintext-passwords as a last way out */
	memset(md5password, '\0', 16);
	strncpy((char*) md5password, (const char*) password, 15);
#	endif

#if 0 /* crypt-passwords */
	/* XXX Broken */
	s = crypt(password, "$1");
	memcpy(md5password, s, 16);
#	endif /* PASSWORD_USE_MD5 */

	/*
	 * Init send-buffer
	 */
	initbuf(buf, hostid, md5password);

	/*
	 * Figure out where to send packages
	 */
	do {
		if( (srvhe = gethostbyname(server)) == NULL )
		{
#			ifdef DO_SYSLOG
				syslog(LOG_INFO, "Failed to resolve host '%s', retrying in 10 minutes.", server);
#			endif
			sleep(600);
		}
	} while( srvhe == NULL );
	srvaddr.sin_family = AF_INET;
	srvaddr.sin_port = htons(serverport);
	memcpy(&srvaddr.sin_addr.s_addr, srvhe->h_addr, srvhe->h_length);
	memset(&(srvaddr.sin_zero), '\0', 8); /* Set rest of content to zero */

	/*
	 * Create socket
	 */
	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
	{
		err(EXIT_FAILURE, "Error creating socket");
		exit(EXIT_FAILURE);
	}

	/*
	 * Bind socket (Required in some environments)
	 */
	memset(&localaddr, '\0', sizeof(struct sockaddr_in)); /* Init to zero */
	localaddr.sin_family = AF_INET;
	localaddr.sin_port = 0;
	if(bind(sock, (struct sockaddr*)&localaddr, sizeof(struct sockaddr_in)) == -1)
	{
		err(EXIT_FAILURE, "Error binding socket");
		exit(EXIT_FAILURE);
	}

	/*
	 * Do login
	 */

	for(;;)
	{
		/* Extract system information */
		if((uname(&un)) == -1)
		{
			err(EXIT_FAILURE, "Could not extract system information");
			exit(EXIT_FAILURE);
		}
		/* XXX Should just cut strings that are too long instead of failing */
		if(strlen(un.sysname) > MAXLEN_SYSNAME ||
	   	strlen(un.release) > MAXLEN_RELEASE ||
	   	strlen(un.version) > MAXLEN_VERSION ||
	   	strlen(un.machine) > MAXLEN_MACHINE )
		 	{
		 		err(EXIT_FAILURE, "Bad system information extracted");
				exit(EXIT_FAILURE);
		 	}
		/* Create block of data for login command */
		tmpbuf[0] = CLIENTID;
		tmpbuf[1] = VER_MAJOR;
		tmpbuf[2] = VER_MINOR;
		tmpbuf[3] = VER_PATCH;
		for(i=0,j=6; i < 4; i++)
			switch(i)
			{
				/* Sysname */
				case 0:
					strcpy((char*)tmpbuf+j, un.sysname);
					j+=strlen(un.sysname) + 1;
					break;

				/* Release */
				case 1:
					strcpy((char*)tmpbuf+j, un.release);
					j+=strlen(un.release) + 1;
					break;

				/* Version */
				case 2:
					strcpy((char*)tmpbuf+j, un.version);
					j+=strlen(un.version) + 1;
					break;

				/* Machine */
				case 3:
					strcpy((char*)tmpbuf+j, un.machine);
					j+=strlen(un.machine) + 1;
					break;

				/* This should not happen */
				default:
					err(EXIT_FAILURE, "Internal error");
					exit(EXIT_FAILURE);
			}
		buflen = j;
		j-=6;
		tmpbuf[5] = j & 255; j >>= 8; /* Argument length */
		tmpbuf[4] = j & 255;

		/* XXX Needs to handle situations where the entire package can't be sent at once, or received
	       	with just one recvfrom(); */

#		ifndef USE_SELECT
		if( setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
		{
			err(EXIT_FAILURE, "Error when modifying socket with setsockopt()");
			exit(EXIT_FAILURE);
		}
#		endif

		for(i=0;;i++)
		{
			if(i==3) /* Three tries has failed, sleep for 1800 secs */
			{
#				ifdef DO_SYSLOG
					syslog(LOG_INFO, "Failed to login three times in a row, sleeping 30 minutes before next try");
#				endif
				sleep(1800);
				i=0;
			}
			
			/* Update send-buffer */
			updatebuf(buf, CCMD_LOGIN, tmpbuf, buflen);

			/* Send login package */
			l = sendto(sock, buf, HEADER_SIZE + buflen, 0, (struct sockaddr*) &srvaddr, sizeof(struct sockaddr_in));
#			ifdef DEBUG
				printf("DEBUG: Sent %i bytes to ip %s with command %i.\n",
					l, inet_ntoa(srvaddr.sin_addr), (int) buf[1]);
#			endif

			/* Send login and wait for respons */
			tv.tv_sec=30;
			tv.tv_usec=0;

			/* Wait for a reply from server */
			/* XXX Should make sure that the received sockaddr-structure contains an address that is
			       of the type we expect. */
			sl = sizeof(remoteaddr); /* Required on some platforms */
#			ifdef USE_SELECT
			FD_ZERO(&readfds);
			FD_SET(sock, &readfds);
			if((r = select(sock+1, &readfds, NULL, NULL, &tv)) > 0 )
			{
				if((l = recvfrom(sock, recvbuf, BUF_SIZE, 0, (struct sockaddr*) &remoteaddr, &sl)) == -1)
				{
					err(EXIT_FAILURE, "Error reading from socket");
					exit(EXIT_FAILURE);
				}
#			else
			if((l = recvfrom(sock, recvbuf, BUF_SIZE, 0, (struct sockaddr*) &remoteaddr, &sl)) != -1)
			{
#	 		endif
				/* Process data */
				/* XXX We should allow for some other commands from the server here,
				       for example, REQUESTSHUTDOWN and RELOGIN commands */
				if((j = validatepackage(recvbuf)) == SCMD_LOGINOK)
					break;
				else
					if(j == SCMD_LOGINFAILED)
					{
#						ifdef DO_SYSLOG
							syslog(LOG_ERR, "Authorization failed, bailing out!");
#						endif
						printf("uptimec: Authorization failed, bailing out.\n");
						exit(EXIT_FAILURE);
					}
#					ifdef DEBUG
					else
						printf("DEBUG: Received unexpected command (%i), discarding...\n", j);
#					endif
			}
			else 
#				ifdef USE_SELECT
				if( r < 0 )
				{
					err(EXIT_FAILURE, "Error in select()");
					exit(EXIT_FAILURE);
				}
#				else
				if(errno != EAGAIN)
				{
					err(EXIT_FAILURE, "Error reading from socket");
					exit(EXIT_FAILURE);
				}
#				endif
		}
#	ifdef DO_SYSLOG
		syslog(LOG_INFO, "Did successful login against server %s, entering update-mode.", server);
#	endif

		/* Run main loop */
		relogin=0;
		while(!relogin)
		{
			buflen = 10;	/* Static */

			/* Extract system information */
			uptime = get_uptime();
			get_load(loadavg);

#ifdef DEBUG
			printf("DEBUG: Sending - Uptime: %i secs\n", uptime);
			printf("                  Load1: %i\n", loadavg[0]);
			printf("                  Load2: %i\n", loadavg[1]);
			printf("                  Load3: %i\n", loadavg[2]);
#endif

			/*
		 	* Insert data into buffer
		 	*/

			/* Uptime */
			tmpbuf[3] = uptime & 255; uptime >>= 8;
			tmpbuf[2] = uptime & 255; uptime >>= 8;
			tmpbuf[1] = uptime & 255; uptime >>= 8;
			tmpbuf[0] = uptime & 255;

			/* Load */
			tmpbuf[5] = loadavg[0] & 255; loadavg[0] >>= 8;
			tmpbuf[4] = loadavg[0] & 255; /* 1 min avg */
			tmpbuf[7] = loadavg[1] & 255; loadavg[1] >>= 8;
			tmpbuf[6] = loadavg[1] & 255; /* 5 min avg */
			tmpbuf[9] = loadavg[2] & 255; loadavg[2] >>= 8;
			tmpbuf[8] = loadavg[2] & 255; /* 15 min avg */

			/* Update send-buffer */
			updatebuf(buf, CCMD_UPDATE, tmpbuf, buflen);
	
			/*
		 	* Send login package
		 	*/
			l = sendto(sock, buf, HEADER_SIZE + buflen, 0, (struct sockaddr*) &srvaddr, sizeof(struct sockaddr_in));
#ifdef DEBUG
				printf("DEBUG: Sent %i bytes to ip %s with command %i.\n",
					l, inet_ntoa(srvaddr.sin_addr), (int) buf[1]);
#endif

			/* XXX All packages received from the server should make sure that they
		       	have the same remote address as the server we logged in to */
			/*
		 	* Listen for packages from the server
		 	*/

			/* Configure delay between each update */
			/* The tv- and tl-variables are reset to these values at the
		   	end of each loop below. */
			tv.tv_sec=30;	/* Twenty periods of 30 seconds give 10 minutes  */
			tv.tv_usec=0;
	
			tl = time(NULL);	/* Register time when loop started so that we know
											   	how long to sleep before next package in case
												 	we receive something from the server before our
												 	socket-read times out. */

			/* Some systems seems to have problems handling longer timeouts than 30
		   	seconds for SO_RCVTIMEO (setsockopt), so we'll go with one minute
			 	and loop ten twenty */
			for(k=0; k < 20 && !relogin;)
			{
				/* tc.tc_secs should never become zero, or the client will hang forever,
			   waiting for a package that it will never receive. So, just to be sure,
				 we'll check it's value before configuring the socket based on it */
				if( tv.tv_sec < 1 )
					break;

#				ifndef USE_SELECT
				/* Instruct the socket on the maximum time to wait for next package */
				if( setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1)
				{
					err(EXIT_FAILURE, "Error when modifying socket with setsockopt()");
					exit(EXIT_FAILURE);
				}
#				endif

				sl = sizeof(remoteaddr); /* Required on some platforms */
#				ifdef USE_SELECT
				FD_ZERO(&readfds);
				FD_SET(sock, &readfds);
				if((r = select(sock+1, &readfds, NULL, NULL, &tv)) > 0 )
				{
					if((l = recvfrom(sock, recvbuf, BUF_SIZE, 0, (struct sockaddr*) &remoteaddr, &sl)) == -1)
					{
						err(EXIT_FAILURE, "Error reading from socket");
						exit(EXIT_FAILURE);
					}
#				else
				if((l = recvfrom(sock, recvbuf, BUF_SIZE, 0, (struct sockaddr*) &remoteaddr, &sl)) != -1)
				{
#				endif
					/* XXX We shouldn't even do this if we haven't received enough data to fill a
				       	header-block */
					if( (i = validatepackage(recvbuf)) != -1 )
					{
						/* We received a valid package */
						/* Perform action based on packages command */
						switch(i)
						{
							case SCMD_UPDATEOK:
								/* Just smile and be happy */
#								ifdef DEBUG
								printf("DEBUG: Server validated our last update.\n");
#								endif
								break;

							case SCMD_UPDATEFAILED:
								/* Not really much we can do if we receive this,
								   so we'll just continue as normal */
#								ifdef DEBUG
								printf("DEBUG: Server said that the last update failed.\n");
#								endif
								break;
						
							case SCMD_REQUESTSHUTDOWN:
#								ifdef DEBUG
								printf("DEBUG: Server asked us to shutdown.");
#								endif
#								ifdef DO_SYSLOG
								syslog(LOG_ALERT, "The server requested that we shutdown");
#								endif
								exit(EXIT_SUCCESS);
								break; /* We will never reach this */
							
							case SCMD_REQUESTRELOGIN:
#								ifdef DEBUG
								printf("DEBUG: Server asked us to login again.");
#								endif
#								ifdef DO_SYSLOG
								syslog(LOG_ALERT, "The server requested that we do a new login");
#								endif
								relogin=1;
								break;

							default:
								/* We received a command that we did not expect here, ignore it */
#								ifdef DEBUG
								printf("DEBUG: We received an unexpected command from server (%i)\n", i);
#								endif
								break;
						}
					}

					/* Figure out how long we should wait before next update */
					if((j = time(NULL) - tl) < 30)
					{
						/* Set time to configure socket to timeout on before we send the next update */
						tv.tv_sec = 30 - j;
						continue;
					}
				}
				else
#					ifdef USE_SELECT
					if( r < 0 )
					{
						err(EXIT_FAILURE, "Error in select()");
						exit(EXIT_FAILURE);
					}
#					else
					if(errno != EAGAIN)
					{
						/* Failed to read from socket */
						err(EXIT_FAILURE, "Error reading from socket");
						exit(EXIT_FAILURE);
					} /* Else: Nothing received, continue with next loop */
#					endif

				/* If we reach this part, we did not receive any data under
				   the last receive period, so we should reset our timeout-
					 numbers and go on with next loop */
				tv.tv_sec=30;
				tv.tv_usec=0;
				tl = time(NULL);	
				k++;	/* Increase loop-counter */
			}
		} /* while(!relogin) - The main loop */
	}	/* for(;;) - Will exit to this loop for relogins */
}

void usage(void)
{
	printf("Usage: uptimec [-d] [-s server] [-P port] -i hostid -p password\n");
	printf("       uptimec -v\n");
	printf("       uptimec -h\n");
	exit(EXIT_FAILURE);
}

void help(void)
{
	printf("Help\n\n");
	/* XXX Add help message */
	printf("Options:\n");
	printf("   -d           Do not detach.\n");
	printf("   -i hostid    Numeric uptime-id for this host.\n");
	printf("   -p password  Password to use when talking to server.\n");
	printf("   -s server    Specify server to send updates to.\n");
	printf("   -P port      Specify server port.\n");
	printf("   -v           Display client version.\n");
	printf("   -h           View this information.\n\n");
	exit(EXIT_SUCCESS);
}

void version(void)
{
	if( VER_PATCH == 0 )
		printf("Uptimec version %i.%i\n", VER_MAJOR, VER_MINOR);
	else
		printf("Uptimec version %i.%ip%i\n", VER_MAJOR, VER_MINOR, VER_PATCH);
	printf("Copyright (c) %s Ola Eriksson, All Rights Reserved\n", COPYRIGHT_YEAR);
	printf("This is free software; see the source for copying conditions.\n\n");
	printf("Visit http://www.mrEriksson.net/ for more information.\n");
	exit(EXIT_SUCCESS);
}

void initbuf(unsigned char* buf, int hostid, unsigned char* md5password)
{
	long l = hostid;

	/* Construct package */
	buf[0] = PROTVERSION;
	buf[1] = CCMD_VOID;	/* Init as no command */
	buf[2] = 0;	/* Init to zero */
	buf[3] = 0;	/* -"- */

	/* Insert hosts id */
	buf[7] = l & 255; l >>= 8;
	buf[6] = l & 255; l >>= 8;
	buf[5] = l & 255; l >>= 8;
	buf[4] = l & 255; 

	/* Insert digested password into header */
	memcpy(buf + 8, md5password, 16);
}

void updatebuf(unsigned char* buf, int cmd, unsigned char* data, unsigned int datalen)
{
	static unsigned char seq = 0;

	/* Insert command */
	buf[1] = (unsigned char)cmd;

	/* Insert sequence number */
	buf[2] = seq;

	/* Calculate checksum */
	buf[3] = (buf[0] ^ buf[1]) ^ buf[2];

	/* Add data, if any */
	if(data != NULL)
	{
		/* Make sure we didn't receive a too large block of data */
		if(datalen > BUF_SIZE - HEADER_SIZE)
		{
			printf("Internal error occured.\n");
			exit(EXIT_FAILURE);
		}
		memcpy(buf + HEADER_SIZE, data, datalen);
	}

	/* Update sequence number */
	if(seq == 255)
		seq = 0;
	else
		seq++;
}

int validatepackage(unsigned char* buf)
{
	int i;
	int j;

#ifdef DEBUG_DUMPHEADER
	printf("Dumping header:\n");
	for(i=0; i < 4; i++)
		printf("Byte %i: %i\n", i, (int)buf[i]);
	printf("---[END OF DUMP]---\n");
#endif

	/* Validate checksum */
	j = (buf[0] ^ buf[1]) ^ buf[2];
	if(j != buf[3])
	{
#ifdef DEBUG
		printf("DEBUG: Received package with invalid checksum, discarding...\n");
#endif
		return -1;
	}

	j = -1;
	/* Make sure that this is a valid command */
	for(i=0; i < sizeof(VALID_SCMDS) / sizeof(VALID_SCMDS[0]); i++)
		if( buf[1] == VALID_SCMDS[i] )
			j = buf[1];

	if(j == -1)
	{
#ifdef DEBUG
		printf("DEBUG: Received unknown command (%i), discarding...\n", buf[1]);
#endif
		return -1;
	}

	/* Valid package, return packages command */
	return buf[1];
}

time_t get_uptime(void)
{
	time_t uptime = 0;

#	define GETUPTIME_NO_METHOD_FOUND

/* NetBSD, FreeBSD, OpenBSD and MacOS X */
#	ifdef GETUPTIME_BSDLIKE
#	undef GETUPTIME_NO_METHOD_FOUND
	int mib[2];
	size_t size;

	struct timeval boottime;

	time_t now;

	mib[0] = CTL_KERN;
	mib[1] = KERN_BOOTTIME;
	size = sizeof(boottime);

	time(&now);

	if( sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 )
		if( boottime.tv_sec != 0 )
		{
			uptime = now - boottime.tv_sec;
			uptime += 30;
		}
		else {
			errx(1, "Bad boottime value");
		}
# endif /* defined(GETUPTIME_BSDLIKE) */


/* Most Linux-systems */
#	ifdef GETUPTIME_PROCUPTIME
#	undef GETUPTIME_NO_METHOD_FOUND
	/* Try to get uptime from /proc/uptime */
	FILE *f;
	double duptime = 0;

	f = fopen("/proc/uptime", "r");
	if(!f)
	{
		err(EXIT_FAILURE, "Failed to open /proc/uptime");
		exit(EXIT_FAILURE);
	}

	if(fscanf(f, "%lf", &duptime) != 1)
	{
		err(EXIT_FAILURE, "Failed to get valid uptime from /proc/uptime");
		exit(EXIT_FAILURE);
	}
	fclose(f);

	/* XXX Some Linuxes do uptime wraparounds, we should compensate for this */
	uptime = (time_t) duptime;

#	endif /* defined(GETUPTIME_PROCUPTIME) */

/* Solaris and HP/UX */
#	ifdef GETUPTIME_UTMPX
#	undef GETUPTIME_NO_METHOD_FOUND
	/* Extract uptime from utmpx */
	struct utmpx id;
	struct utmpx* u;

	/* Rewind utmpx entry pointer */
	setutxent();

	id.ut_type = BOOT_TIME;
	u = getutxid(&id);

	if( u == NULL )
		err(EXIT_FAILURE, "Failed to extract uptime");

	uptime = time(NULL) - u->ut_tv.tv_sec;
#	endif /* defined(GETUPTIME_UTMPX) */

/* Ultrix & Irix */
#	ifdef GETUPTIME_KMEM
#	undef GETUPTIME_NO_METHOD_FOUND
	/* Extract uptime from kmem */
	static struct NLIST nl[2];
	static nlist_loaded = 0;
	static int f = -1;
	time_t boottime;

	/* Figure out where in kmem we can find the boottime */
	if(!nlist_loaded)
	{
		nlist_loaded = 1;
		nl[0].n_name = "boottime";
		nl[1].n_name = NULL;
		if(NLIST(NLIST_KERNEL, nl) != 0)
			err(EXIT_FAILURE, "Unable to get boottime-offset from %s.", NLIST_KERNEL);
	}

	/* Open /dev/kmem */
	if(f == -1)
		if((f = open("/dev/kmem", O_RDONLY)) < 0)
			err(EXIT_FAILURE, "Failed to open /dev/kmem");

	/* Read boottime from kmem */
	if(lseek(f, nl[0].n_value, SEEK_SET) == -1)
		err(EXIT_FAILURE, "Failed to access /dev/kmem");
	if(read(f, &boottime, sizeof(boottime)) == -1)
		err(EXIT_FAILURE, "Failed to read boottime from /dev/kmem");

	/* Calculate uptime */
	uptime = time(NULL) - boottime;
#endif

/* Tru64 */
#	ifdef GETUPTIME_TBLSYSINFO
#	undef GETUPTIME_NO_METHOD_FOUND
	struct tbl_sysinfo tbl_si;

	if(table(TBL_SYSINFO, 0, &tbl_si, 1, sizeof(tbl_si)) == -1 )
		err(EXIT_FAILURE, "Failed to get boottime from TBL_SYSINFO");

	uptime = time(NULL) - tbl_si.si_boottime;
#	endif

/* Make sure that we know how to extract uptime */
#	ifdef GETUPTIME_NO_METHOD_FOUND
#		error I do not know how to extract uptime from this system!
#	endif

	return uptime;
}

int get_load(int ret[])
{
	double loadavg[3];
	int i;

	if(getloadavg(loadavg, sizeof(loadavg) / sizeof(loadavg[0])) == -1)
	{
		for(i=0; i<3; i++)
			ret[i] = 65535;

		return 0;
	}
	
	for(i=0; i < 3; i++)
		if(loadavg[i] < 655)
			ret[i] = (int) (loadavg[i] * 100.0);
		else
			ret[i] = 65500;

	return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1