/*********************************************
**********************************************
This is the main ICQ file. Currently it sits
in a logs in and sits in a loop it can receive
messages and keeps the connection alive.
Use crtl-break to exit.

This software is provided AS IS to be used in
whatever way you see fit and is placed in the
public domain.

Author : Matthew Smith April 19, 1998
Contributors : Nicolas Sahlqvist April 27, 1998
                           Ulf Hedlund (guru@slideware.com) April 28, 1998
            Michael Ivey May 4, 1998
            Michael Holzt May 5, 1998

Changes :
   4-28-98 support for WIN32 [UH]
   4-20-98 added variable time_delay between keep_alive packets mds
   4-20-98 added instant message from server support mds
   4-21-98 changed so that long ( 250+ characters ) messages work
            new maximum is ~900 which is hopefully big enough.
            When I know more about udp maybe I can come up with
            a general solution. mds I now think ICQ has a max that is
            smaller than this so everything is ok mds I now think that
            the icq client's maximum is arbitrary and can be ignored :)
   4-23-98 Added beginnings of a user interface
   4-26-98 Changed the version to 0.2a :)
   4-27-98 Nicco added feature to use nick names to log in
   5-05-98 Authorization Messages
   5-13-98 Added time stamps for most things.
**********************************************
**********************************************/
#include "micq.h"
#include "datatype.h"
#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32
#include <conio.h>
#include <io.h>
#include <winsock2.h>
#include <time.h>
#else
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#endif
#include <fcntl.h>
#include <time.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

BOOL Int_End;
BOOL Russian = FALSE;
BOOL Logging = TRUE;
BOOL Quit = FALSE;
BOOL Verbose = FALSE; /* this is needed because I leeched so code
                  from another project I'm working on. */
BOOL serv_mess[ 1024 ];
WORD last_cmd[ 1024 ]; /* command issued for the first 1024 SEQ #'s */
/******************** if we have more than 1024 SEQ this will need some hacking */
WORD seq_num = 1;  /* current sequence number */
DWORD our_ip = 0x0100007f; /* localhost for some reason */
DWORD our_port; /* the port to make tcp connections on */
/************ We don't make tcp connections yet though :( */
DWORD UIN; /* current User Id Number */
BOOL Contact_List = FALSE;
Contact_Member Contacts[ 100 ]; /* no more than 100 contacts max */
int Num_Contacts=0;
DWORD Current_Status=STATUS_OFFLINE;
DWORD last_recv_uin=0;
char passwd[100];
char server[100];
DWORD set_status;
DWORD remote_port;
BOOL Done_Login=FALSE;
BOOL auto_resp=FALSE;
char auto_rep_str[450] = { "User is away.\r\n" };
char message_cmd[16];
char info_cmd[16];
char quit_cmd[16];
char reply_cmd[16];
char again_cmd[16];
char add_cmd[16];

char list_cmd[16];
char away_cmd[16];
char na_cmd[16];
char dnd_cmd[16];
char online_cmd[16];
char occ_cmd[16];
char ffc_cmd[16];
char inv_cmd[16];
char status_cmd[16];
char auth_cmd[16];
char auto_cmd[16];
char change_cmd[16];
char search_cmd[16];

/*/////////////////////////////////////////////
// Connects to hostname on port port
// hostname can be DNS or nnn.nnn.nnn.nnn
// write out messages to the FD aux */
int Connect_Remote( char *hostname, int port, FD_T aux )
{
        int conct, length;
   int sok;
        struct sockaddr_in sin;  /* used to store inet addr stuff */
        struct hostent *host_struct; /* used in DNS llokup */

        sin.sin_addr.s_addr = inet_addr( hostname ); /* checks for n.n.n.n notation */
        if ( sin.sin_addr.s_addr  == -1 ) /* name isn't n.n.n.n so must be DNS */
        {
           host_struct = gethostbyname( hostname );
      if ( host_struct == NULL )
      {
         if ( Verbose )
         {
            M_fdprint( aux, "Shakespeare couldn't spell why should I?\n" );
            M_fdprint( aux, " Especially something like %s\n", hostname );
            /*herror( "Can't find hostname" );*/
         }
         return 0;
      }
           sin.sin_addr = *((struct in_addr *)host_struct->h_addr);
   }
        sin.sin_family = AF_INET; /* we're using the inet not appletalk*/
        sin.sin_port = htons( port );   /* port */
        sok = socket( AF_INET, SOCK_DGRAM, 0 );/* create the unconnected socket*/
   if ( sok == -1 )
   {
      perror( "Socket creation failed" );
      exit( 1 );
   }
   if ( Verbose )
   {
      M_fdprint( aux, "Socket created attempting to connect\n" );
   }
        conct = connect( sok, (struct sockaddr *) &sin, sizeof( sin ) );
        if ( conct == -1 )/* did we connect ?*/
        {
      if ( Verbose )
      {
           M_fdprint( aux, " Conection Refused on port %d at %s\n", port, hostname );
         #ifdef FUNNY_MSGS
            M_fdprint( aux, " D'oh!\n" );
         #endif
           perror( "connect" );
      }
           return 0;
        }
   length = sizeof( sin ) ;
   getsockname( sok, (struct sockaddr *) &sin, &length );
   our_ip = sin.sin_addr.s_addr;
   our_port = sin.sin_port;
   if (Verbose )
   {
      #ifdef FUNNY_MSGS
         M_fdprint( aux, "Our port is %d, take her to sea Mr. Mordoch.\n", ntohs( sin.sin_port ) );
      #else
         M_fdprint( aux, "The port that will be used for tcp ( not yet implemented ) is %d\n", ntohs( sin.sin_port ) );
      #endif
   }
   if ( Verbose )
   {
      M_fdprint( aux, "Connected to %s, waiting for response\n", hostname );
   }
   return sok;
}


