/*
 * infon, a simple information server for the Fonera Access Point
 * Copyright (C) 2007, Emile "iMil" Heitor
 *
 * 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.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Emile "iMil" Heitor <imil@gcu.info>
 * G.C.U. - 2007
 */

#include <limits.h>
#include <stdio.h>
#include <stdarg.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


#define TCP_PORT 1702
#define BUFLEN 2048

/* Zombie handling */
void
resident(int signo)
{
	union wait wstatus;

	while(wait3((int *)&wstatus, WNOHANG, (struct rusage *)NULL) > 0);
}

/* Replace a char with \0 */
void
ctoeol(char *str, char c)
{
	for(;*str != '\0'; str++)
		if (*str == c) {
			*str = '\0';
			return;
		}
}

void
sptoeol(char *str)
{
	ctoeol(str, ' ');
}

void
crtoeol(char *str)
{
	ctoeol(str, '\n');
}

/* Extract column value */
char *
getcolval(char *raw, const char *token, int colnum)
{
	char *p, *ep;
	int i;

	if ((p = strstr(raw, token)) == NULL)
		return NULL;

	for (i = 0; i < colnum; i++) {
		p += strlen(token);
		p++;
		/* there's spaces between identifier and data */
		if (*p == ' ')
			for (; *p == ' '; p++);
	}

	p = strdup(p);
	ep = p;
	sptoeol(ep);

	return p;
}

/* For future use */
char *
fonexec(const char *cmd)
{
	FILE *fp;
	char buf[BUFLEN];

	if ((fp = popen(cmd, "r")) == NULL)
		return NULL;
	else {
		(void)fgets(buf, BUFLEN, fp);
		pclose(fp);
		crtoeol(&buf[0]);
		
		return strdup(buf);
	}
}

char *
procread(const char *entry)
{
	int fd;
	char buf[BUFLEN]="";

	if ((fd = open(entry, O_RDONLY)) < 0)
		return NULL;
	else {
		read(fd, buf, BUFLEN);
		close(fd);
		return strdup(buf);
	}
}

void
meminfo(int fd)
{
	char *mem, *total, *freem, out[BUFLEN] = "";

	if ((mem = procread("/proc/meminfo")) == NULL)
		write(fd, "could not read /proc/meminfo\n", BUFLEN);
	else {
		total = getcolval(mem, "MemTotal:", 1);
		freem = getcolval(mem, "MemFree:", 1);
		
		snprintf(out, BUFLEN, "%s / %s", total, freem);

		write(fd, out, strlen(out));

		free(total); free(freem); free(mem);
	}
}

void
loadavg(int fd)
{
	char *load;

	if ((load = procread("/proc/loadavg")) == NULL)
		write(fd, "could not read /proc/loadavg\n", BUFLEN);
	else {
		sptoeol(load);
		write(fd, load, strlen(load));
		free(load);
	}
}

void
linkq(int fd)
{
	char *lnq, *ath0, *ath1, out[BUFLEN] = "";

	if ((lnq = procread("/proc/net/wireless")) == NULL)
		write(fd, "could not read /proc/net/wireless\n", BUFLEN);
	else {
		ath0 = getcolval(lnq, "ath0:", 2);
		ath1 = getcolval(lnq, "ath1:", 2);

		snprintf(out, BUFLEN, "%s / %s", ath0, ath1);

		write(fd, out, strlen(out));

		free(ath0); free(ath1); free(lnq);
	}
}

void
gettxrx(int fd)
{
	char *procnetdev, *ath0tx, *ath0rx, *ath1tx, *ath1rx, out[BUFLEN] = "";

	if ((procnetdev = procread("/proc/net/dev")) == NULL)
		write(fd, "could not read /proc/net/dev\n", BUFLEN);
	else {
		ath0rx = getcolval(procnetdev, "wifi0:", 1);
		ath1rx = getcolval(procnetdev, "ath1:", 1);
		ath0tx = getcolval(procnetdev, "wifi0:", 9);
		ath1tx = getcolval(procnetdev, "ath1:", 9);

		snprintf(out, BUFLEN, "%s / %s | %s / %s", 
			 ath0rx, ath0tx, ath1rx, ath1tx);

		write(fd, out, strlen(out));

		free(ath0rx); free(ath1rx);
		free(ath0tx); free(ath1tx);
		free(procnetdev);
	}
}

void
foocommand(int fd)
{
	char *cmd = "/bin/date", *res;

	res = fonexec(cmd);
	write(fd, res, strlen(res));
	free(res);
}

void
help(int fd)
{
	const char *h = "l: load average\nm: memory info (total / free)\nk: link quality (ath0 / ath1)\nt: ath0 rx / ath0 tx | ath1 rx / ath1 tx\nq: exit console";

	write(fd, h, strlen(h));
}

void
service(int fd)
{
	char buf[BUFLEN] = "";

	write(fd, "type h for help\nfonera> ", 25);
	for (;;) {

		read(fd, buf, BUFLEN);
		switch(buf[0]) {
		case 'l':
			loadavg(fd);
			break;
		case 'm':
			meminfo(fd);
			break;
		case 'h':
			help(fd);
			break;
		case 'k':
			linkq(fd);
			break;
		case 't':
			gettxrx(fd);
			break;
		case 'x':
			foocommand(fd);
			break;
		case 'q':
			close(fd);
			return;
			break;
		case '\0':
			break;
		default:
			write(fd, "FON!", 5);
			break;
		}
		if (buf[0] != '\0')
			write(fd, "\nfonera> ", 9);

		buf[0] = '\0';
	}
}

int
main(int argc, char *argv[])
{
	int srv_fd, clt_fd, sin_size, pid, yes=1;
	struct sockaddr_in clt_addr, local_addr;
	struct timeval timeout = {5, 0};  
	struct linger lng = {1, 5};

	if ( (srv_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		perror("server: can't open stream socket");
	
	local_addr.sin_family= AF_INET;
	local_addr.sin_addr.s_addr= htonl(INADDR_ANY);
	local_addr.sin_port= htons(TCP_PORT);
	memset(&(local_addr.sin_zero), 0, 8);

	if (setsockopt(srv_fd,
		       SOL_SOCKET, 
		       SO_REUSEADDR, 
		       &yes, 
		       sizeof(int)) == -1){
		perror("setsockopt");
		exit(1);
	} 
	
	setsockopt(srv_fd, SOL_SOCKET, SO_RCVTIMEO,
		   &timeout, sizeof(struct timeval));
	setsockopt(srv_fd, SOL_SOCKET, SO_SNDTIMEO,
		   &timeout, sizeof(struct timeval));
	setsockopt(srv_fd, SOL_SOCKET, SO_LINGER,
		   &lng, sizeof(struct linger));
	
	if (bind(srv_fd, (struct sockaddr *) &local_addr, 
		 sizeof(local_addr)) < 0) {
		perror("server: can't bind local address");
		exit(1);
	}

	if (listen(srv_fd, 5) < 0) {
		perror("listen");
		exit(1);
	}

	signal(SIGCHLD, resident);
	
	for ( ;; ) {
		sin_size = sizeof(struct sockaddr_in);
		
		if ((clt_fd = accept(srv_fd,
				     (struct sockaddr *)&clt_addr,
				     (socklen_t *)&sin_size)) < 0) {
			continue;
		}

		pid=fork();
		switch(pid) {
		case -1:
			close(srv_fd);
			perror("fork");
			exit(1);
		case 0:
			close(srv_fd);
			service(clt_fd);
			exit(0);
		}
		close(clt_fd);

	}
}

