/*  .__,      .J##,"  '_____     .__   .__, 
    J##]     J######  [######L   ###]  J##]           _#,           J#            .J]
    ###]'  "\#######, [#######, .#### .###]          J##          "###           .##]
   .###'    ####¯4### J##F4###] 4####  ### ,# _,[   [##]    ._, , [##F     _,    J##]
   ]###     ###] "### #### "### [##### ###  J####L"[##### .##### [####] [#####][[####]
   [###    ####       ###'  ### [####L[##] [######,[##### ######][####].#######[#####]
   ####    ###]      .### '.### ######4##] ###'[##F[####][##F¯##][####][##F ### #####.
   ###]    ###] . _. [###  ###F.###[#####][###_J##].###, ######,  ### . .J##### [###
   ###]    ###] [###]4###  ###L ### #####'[#######] ###  .######, ###  [####### [##]
   ###L____###L [### [##]"J###][##] ##### [#######].###    4#### [##]  ####\##F [##'
   #######][######## ######### ###] [#### [##L.   [[###J[##L"J## [##LJ ### J### [##L#
  [#######] #######'[########' ###]  ###]  ####### [#### ######F][#### #######] [###]
  [#######] "#####4  #######'  ###   4##]  [#####  .###] 4#####  .###] [######] 4###]
                                                                    by Patrick Lagacé
  Version 2.0           http://lcdnetstat.intellos.net             patou@sympatico.ca
 
  LCDNetstat program file, you can modify easily this program to display what you
  want on a LCD screen. 


    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
    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA



====================================================================================*/

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

#include <sys/signal.h> //daemon

#define NB_LINE_ON_LCD_DEVICE 4
#define NB_COL_ON_LCD_DEVICE 20
#define ARRAY_SIZE 255

void init_lcd_device();
void init_array();
void get_netstat();
void show_on_lcd();
void lcd_clear();
void set_contrast(int);
void lcd_print(char*,int);
void die(char*,char*);
void note_last_netstat();
void get_config();
static char *save_config_value(char *);   

struct connection
{
   char local_adress[20];
   char name_to_display[20];
   char local_port[6];
   char remote_adress[20];
   char remote_port[6];
   char direction[2];
};
struct connection netstat[ARRAY_SIZE];
struct connection last_netstat;

int fd;
int nb_netstat;
char last_netstat_time[20];


char ignore_list[ARRAY_SIZE][NB_COL_ON_LCD_DEVICE];
char name_resolution_list[ARRAY_SIZE][2][NB_COL_ON_LCD_DEVICE];
char hide_duplicate_list[ARRAY_SIZE][6];
int nb_ignore_list;
int nb_name_resolution_list;
int nb_hide_duplicate_list;

int  debug_mode;
int  daemon_mode;
int  refresh_interval;
char *lcd_device_name;



// ============================================================
//           MAIN
//
// core of LCDNetstat
// ============================================================

int main() {
   int pid;

   get_config();
   init_lcd_device();

   if(daemon_mode){
      if(debug_mode){printf("\nEntereing in dameon mode");}
      signal(SIGTTOU, SIG_IGN);
      signal(SIGTTIN, SIG_IGN);
      signal(SIGTSTP, SIG_IGN);

      pid = fork();

      setsid(); // child process, so let's make us a session leader, and lose control tty
      signal(SIGHUP, SIG_IGN); // SIGHUP will be sent to child process so ignore it 
      if (pid != 0){ exit(0);}
   }

   set_contrast(110);

   strcpy(last_netstat.remote_adress,"");
   strcpy(last_netstat.remote_port,"");
   strcpy(last_netstat.local_adress,"");
   strcpy(last_netstat.local_port,"");
   strcpy(last_netstat.direction,"");
   last_netstat.remote_adress[0]='\0';
   netstat[0].remote_adress[0]='\0';

   while(1){
      init_array();
      get_netstat();
      if(netstat[0].remote_adress[0]!='\0'){ note_last_netstat(); }
      show_on_lcd();
   }
}



// ============================================================
//           SAVECONFIGVALUE
//
// char *str   = value to save
//
// it return a clean result of the value 
//
// convert a value from the config file to assign it
// ============================================================