/******************************************
Handles packets that the server sends to us.
*******************************************/
void Handle_Server_Response( SOK_T sok )
{
   srv_net_icq_pak pak;
   SIMPLE_MESSAGE_PTR s_mesg;
   int s,i,len;

   s = SOCKREAD( sok, &pak.head.ver, sizeof( pak ) - 2  );
   if ( ( serv_mess[ Chars_2_Word( pak.head.seq ) ] ) )
   {
      if ( Chars_2_Word( pak.head.cmd ) != SRV_ACK ) /* ACKs don't matter */
      {
         if ( Verbose )
            printf( "\nIgnored a message cmd  %04x\n", Chars_2_Word( pak.head.cmd )  );
         ack_srv( sok, Chars_2_Word( pak.head.seq ) ); /* LAGGGGG!! */
      }
      return;
   }
   if ( Chars_2_Word( pak.head.cmd ) != SRV_ACK )
      serv_mess[ Chars_2_Word( pak.head.seq ) ] = TRUE;
   switch ( Chars_2_Word( pak.head.cmd ) )
   {
   case SRV_ACK:
      if ( Verbose )
         printf( "\nThe server acknowledged the %04x command.\n",
            last_cmd[ Chars_2_Word( pak.head.seq ) ] );
      break;
   case SRV_LOGIN_REPLY:
      UIN = Chars_2_DW( &pak.data[0] );
      our_ip = Chars_2_DW( &pak.data[4] );
      printf( "\nLogin successful! UIN : %lu\n", UIN );
      printf( " IP : %u.%u.%u.%u\t", pak.data[4], pak.data[5], pak.data[6], pak.data[7] );
      Time_Stamp();
      printf( "\n" );
      ack_srv( sok, Chars_2_Word( pak.head.seq ) );
      snd_login_1( sok );
      snd_contact_list( sok );
      icq_change_status( sok, set_status );
      Prompt();
      break;
   case SRV_RECV_MESSAGE:
         Recv_Message( sok, pak );
      Prompt();
      break;
   case SRV_X1: /* unknown message  sent after login*/
      if ( Verbose )
         printf( "\nAcknowleged SRV_X1 0x021C Begin messages?\n" );
      ack_srv( sok, Chars_2_Word( pak.head.seq ) );
      break;
   case SRV_X2: /* unknown message  sent after login*/
      if ( Verbose )
         printf( "\nAcknowleged SRV_X2 0x00E6 Done old messages?\n" );
      Show_Quick_Status();
      Done_Login = TRUE;
      ack_srv( sok, Chars_2_Word( pak.head.seq ) );
      snd_got_messages( sok );
      Prompt();
      break;
   case SRV_INFO_REPLY:
      Display_Info_Reply( sok, pak );
      Prompt();
      break;
   case SRV_USER_OFFLINE:
      User_Offline( sok, pak );
      Prompt();
      break;
   case SRV_USER_ONLINE:
      User_Online( sok, pak );
      Prompt();
      break;
   case SRV_STATUS_UPDATE:
      Status_Update( sok, pak );
      Prompt();
      break;
   case SRV_GO_AWAY:
      #ifdef FUNNY_MSGS
      printf( "\nServer sent \"Go away!!\" command.\t" );
      #else
      printf( "\nServer has forced us to disconnect.  This may be because of a bad password.\t" );
      #endif
      Time_Stamp();
      Quit = TRUE;
      Prompt();
      break;
   case SRV_END_OF_SEARCH:
      printf( "\nSearch done.\n" );
      ack_srv( sok, Chars_2_Word( pak.head.seq ) );
      Prompt();
      break;
   case SRV_USER_FOUND:
      Display_Search_Reply( sok, pak );
      Prompt();
      break;
   case SRV_SYS_DELIVERED_MESS:
      printf( "\n" );
      s_mesg = ( SIMPLE_MESSAGE_PTR ) pak.data;
      last_recv_uin = Chars_2_DW( s_mesg->uin );
      Print_UIN_Name( Chars_2_DW( s_mesg->uin  ) );
      printf( " - Instant Message\a " );
      if ( Verbose )
         printf( " Type = %04x\t", Chars_2_Word( s_mesg->type ) );
      Time_Stamp();
      Do_Msg( sok, Chars_2_Word( s_mesg->type ), Chars_2_Word( s_mesg->len ),
           s_mesg->len + 2,last_recv_uin);
      ack_srv( sok, Chars_2_Word( pak.head.seq ) );
      if ( 0xfe != *( ((unsigned char *) s_mesg ) + sizeof( s_mesg ) ) )
      {
         if ( ( Current_Status != STATUS_ONLINE ) && ( auto_resp ) )
         {
            icq_sendmsg( sok, Chars_2_DW( s_mesg->uin ), auto_rep_str );
            printf( "[ Sent auto-reply message ]\n" );
         }
      }
      Prompt();
      break;
   default: /* commands we dont handle yet */
       printf( CLIENTCOL "\nThe response was %04X\t", Chars_2_Word( pak.head.cmd ) );
       printf( "The version was %X\t", Chars_2_Word( pak.head.ver ) );
       Time_Stamp();
       printf( "\nThe SEQ was %04X\t", Chars_2_Word( pak.head.seq ) );
       len = s - ( sizeof( pak.head ) - 2 );
       printf( "The size was %d\n", len );
       if ( Verbose )
       {
          for ( i=0; len > i; i++ )
          {
             printf( "%02X ",( unsigned char ) pak.data[i] );
             if ( i % 10 == 9 )
             {
                printf( "\n" );
             }
          }
       }
       printf( NOCOL "\n" );
       Prompt();
       ack_srv( sok, Chars_2_Word( pak.head.seq ) ); /* fake like we know what we're doing*/
       break;
   }
}

