/*
 *  in.rupd version 0.8 ruptime daemon for Linux
 *  Copyright (C) 1995  Adam Migus, Memorial University of Newfoundland
 *	(MUN)
 *
 *  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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  If you make modifications to the source, I would be happy to have
 *  them to include in future releases.  Feel free to send them to:
 *      Adam Migus	      				
 *		  amigus@cs.mun.ca 
 *      amigus@ucs.mun.ca   
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <syslog.h>
#include <utmp.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>

#define SERVICE "mun-ruptime"
#define PROTOCOL "tcp"

void return_data(int sockfd);

char *program_name;

char version[] = "version 0.8\n";

void usage() {
	printf( "Usage: %s -d -h -? -v  -p [port]\n"\
			"\t-d,	run in standalone daemon mode\n"\
			"\t-p,	specify a port\n"\
			"\t-h,	print this message\n"\
			"\t-?,	print this message\n"\
			"\t-v,	print version and exit\n"\
			"Should be called with no arguements if used with inetd(8)\n",\
		   	 program_name);
}
/* Gets the uptime of the local system */
int uptime(char *the_uptime)
{
	int fd, cur_pos=0;
	double up_secs, idle_secs;
	int up_mins=0;
	int up_hrs=0;
	int up_days=0;
	static char buf[BUFSIZ];

	if((fd = open(UPTIME,O_RDONLY)) < 0) {
		return(-1);
	}
	if((read(fd,(char *)&buf,sizeof(buf))) < 0) {
		perror(UPTIME);
		return(-1);
	}
	(void)close(fd);
	if(sscanf(buf,"%lf %lf",&up_secs,&idle_secs) < 2) {
		return(-1);
	}
	up_mins = (int)up_secs/60;
	up_hrs = (int)up_mins/60;
	up_days = (int)up_hrs/24;
	up_mins = up_mins % 60;
	up_hrs = up_hrs % 24;

	bzero(&buf,sizeof(buf));

	cur_pos += sprintf(buf,"up "); 
	if(up_days)
		cur_pos += sprintf(buf+cur_pos,"%d day%s, ",up_days,((up_days > 1) ? "s" : ""));
	if(up_hrs)
		cur_pos += sprintf(buf+cur_pos,"%2d:%02d, ",up_hrs,up_mins);
	else
		cur_pos += sprintf(buf+cur_pos,"%d min%s, ",up_mins,((up_mins > 1) ? "s" : ""));

	strncpy(the_uptime,buf,strlen(buf));
	return(0);
}
/* Gets the load average of the local system */
int load_average(char *the_loadavg)
{
	int fd;
	static char buf[BUFSIZ];
	double one_min,five_min,fifteen_min;

	if((fd = open(LOAD_AVG,O_RDONLY)) < 0) {
		return(-1);
	}
	if((read(fd,(char *)&buf,sizeof(buf))) < 0) {
		perror(LOAD_AVG);
		return(-1);
	}
	(void)close(fd);
	if(sscanf(buf,"%lf %lf %lf",&one_min,&five_min,&fifteen_min) < 3) {
		return(-1);
	}
	bzero(&buf,sizeof(buf));
	sprintf(buf," load average: %0.2f %0.2f %0.2f",one_min, 
			five_min,fifteen_min);
	strncpy((char *)the_loadavg,buf,strlen(buf));
	return(0);
}
/* Gets number of users on the system */
int count_users(char *the_users)
{
    int ufd, users = 0;
    struct utmp u;
	static char buf[BUFSIZ];

    if ((ufd = open(_PATH_UTMP, O_RDONLY)) < 0) {
		return(-1);
	}
    while (read(ufd, (char *) &u, sizeof(u)) == sizeof(u)) {
        if(u.ut_type==7 && u.ut_name[0] != '\0') {
            users++;
        }
    }
	(void)close(ufd);
	bzero(&buf,sizeof(buf));
	sprintf(buf,"%i users, ",users);
	strncpy(the_users,buf,strlen(buf));
	return(0);
}
/* Gets local system time */
int system_time(char *the_time)
{
	struct tm *tm;
	time_t time_sec;
	int hours,minutes;
	char day_night[2];
	char buf[BUFSIZ];

	time_sec=time(NULL);
	tm=localtime(&time_sec);
	hours=(tm->tm_hour%12 ? tm->tm_hour%12 : 12);
	minutes=tm->tm_min;
	strcpy(day_night,(tm->tm_hour > 11 ? "pm" : "am"));

	bzero(&buf,sizeof(buf));
	sprintf(buf,"%2d:%02d%s ",hours,minutes,day_night);
	strncpy((char *)the_time,buf,strlen(buf));
	return(0);
}
/* Standalone Daemon Mode  */
void daemon_mode(int *port)
{
	int sockfd, output;
	struct sockaddr_in sockaddr;
	struct sockaddr_in caller;
	struct servent *server;
	struct hostent *caller_name;
	int sockaddr_size = sizeof(sockaddr);
	int caller_size = sizeof(caller);
	int sock_opt = 0;
	int opt_len = sizeof(int);
	char host_log_name[20];
	char host_log_addr[20];
	char host_string[20];

	(void)bzero(&sockaddr,sizeof(sockaddr));
	(void)bzero(&caller,sizeof(caller));

	if((sockfd = socket( AF_INET, SOCK_STREAM, 0)) < 0)
		perror("socket");

	server = getservbyname(SERVICE,PROTOCOL);
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_addr.s_addr = INADDR_ANY;
	if(*port != 0)
        sockaddr.sin_port = htons(*port);
    else
        sockaddr.sin_port = server->s_port;

    if(bind(sockfd,(struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
        fprintf(stderr,"Error can't bind socket...\n");
        exit(0);
    }
	
    sock_opt = 1;
    if(getsockopt(sockfd,0,SO_REUSEADDR,&sock_opt,(int *)&opt_len) == -1) {
        perror("getsockopt");
        exit(0);
    }

    if(getsockname(sockfd,(struct sockaddr *)&sockaddr,
							(int *)&sockaddr_size) < 0)
        fprintf(stderr,"Error can't get socket name...\n");

	while((listen(sockfd,8)) == 0){
    	output = accept(sockfd,(struct sockaddr *)&caller,&caller_size);

		getpeername(0,(struct sockaddr *)&caller,&caller_size);

		bzero(&host_log_addr,sizeof(host_log_addr));	
		bzero(&host_log_name,sizeof(host_log_name));	
		strncpy((char *)&host_log_addr,inet_ntoa(caller.sin_addr),
				strlen(inet_ntoa(caller.sin_addr)));

		if((caller_name = gethostbyaddr((char *)&caller.sin_addr.s_addr, 
			sizeof(caller.sin_addr.s_addr),AF_INET)) != NULL) { 
			bzero(&host_log_name,sizeof(host_log_name));	
			strncpy((char *)&host_log_name,caller_name->h_name,
					strlen(caller_name->h_name));
		}
		if(host_log_name[0] != '\0') /* Got the hostname */	
			strncpy((char *)&host_string,host_log_name,sizeof(host_log_name));
		else /* Got the host IP address */
			strncpy((char *)&host_string,host_log_addr,sizeof(host_log_addr));

		openlog((char *)program_name,LOG_PID,LOG_NOTICE);
		syslog(LOG_NOTICE,"connect from %s\n",host_string);
		(void)closelog();
		(void)return_data(output);
	}
}
/* Tests to see if there's an inetd connect */
int inetd_connect(int sockfd)
{
	struct sockaddr_in name;
	int namelen = sizeof(name);

	if(getpeername(0,(struct sockaddr *)&name,&namelen) <0) {
		return(0);
		exit(1);
	}
	return(1);
}
/* Gets and sends all the data */
void return_data(int sockfd)
{
	char the_hostname[50];
	char the_uptime[50];
	char the_loadavg[50];
	char the_time[50];
	char the_users[50];
	char buffer[BUFSIZ];
	int cur_pos=0;

	bzero(&the_uptime,50);
	bzero(&the_loadavg,50);
	bzero(&the_time,50);
	bzero(&the_users,50);

	if(uptime((char *)&the_uptime) < 0)
		strncpy((char *)&the_uptime," unavailable, ",14);
	if(load_average((char *)&the_loadavg) < 0)
		strncpy((char *)&the_time," unavailable, ",14);
	if(system_time((char *)&the_time) < 0)
		strncpy((char *)&the_time," unavailable, ",14);
	if(count_users((char *)&the_users) < 0)
		strncpy((char *)&the_users," unavailable, ",14);

	bzero(&buffer,sizeof(buffer));

	if(gethostname((char *)&the_hostname,sizeof(the_hostname)) < 0)
		perror("gethostname");

	cur_pos += sprintf(buffer+cur_pos,"  ");
	cur_pos += sprintf(buffer+cur_pos,"%s: ",the_hostname);
	cur_pos += sprintf(buffer+cur_pos,"%s",the_time);
	cur_pos += sprintf(buffer+cur_pos,"%s",the_uptime);
	cur_pos += sprintf(buffer+cur_pos,"%s",the_users);
	cur_pos += sprintf(buffer+cur_pos,"%s",the_loadavg);
/*	
	strncat((char *)&buffer,"  ",2);
	strncat((char *)&the_hostname,": ",2);
	strncat((char *)&buffer,the_hostname,strlen(the_hostname));
	strncat((char *)&buffer,the_time,strlen(the_time));
	strncat((char *)&buffer,the_uptime,strlen(the_uptime));
	strncat((char *)&buffer,the_users,strlen(the_users));
	strncat((char *)&buffer,the_loadavg,strlen(the_loadavg));
*/
	if(sockfd < 0) {
		fprintf(stdout,"%s\n",buffer);
		exit(0);
	}
  	send(sockfd,(char *)&buffer,strlen(buffer),0);
	close(sockfd);
}
void main(int argc , char **argv)
{
	int port=0, sockfd=0, daemon=0;
	extern char *optarg;
	char option;

	program_name = argv[0];


	while((option = getopt(argc, argv, "d?hvp:")) != -1)
		switch(option) {
			case 'd':
						daemon = 1;	
						break;
			case 'p':
						port = atoi(optarg);
						break;
			case 'v':
						printf("%s %s",program_name,version);
						exit(0);
			case 'h':
			case '?':
			default :
						usage(); 
						exit(0);
		}

	if(daemon == 1) {
		switch(fork()) { 
			case -1:
				fprintf(stderr,"Cannot fork.");
				perror("fork");
				break;
			case 0:
				break;
			default:
				exit(0);
		}
		daemon_mode(&port);
	}
	else if(inetd_connect((int)sockfd) == 1){
		(void)return_data(sockfd);
		exit(0);
	}
	else {
		(void)return_data(-1);
	}
	exit(0);
}