static char *save_config_value(char *str)
{
   char *result;

   if ( (result=malloc(strlen(str)+1))==NULL) return NULL;

   strcpy(result,str);
   return result;
}



// ============================================================
//           GETCONFIG
// 
// read a configuration file and get the value
// ============================================================

void get_config()
{
   // I dont understand why exactly, but it dont work without 
   // 2 blank value in the extremety of the array
   char *kwords[]= { 
                     "unused",
                     "IgnoreList",       
                     "NameResolution",
                     "HideDuplicate", 
                     "Debug",        
                     "Daemon",       
                     "LCDDeviceName",
                     "RefreshInterval",
                     "unused"
   };
   FILE *fp;

   char buffer[132];
   char keyword[32];
   char value[132];
   char *cp1, *cp2;
   int  i,j,first_word,key;
   int  num_kwords=sizeof(kwords)/sizeof(char *);

   nb_hide_duplicate_list=0;
   nb_ignore_list=0;
   nb_name_resolution_list=0;

   if ( (fp=fopen("/etc/lcdnetstat.conf","r")) == NULL){  die("get_config","Unable to open the configuration file");  }
   while ( (fgets(buffer,200,fp)) != NULL) {
      // Skip comments and blank lines
      if ( (buffer[0]=='#') || isspace((int)buffer[0]) ) continue;

      // Get keyword 
      cp1=buffer;cp2=keyword;
      while ( isalnum((int)*cp1) ) *cp2++ = *cp1++;
      *cp2='\0';


      if ( (keyword[0]=='\0') || (value[0]=='\0') ) continue;

      // Get the keywords number
      key=0;
      for (i=0;i<=num_kwords;i++){
         if (!strcmp(keyword,kwords[i])) { key=i; break; }
      }
      if (key==0) {
         sprintf(value,"Invalid keyword (%s) found in the configuration file\n",keyword);
         die("get_config",value);
      }

      // Get value 
      cp2=value;
      while ( (*cp1!='\n')&&(*cp1!='\0')&&(isspace((int)*cp1)) ) cp1++;
      while ( (*cp1!='\n')&&(*cp1!='\0') ) *cp2++ = *cp1++;
      *cp2--='\0';
      while ( (isspace((int)*cp2)) && (cp2 != value) ) *cp2--='\0';

      // Check if blank keyword/value 
      if ( (keyword[0]=='\0') || (value[0]=='\0') ) continue;
   
      // Evaluate the value depending of the keyword
      switch (key){
         case 1:  strcpy(ignore_list[nb_ignore_list],value);
                  nb_ignore_list++;
                  break;
         case 2:  i=0;
                  j=0;
                  first_word=1;;
                  while(value[i]!='\0'){
                     if(value[i]==' '){ 
                        first_word=0; 
                     } else {
                        if(first_word){
                           //put the value in the name part
                           name_resolution_list[nb_name_resolution_list][0][i]=value[i];
                        } else {
                           // put the value in the ip pars
                           name_resolution_list[nb_name_resolution_list][1][j]=value[i];
                           j++;
                        }
                     }
                     i++;
                  }
                  break;
         case 3:  strcpy(hide_duplicate_list[i],value);
                  nb_hide_duplicate_list++;
                  break; 
         case 4:  debug_mode=atoi(value);      break; 
         case 5:  daemon_mode=atoi(value);      break;
         case 7:  refresh_interval=atoi(value);   break;
         case 6:  lcd_device_name=save_config_value(value); break;
      }
   }
   fclose(fp);
   
   if(debug_mode){
      printf("\n\n-------------------------------");
      printf("\nnum_kwords: %d",num_kwords);
      printf("\ndebug_mode: %d",debug_mode);
      printf("\ndaemon_mode: %d",daemon_mode);
      printf("\nrefresh_intercal: %d",refresh_interval);
      printf("\nlcd_device_name: %s",lcd_device_name);
      i=0;
      while(ignore_list[i][0]!='\0'){
         printf("\nignore_list[%d]: %s",i,ignore_list[i]);
         i++;
      }
      i=0;
      while(hide_duplicate_list[i][0]!='\0'){
         printf("\nhide_duplicate_list[%d]: %s",i,hide_duplicate_list[i]);
         i++;
      }
      i=0;
      while(name_resolution_list[i][0][0]!='\0'){
         printf("\nname_resolution_list[%d]: (%s) traduit de (%s)",i,name_resolution_list[i][0],name_resolution_list[i][1]);
         i++;
      }
      printf("\n-------------------------------\n\n");
   }
}