/********* NO LONGER USED ******************************/
/*******************************************************
Reads in the Contacts file
into the Contacts array.
********************************************************/
void Read_Contacts( char *file )
{
   FD_T fd;
   char *tmp;
   DWORD tmp_uin;
   char buf[ 32 ]; /* 10 digits uin + space + 20 char nickname + \000 */

   fd = open( file, O_RDONLY );
   if ( fd == -1 )
   {
      fprintf( stderr, "Open %s", file );
      perror( "" );
      Contact_List = FALSE;
      return;
   }
   Contact_List = TRUE;
   for ( ; ! M_fdnreadln( fd, buf, sizeof( buf ) ); )
   {
      if ( Num_Contacts == 100 )
         break;
      if ( ( buf[0] != '#' ) && ( buf[0] != 0 ) )
      {
         if ( isdigit( (int) buf[0] ) )
         {
            Contacts[ Num_Contacts ].uin = atoi( strtok( buf, " " ) );
            Contacts[ Num_Contacts ].status = STATUS_OFFLINE;
            Contacts[ Num_Contacts ].last_time = -1L;
            Contacts[ Num_Contacts ].current_ip = -1L;
            tmp = strtok( NULL, "" );
            if ( tmp != NULL )
               memcpy( Contacts[ Num_Contacts ].nick, tmp, sizeof( Contacts->nick )  );
            else
               Contacts[ Num_Contacts ].nick[0] = 0;
            if ( Contacts[ Num_Contacts ].nick[19] != 0 )
               Contacts[ Num_Contacts ].nick[19] = 0;
            if ( Verbose )
               printf( "%ld = %s\n", Contacts[ Num_Contacts ].uin, Contacts[ Num_Contacts ].nick );
            Num_Contacts++;
         }
         else
         {
            tmp_uin = Contacts[ Num_Contacts - 1 ].uin;
            tmp = strtok( buf, ", \t" ); /* aliases may not have spaces */
            for ( ; tmp!=NULL; Num_Contacts++ )
            {
               Contacts[ Num_Contacts ].uin = -tmp_uin;
               Contacts[ Num_Contacts ].status = STATUS_OFFLINE;
               Contacts[ Num_Contacts ].last_time = -1L;
               Contacts[ Num_Contacts ].current_ip = -1L;
               memcpy( Contacts[ Num_Contacts ].nick, tmp, sizeof( Contacts->nick )  );
               tmp = strtok( NULL, ", \t" );
            }
         }
      }
   }
   if ( Verbose )
      printf( "Total contacts on list %d\n", Num_Contacts );
   close( fd );
}

