/* $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