
/* fpont 1/00 */
/* pont.net    */
/* tcpServer.c */

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h> /* close */
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/io.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#include <net/if_arp.h>

#define SUCCESS 0
#define ERROR   1

#define SERVER_PORT 1500

/* Protocol:
 * <command> <argument>'\n'
 *
 * valid commands are:
 * 'set','reset','invert' (port number as argument; 0-7 = first,
 * 		8-15 = second, 16-23 = third parallel port)
 * 'state'   (port number as argument [see 'set' etc.]; returns the 
 * 		state of the input line of this port; either '0' or '1') 
 *
 * 'show_ip' (no argument; returns IP address of server host, 
 * 		string represenation, i.e. '192.168.0.76')
 *
 * Possible characters between the tokens command and argument are
 * space ' ', carriage return '\r' (for telnet compatibility) and newline '\n'
 */

 
#define COMMAND_SET 	"set"
#define CMD_SET  	1
#define CMD_SET_ARGS	1
#define COMMAND_RESET 	"reset"
#define CMD_RESET 	2
#define CMD_RESET_ARGS	1
#define COMMAND_INVERT 	"invert"
#define CMD_INVERT	3
#define CMD_INVERT_ARGS	1
#define COMMAND_STATE	"state"
#define CMD_STATE	4
#define CMD_STATE_ARGS	1
#define COMMAND_SHOWIP	"show_ip"
#define CMD_SHOWIP	5
#define CMD_SHOWIP_ARGS	0
#define CMD_SHOWIP_IF   "eth0"

#define MAXARGS	        1
#define STOPCHARS	" \n\r"
#define BUFFSIZE	128

#define inaddrr(x) (*(struct in_addr *) &ifr->x[sizeof sa.sin_port])
#define IFRSIZE   ((int)(size * sizeof (struct ifreq)))

int pp_address[3] = {0x0378, 0x0278, 0x03BC};  // stores the hardware addresses of the three parallel ports
int pp_state[3] = {0, 0, 0};  // 0 = unitialized; 1 = working; -1 not detected
char clientstring[64] = "parent process";

int fetch_token(int clientSock, char* buffer, int buffsize);

void execute(int clientSock, int command, char args[][BUFFSIZE]);

int set(int portnr);
int reset(int portnr);
int invert(int portnr);
int state(int clientSock, int portnr);
int show_ip(int clientSock);

int safewrite(int clientSock, char* string);
int safereadchar(int clientSock, char* character);

int checkpp(int portnr);

void notice(char* msg, ...);
void error(char* msg, ...);