/**********************************************
Verifies that we are in the correct endian
***********************************************/
void Check_Endian( void )
{
   int i;
   char passwd[10];

   passwd[0] = 1;
   passwd[1] = 0;
   passwd[2] = 0;
   passwd[3] = 0;
   passwd[4] = 0;
   passwd[5] = 0;
   passwd[6] = 0;
   passwd[7] = 0;
   passwd[8] = 0;
   passwd[9] = 0;
   i = *  ( DWORD *) passwd;
   if ( i == 1 )
   {
      printf( "Using intel byte ordering.\n" );
      Int_End = TRUE;
   }
   else
   {
      printf( "Using motorola byte ordering.\n" );
      Int_End = FALSE;
   }
}

/******************************
Main function connects gets UIN
and passwd and logins in and sits
in a loop waiting for server responses.
******************************/

#ifdef AMIGA
struct Library *MiamiBase  = NULL;
struct Library *SocketBase = NULL;
int             retcode    = 0;
#endif

int main( int argc, char *argv[] )
{
   int sok;
   int i;
   int next;
   int time_delay = 120;
   struct timeval tv;
   fd_set readfds;
#ifdef _WIN32
   WSADATA wsaData;
#endif

   #ifdef AMIGA
    MiamiBase  = OpenLibrary("miami.library", 3);
    SocketBase = OpenLibrary(SOCKETNAME, 0);

    if( (!SocketBase) || (!MiamiBase) )
     {
      printf("\nYou have to start Miami first !\n");
      goto amiend;
     }
   #endif

   printf( SERVCOL "Matt's ICQ clone " NOCOL "compiled on %s %s\n" SERVCOL " Version 0.2a" NOCOL "\n", __TIME__, __DATE__ );
#ifdef FUNNY_MSGS
   printf( "No Mirabilis client was maimed, hacked, tortured, sodomized or otherwise harmed\nin the making of this utility.\n" );
#else
   printf( "This program was made without any help from Mirabilis or there consent.\n" );
   printf( "Not reverse engineering or decompilation of any Mirabilis code took place\nto make this program.\n" );
#endif
   Get_Config_Info();
   memset( serv_mess, FALSE, 1024 );
   if (argc > 1 )
   {
      for ( i=1; i< argc; i++ )
      {
         if ( argv[i][0] != '-' )
         ;
         else if ( (argv[i][1] == 'v' ) || (argv[i][1] == 'V' ) )
         {
            Verbose = ! Verbose;
         }
      }
   }
   Check_Endian();
#ifdef _WIN32
   i = WSAStartup( 0x0101, &wsaData );
   if ( i != 0 ) {
#ifdef FUNNY_MSGS
                perror("Windows Sockets broken blame Bill -");
#else
                perror("Sorry, can't initialize Windows Sockets...");
#endif
#ifdef AMIGA
            retcode = 1;
            goto amiend;
#else
            exit(1);
#endif
   }
#endif
   sok = Connect_Remote( server, remote_port, STDERR );
   if ( ( sok == -1 ) || ( sok == 0 ) )
   {
        printf( "Couldn't establish connection\n" );
#ifdef AMIGA
            retcode = 1;
            goto amiend;
#else
            exit(1);
#endif
   }
   Login( sok, UIN, &passwd[0], our_ip, our_port );
   next = time( NULL );
   next += 60;
   Prompt();
   for ( ; !Quit; )
   {
#ifdef UNIX
          tv.tv_sec = 2;
      tv.tv_usec = 500000;
#else
          tv.tv_sec = 0;
      tv.tv_usec = 100000;
#endif

      FD_ZERO(&readfds);
      FD_SET(sok, &readfds);
#ifndef _WIN32
      FD_SET(STDIN, &readfds);
#endif

      /* don't care about writefds and exceptfds: */
      select(sok+1, &readfds, NULL, NULL, &tv);

      if (FD_ISSET(sok, &readfds))
          Handle_Server_Response( sok );
#if _WIN32
          else if (_kbhit())            /* sorry, this is a bit ugly...   [UH]*/
#else
          else if (FD_ISSET( STDIN, &readfds ) )
#endif
      {
         Get_Input( sok );
      }
/*          printf("Timed out.\n");*/
      if ( time( NULL ) > next )
      {
         next = time( NULL ) + time_delay;
         Keep_Alive( sok );
      }
   }
   Quit_ICQ( sok );

#ifdef AMIGA
amiend:
   if(SocketBase) CloseLibrary((struct Library *) SocketBase);
   if(MiamiBase)  CloseLibrary((struct Library *) MiamiBase);

   exit(retcode);
#else
   return 0;
#endif
}