// ============================================================
//           NOTE_LAST_NETSTAT
//
// keep a note of the last connection and get a timestamp
// ============================================================

void note_last_netstat()
{
   time_t  now;                                  
   struct  tm *tp;            

   last_netstat=netstat[0];

   now = time(NULL);
   strftime(last_netstat_time,sizeof(last_netstat_time),"%d-%b  %H:%M:%S ",
   localtime(&now));
}



// ============================================================
//           LCD_PRINT
//
// char *dat = The string of data to display on the LCD screen
// int num   = The line number on the LCD screen, (1st is 0)
//
// print a line on the lcd screen on the line num_ligne
// ============================================================

void lcd_print(char *dat,int num_ligne)
{
   char out[NB_COL_ON_LCD_DEVICE * NB_LINE_ON_LCD_DEVICE];
   int i;
   int j;
   char line_to_display[NB_COL_ON_LCD_DEVICE];

   for(i=0;i<NB_COL_ON_LCD_DEVICE;i++){line_to_display[i]=' '; }

   i=0;
   while(dat[i]!='\0' && i<NB_COL_ON_LCD_DEVICE){
      line_to_display[i]=dat[i];
      i++;
   }
   if(i<NB_COL_ON_LCD_DEVICE){
      for (j=i;j<=NB_COL_ON_LCD_DEVICE;j++){
         line_to_display[j]=' ';
      }
   }
   line_to_display[NB_COL_ON_LCD_DEVICE]='\0';

   sprintf(out, "%c%c%c", 17, 0, num_ligne);
   write(fd, out, 3);
   if(debug_mode){
      printf("\n[01234567890123456789]:%d",i);
      printf("\n[%s]",line_to_display);
   }
   write(fd, line_to_display, NB_COL_ON_LCD_DEVICE);
   write(fd,"\r\n",2);
}



// ============================================================
//           LCD_CLEAR
//
// clear the lcd screen
// ============================================================

void lcd_clear()
{
   lcd_print("                     ",0);
   lcd_print("                     ",1);
   lcd_print("                     ",2);
   lcd_print("                     ",3);
}



// ============================================================
//           SET_CONTRAST
//
// int contrast    = Value of hte constrast
// 
// adjust the contrast of the LCD screen
// 0=totally soft, 255=totally dark,(110 is very good for CFontz)
// ============================================================

void set_contrast(int contrast)
{
   int realcontrast;
   char out[4];
   static int status=140;
   if(contrast > 0)
   {
      status=contrast;
      realcontrast = (((int)(status)) * 100) / 255;
      sprintf(out, "%c%c", 15, realcontrast);
      write(fd, out, 3);
   }
}



// ============================================================
//           DIE
//
// char *module    = name of the module where the error occurs
// char *message   = error message to display
//
// display error information if something turn wrong
// ============================================================

void die(char *module, char *message) {
   fprintf(stderr,"[LCDNetstat] Encounter the following error\n");
   fprintf(stderr,"Module: %s\n",module);
   fprintf(stderr,"Error message: %s\n\n",message);
   exit(-1);
}



// ============================================================
//           TRIM
//
// char *str      = string who may countain blank space at the end
//
// it return the string str without any blank at the end
// 
// similar at trim of VisualBasic, it remove the blank space
// ============================================================

static char* trim(char* str){
    if (str) { while (isspace(*str)) str++; }
    return str;
}



// ============================================================
//           INIT_ARRAY
//
// initialize the array who countain the netstat stuff
// ============================================================

void init_array() {
   int i;

   nb_netstat=0;
   for(i=0;i<ARRAY_SIZE;i++){
      strcpy(netstat[i].local_adress, "");
      strcpy(netstat[i].local_port,   "");
      strcpy(netstat[i].remote_adress,"");
      strcpy(netstat[i].remote_port,  "");
      strcpy(netstat[i].direction,    "");
   }
   netstat[0].remote_adress[0]='\0';
}