int main (int argc, char *argv[]) {
  
  int serverSock, clientSock, clientLen, pid=1;

  struct sockaddr_in clientAddr, serverAddr;
  char buffer[BUFFSIZE];
  int command = 0, expectargs = 0, argno = 0;
  int oldcommand;
  char args[MAXARGS][BUFFSIZE];

  /* create socket */
  serverSock = socket(AF_INET, SOCK_STREAM, 0);
  if (serverSock < 0) {
    error("cannot open socket");
    return ERROR;
  }
  
  /* bind server port */
  serverAddr.sin_family = AF_INET;
  /* htonl = convert long from host byte order to network byte order */
  serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  /* htons = convert short from host byte order to network byte order */
  serverAddr.sin_port = htons(SERVER_PORT);
  
  if (bind(serverSock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0) {
    error("ERROR: cannot bind port");
    return ERROR;
  }

  listen(serverSock,5);
  notice("waiting for data on TCP port %u",SERVER_PORT);
  
  while (pid) {

    /* clean up process table (removes defunct childs) */
    waitpid(0,0,WNOHANG);

    clientLen = sizeof(clientAddr);
    clientSock = accept(serverSock, (struct sockaddr *) &clientAddr, &clientLen);
    if (clientSock < 0) {
      error("cannot accept connection");
      return ERROR;
    }
    pid = fork();
    if (pid == 0) { /* we are a child */
      sprintf(clientstring, "%s:%d", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
      notice("client connected and fork successful");   
      while (fetch_token(clientSock, buffer, BUFFSIZE) != ERROR) {
        notice("client sends token '%s'", buffer);

	/* see if token is a command. It may happen that an old command that received not enough arguments is
	 * overwritten by a new command, and the old one just gets lost without being executed.
	 * This is for robustness reasons; that way the client and the server can't get out of sync with 
	 * commands and arguments
	 */
	oldcommand = command;
	if 	(strcmp(COMMAND_SET,   buffer) == 0) {command = CMD_SET;    expectargs = CMD_SET_ARGS;    argno = 0;}
	else if (strcmp(COMMAND_RESET, buffer) == 0) {command = CMD_RESET;  expectargs = CMD_RESET_ARGS;  argno = 0;}
	else if (strcmp(COMMAND_INVERT,buffer) == 0) {command = CMD_INVERT; expectargs = CMD_INVERT_ARGS; argno = 0;}
	else if (strcmp(COMMAND_STATE, buffer) == 0) {command = CMD_STATE;  expectargs = CMD_STATE_ARGS;  argno = 0;}
	else if (strcmp(COMMAND_SHOWIP,buffer) == 0) {command = CMD_SHOWIP; expectargs = CMD_SHOWIP_ARGS; argno = 0;}
	else {
	  if (expectargs > 0) { /* consider token as an argument */
	    strcpy(args[argno++], buffer);
	    expectargs--;
	  }
	  else notice("client sends garbage (not a command, not an argument): '%s'", buffer);
	}
	if (oldcommand && argno == 0) error("client started new command without finishing the old one");

	if (command && !expectargs) {
	  execute(clientSock, command, args);
	  command = 0;
	}

      } /* while (fetch_token) */

    }
  } /* while (pid) */
  return 0;
}


/* The following function will read character by character from the client.
 * It stops as soon as one of the characters in STOPCHARS is encountered,
 * and returns the error number. The string is put into the specified buffer.
 * STOPCHARS is designed to give useful results when used with telnet to make
 * debugging easy
 */

int fetch_token(int clientSock, char* buffer, int buffsize) {
  int pos = 0;
  char c;
  do {
    if (safereadchar(clientSock, &c) == ERROR) return ERROR;
    if (!strchr(STOPCHARS,c)) buffer[pos++] = c;
  } while (pos < buffsize-1 && (!strchr(STOPCHARS,c) || pos == 0));
  buffer[pos] = '\0';
  return SUCCESS;
}

void execute(int clientSock, int command, char args[][BUFFSIZE]) {
  switch (command) {
    case CMD_SET:
      notice("set command received (argument %s)", args[0]);
      set(atoi(args[0]));
      break;
    case CMD_RESET:
      notice("reset command received (argument %s)", args[0]);
      reset(atoi(args[0]));
      break;
    case CMD_INVERT:
      notice("invert command received (argument %s)", args[0]);
      invert(atoi(args[0]));
      break;
    case CMD_STATE:
      notice("state command received (argument %s)", args[0]);
      state(clientSock, atoi(args[0]));
      break;
    case CMD_SHOWIP:
      notice("showip command received");
      show_ip(clientSock);
      break;
    default:
      error("Something went wrong internally (execute called with bad command). Child exits.");
      exit(ERROR);
    }
}

int checkpp(int portnr) {
  int pp = portnr / 8;
  if (pp < 0 || pp > 3) {
    error("port nr %d is out of range (0-23)", portnr);
    return ERROR;
  }
  if (pp_state[pp] == 0) {
    if (ioperm(pp_address[pp],3,1)) {
      error("could not get access to parallel port nr %d", clientstring, pp);
      pp_state[pp] = -1;
      return ERROR;
    }
    pp_state[pp] = 1;
  }
  else if (pp_state[pp] == -1) return ERROR;
  return SUCCESS;
}

int set(int portnr) {
  int pp = portnr / 8;
  unsigned char state;
  if (checkpp(portnr) == ERROR) return ERROR;
  state = inb(pp_address[pp]);
  outb(state | (1 << (portnr % 8)), pp_address[pp]);
  return SUCCESS;
}

int reset(int portnr) {
  int pp = portnr / 8;
  unsigned char state;
  if (checkpp(portnr) == ERROR) return ERROR;
  state = inb(pp_address[pp]);
  outb(state & ~(1 << (portnr % 8)), pp_address[pp]);
  return SUCCESS;
}

int invert(int portnr) {
  int pp = portnr / 8;
  unsigned char state;
  if (checkpp(portnr) == ERROR) return ERROR;
  state = inb(pp_address[pp]);
  outb(state ^ (1 << (portnr % 8)), pp_address[pp]);
  return SUCCESS;
}

int state(int clientSock, int portnr) {
  int pp = portnr / 8;
  unsigned char state;
  if (checkpp(portnr) == ERROR) return ERROR;
  state = inb(pp_address[pp]);
  
  if (state & (1 << (portnr % 8))) {
    return safewrite(clientSock, "1\n");
  }
  else {
    return safewrite(clientSock, "0\n");
  }
}

int show_ip(int clientSock) {
  /* some of the following code is stolen from
   * http://mail.nl.linux.org/kernelnewbies/2003-05/msg00090.html
   * it is basically some low-level, probably linux specific playing
   * around with structs and strange syscalls ;-)
   */
  struct ifreq *ifr;
  struct ifconf ifc;
  struct sockaddr_in sa;
  int size = 1;
  char ipaddress[16] = "0.0.0.0";
  ifc.ifc_len = IFRSIZE;
  ifc.ifc_req = NULL;
  do {
    size++;
    /* realloc buffer size until no overflow occurs  */
    if (NULL == (ifc.ifc_req = realloc(ifc.ifc_req, IFRSIZE))) {
      error("out of memory. child exits.");
      exit(ERROR);
    }
    ifc.ifc_len = IFRSIZE;
    if (ioctl(clientSock, SIOCGIFCONF, &ifc)) {
      error("could not fetch information about network interfaces. child exits.");
      exit(ERROR);
    }
  } while  (IFRSIZE <= ifc.ifc_len);
  	/* it seems that allocating while IFRSIZE < ifc.ifc_len is enough; however,
	 * the manpage disagrees... does not really matter anyway
	 */
  
  	/* all we care is eth0; so go over the data structure and see if it's there.
	 * we use strncmp to make sure that eth0:0 etc. is also accepted
	 */
  ifr = ifc.ifc_req;
  while ((char *)ifr < (char *)ifc.ifc_req + ifc.ifc_len && 
	 strncmp(CMD_SHOWIP_IF, ifr->ifr_name, strlen(CMD_SHOWIP_IF)) != 0) {
    ifr++;
  }

  if (strncmp(CMD_SHOWIP_IF, ifr->ifr_name, strlen(CMD_SHOWIP_IF)) == 0) {
    strncpy(ipaddress, inet_ntoa(inaddrr(ifr_addr.sa_data)), 16);
    ipaddress[15] = '\0';
  } 
  free(ifc.ifc_req);
  if (safewrite(clientSock, ipaddress) == SUCCESS && safewrite(clientSock, "\n") == SUCCESS)
    return SUCCESS;
  return ERROR;
}

int safewrite(int clientSock, char* string) {
  int n, i;
  i = strlen(string);
  n = write(clientSock, string, i);
  if (n == 0) { notice("client disconnected"); return ERROR; }
  if (n != i) { notice("error writing to client"); return ERROR; }
  else return SUCCESS;
}

int safereadchar(int clientSock, char* character) {
  int n;
  n = read(clientSock, character, 1);
  if (n == 0) { notice("client disconnected"); return ERROR; }
  if (n != 1) { notice("error reading from client"); return ERROR; }
  else return SUCCESS;
}

void notice(char* msg, ...) {
  va_list args;

  va_start(args, msg);
  printf("%s NOTICE: ", clientstring);
  vprintf(msg, args);
  printf("\n");
  va_end(args);
}

void error(char* msg, ...) {
  va_list args;

  va_start(args, msg);
  fprintf(stderr, "%s ERROR: ", clientstring);
  vfprintf(stderr, msg, args);
  fprintf(stderr, "\n");
  va_end(args);
}