// ============================================================
//           SHOW_ON_LCD
//
// manage what we print on the LCD screen
// ============================================================

void show_on_lcd() {
   char line_to_display[50];
   char out[NB_COL_ON_LCD_DEVICE];
   int current_lcd_line;
   int current_netstat;
   int current_page;
   int nb_page;

   if(netstat[0].remote_adress[0]!='\0'){
      nb_page=(nb_netstat-1)/NB_LINE_ON_LCD_DEVICE;
      for(current_page=0;current_page<=nb_page;current_page++){
         for(current_lcd_line=0;current_lcd_line<NB_LINE_ON_LCD_DEVICE;current_lcd_line++){

            current_netstat=current_lcd_line+(current_page*NB_LINE_ON_LCD_DEVICE);

            strcpy(line_to_display,"                             ");
            strcpy(line_to_display,"");
            if(netstat[current_netstat].remote_adress[0]!='\0'){
               if(netstat[current_netstat].direction[0]=='<'){
                  strcat(line_to_display,trim(netstat[current_netstat].name_to_display));
                  strcat(line_to_display,netstat[current_netstat].direction);
                  strcat(line_to_display,netstat[current_netstat].remote_port);
               }else{
                  strcat(line_to_display,trim(netstat[current_netstat].name_to_display));
                  strcat(line_to_display,netstat[current_netstat].direction);
                  strcat(line_to_display,netstat[current_netstat].local_port);  
               }
            }
            lcd_print(line_to_display,current_lcd_line);
            if (debug_mode==1){
               printf("\nlocaladress : %s",netstat[current_netstat].local_adress);
               printf("\nlocalport   : %s",netstat[current_netstat].local_port);
               printf("\nremoteadress: %s",netstat[current_netstat].remote_adress);
               printf("\nremoteport  : %s",netstat[current_netstat].remote_port);
               printf("\ndirection   : %s",netstat[current_netstat].direction);
               printf("\nline to displat: %d  data: \"%s\"",current_netstat,line_to_display);
            }
         }
         sleep(refresh_interval);
      }
   }else{
      if(last_netstat.remote_adress[0]=='\0'){
         lcd_print("                    ",0);
         lcd_print("   TCP/IP  Status   ",1);
         lcd_print(" There is no active ",2);
         lcd_print("     connection     ",3);
      }else{
         lcd_print("No Connection",0);
         lcd_print("Last one was:",1);
         if(last_netstat.direction[0]=='<'){
            sprintf(out, "%s%s%s", last_netstat.name_to_display, last_netstat.direction, last_netstat.remote_port);
         } else {
            sprintf(out, "%s%s%s", last_netstat.name_to_display, last_netstat.direction, last_netstat.local_port);
         }
         lcd_print(out,2);
         lcd_print(last_netstat_time,3);
      }
      sleep(refresh_interval);
   }
}



// ============================================================
//           GET_NETSTAT
//
// get a netstat result and filter it
// ============================================================

void get_netstat() {
   char buffer[200];
   FILE *file;
   int netstat_line=0;
   int i;
   int j;
   int nbdot;
   int last_position=0;
   char test[6];
   struct connection netstat_brut_line;

   int ignore;
   int hide;

// BSD   if ((file = popen("netstat -naf inet | grep tcp | grep -v LISTEN | grep -v 127.0.0.1", "r"))==NULL){
// LINUX N'ACCEPETE PAS LE F
 if ((file = popen("netstat -na inet | grep tcp | grep -v LISTEN | grep -v 127.0.0.1", "r"))==NULL){

      die("get_netstat","Unable to open a pipe to netstat");
   }else{
      if(fgets(buffer,sizeof(buffer),file) != NULL){
         do{
            // local adress and local port
            nbdot=0;
            for (i=21;i<41;i++){
               if (buffer[i]=='.' || buffer[i]==':'){ nbdot++; }
               if (nbdot<4){
                  netstat_brut_line.local_adress[i-21]=buffer[i];
                  netstat_brut_line.local_adress[i-20]='\0';
                  last_position=i+1;
               } else {
                  if(buffer[i]=='0' || buffer[i]=='1' || buffer[i]=='2' || buffer[i]=='3' || buffer[i]=='4' || buffer[i]=='5' || 
                     buffer[i]=='6' || buffer[i]=='6' || buffer[i]=='7' || buffer[i]=='8' || buffer[i]=='9'){
                     netstat_brut_line.local_port[i-last_position-1]=buffer[i];
                     netstat_brut_line.local_port[i-last_position]='\0';
                  }
               }
   	    }
            // determine what is the direction of the connection (local to remote or remote to local)
            if(atoi(trim(netstat_brut_line.local_port))<=1024){
                strcpy(netstat_brut_line.direction,">");
            } else {
                strcpy(netstat_brut_line.direction,"<");
            }

            // remote adress and remote port
            nbdot=0;
            for (i=44;i<64;i++){
               if (buffer[i]=='.' || buffer[i]==':'){ nbdot++; }
               if (nbdot<4){
                  netstat_brut_line.remote_adress[i-44]=buffer[i];
                  netstat_brut_line.remote_adress[i-43]='\0';
                  last_position=i+1;
               }else{
                  if(buffer[i]=='0' || buffer[i]=='1' || buffer[i]=='2' || buffer[i]=='3' || buffer[i]=='4' || buffer[i]=='5' ||
                     buffer[i]=='6' || buffer[i]=='6' || buffer[i]=='7' || buffer[i]=='8' || buffer[i]=='9'){
                     netstat_brut_line.remote_port[i-last_position-1]=buffer[i];
                     netstat_brut_line.remote_port[i-last_position]='\0';
                  }
               }
            }
            // filter section
            // ignore list
            ignore=0;
            i=0;
            while(ignore_list[i][0]!='\0'){
                  if(debug_mode){printf("\n   IGNORE Compare %s with %s",netstat_brut_line.remote_adress,ignore_list[i]);}
               if(!strcmp(netstat_brut_line.remote_adress,ignore_list[i])){
                  if(debug_mode){printf(" FOUND IN IGNORE LIST!!!");}
                  ignore=1;
               }
               i++;
            }
            if(ignore==0){
               // hide duplicate
               hide=0;
               j=0;
               while(netstat[j].remote_adress[0]!='\0'){
                  i=0;
                  while(hide_duplicate_list[i][0]!='\0'){
                     if(debug_mode){printf("\n   DUPLICATE %s:%s with %s:%s on port %s",netstat_brut_line.remote_adress,netstat_brut_line.local_port,netstat[j].remote_adress,netstat[j].local_port,hide_duplicate_list[i]);}
                     if(!strcmp(netstat_brut_line.remote_adress,netstat[j].remote_adress) && !strcmp(netstat_brut_line.local_port,netstat[j].local_port) && !strcmp(netstat_brut_line.local_port,hide_duplicate_list[i])){
                        if(debug_mode){printf(" FOUND A DUPLICATE!!!");}
                        hide=1;
                     }
                     i++;
                  }
                  j++;
               }
               if(hide==0){
                  // name resolution
                  i=0;
                  strcpy(netstat_brut_line.name_to_display,netstat_brut_line.remote_adress);
                  while(name_resolution_list[i][0][0]!='\0'){
                     if(debug_mode){printf("\n   NAME for %s with %s (%s)",netstat_brut_line.remote_adress,name_resolution_list[i][0],name_resolution_list[i][1]);}
                     if(!strcmp(netstat_brut_line.remote_adress,name_resolution_list[i][1])){
                        if(debug_mode){printf(" FOUND A NAME!!!");}
                        strcpy(netstat_brut_line.name_to_display,name_resolution_list[i][0]);
                     }
                     i++;
                  }
                  netstat[netstat_line]=netstat_brut_line;
                  netstat_line++;
               }
            }
         }while ((fgets(buffer,sizeof(buffer),file) != NULL) && netstat_line<ARRAY_SIZE);
      }
   }
   nb_netstat=netstat_line;
   pclose(file);
}



// ============================================================
//           INIT_LCD_DEVICE
//
// initialize the LCD screen
// ============================================================

void init_lcd_device() {
   fd=open(lcd_device_name,O_WRONLY|O_EXCL);
   if (fd==-1) die("init_lcd_device","Unable to open the lcddevice");
   usleep(850000);
   write(fd,"\004\024\030",4);     // hide cursor/wrap=off/scroll=off
}

