/*
 * mgr_user.c - user commands managing
 *
 * Copyright (C) 2003   Rodolfo Giometti <giometti@linux.com>
 *
 *   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.                  
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

#include "c6term.h"
#include "c6stp.h"

/* --- Public variables ----------------------------------------------------- */

int client_count;

/* --- Local variables ------------------------------------------------------ */

static char *user_cmd_name[] = {
   "connect", "login",   "logout",  "add",  "remove", "", "", "",
   "message", "",        "status",  "pong", "",       "", "", "",
   "nick",    "",        "",        "",     "",       "", "", "",
   "quit",    "version", "",        "",     "",       "", "", "",
   NULL,
};
enum {
   CONNECT 		= 0x00,
   LOGIN 		= 0x01,
   CLIENT_REQ_EXIT 	= 0x02,		/* logout */
   REQ_USERS 		= 0x03,		/* list connected users */
   DEL_USERS 		= 0x04,		/* unlist connected users */
   OL_MESSAGE 		= 0x08,		/* on line message */
   CHNG_STATUS 		= 0x0a,
   PONG 		= 0x0b,
   NICK 		= 0x10,
   QUIT 		= 0x18,
   VERSION 		= 0x19,
};

/* --- Local function ------------------------------------------------------- */

void do_connect(char *addr, char* sport)
{
   struct hostent *hostent_s;
   struct sockaddr_in sock_addr;

   /* Get the remote address */
   if ((hostent_s = gethostbyname(addr)) == NULL) {
      MESSAGE(INVALID_HOST, addr);
      return;
   }

   /* Get the port number */
   if (sscanf(sport, "%d", &port) < 1 || port < 1 || port > 65535) {
      MESSAGE(INVALID_PORT, port);
      return;
   }

   /* Prepare the TCP connection */
   bzero(&sock_addr, sizeof(sock_addr));
   sock_addr.sin_family = AF_INET;
   memcpy(&sock_addr.sin_addr, hostent_s->h_addr, sizeof(int));
   sock_addr.sin_port = htons(port);
   if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
      MESSAGE(GENERIC_COMMUNICATION_ERROR);
      return;
   }

   /* Wait for connection... */
   PDEBUG("Login server connection request sent...");
   if (connect(sock, (struct sockaddr *) &sock_addr, sizeof(sock_addr)) < 0) {
      if (errno == ETIMEDOUT)
         MESSAGE(CONNECTION_TIMEOUT);
      else
         MESSAGE(CONNECTION_REFUSED);
      PDEBUG("connect: %m");
      return;
   }

   /* ... done! :)
      Must save host name */
   if (host)
      free(host);
   host = strdup(addr);
   if (host == NULL) {
      MESSAGE(CANNOT_GET_MEMORY);
      exit(EXIT_FAILURE);
   }

   status = ONLINE;
}

static void send_packet(char *pkt, int len)
{
   struct pkt_s {
      unsigned char id;
      unsigned char cmd;
      unsigned short count;
      unsigned short len;
      unsigned char data[len];
   } __attribute__ ((packed)) s_pkt;

   s_pkt.id = 0x10;
   s_pkt.cmd = 0x0f;
   s_pkt.count = htons(client_count);
   s_pkt.len = htons(len);
   PDEBUG("sending: id %#04x - cmd %#04x - cnt %d - len %d",
          s_pkt.id, s_pkt.cmd, ntohs(s_pkt.count), ntohs(s_pkt.len));

   packet_encode(s_pkt.data, pkt, len);

   if (writen(sock, &s_pkt, sizeof(struct pkt_s)) < 0) {
      MESSAGE(CANNOT_SEND_PACKET);
      PDEBUG("cannot send data to server");
      exit(EXIT_FAILURE);
   }

   client_count++;
}

static int do_login_packet(char *nick, char *pass, unsigned char **pkt, int *len)
{
   int nick_l = strlen(nick);
   int pass_l = strlen(pass);
   struct sockaddr_in sock_addr;
   int sock_len = sizeof(sock_addr);
   struct login_pkt_s {
      unsigned char id;
      unsigned char cmd;
      unsigned short count;
      unsigned short len;
      unsigned char nick_l;
      char nick[nick_l];
      unsigned char pass2_l;
      char pass2[16];
      unsigned char nick2_l;
      char nick2[16];
      unsigned char unknown1[2];
      unsigned int ip;
      unsigned char unknown2[2];
   } __attribute__ ((packed)) *ptr;

   /* Some sanity checks on nick and user password length*/
   if (nick_l > 16 || nick_l < 1 || pass_l > 16 || pass_l < 1) {
      MESSAGE(BAD_USER);
      return -1;
   }

   /* Save the user data since we need them later... */
   strncpy(user, nick, nick_l+1);
   strncpy(passwd, pass, pass_l+1);
   PDEBUG("nick: %s", nick);
   PDEBUG("pass: %s", pass);

   *len = sizeof(struct login_pkt_s);
   if ((ptr = (struct login_pkt_s *) *pkt = malloc(*len)) == NULL) {
      MESSAGE(CANNOT_GET_MEMORY);
      PDEBUG("cannot get memory to store received packet");
      exit(EXIT_FAILURE);
   }

   if (getsockname(sock, (struct sockaddr *) &sock_addr, &sock_len) < 0) {
      MESSAGE(GENERIC_COMMUNICATION_ERROR);
      return -2;   /* signal to reset internal status */
   }

   ptr->id = 0x10;
   ptr->cmd = LOGIN;
   ptr->count = htons(client_count);
   ptr->len = htons(*len);
   ptr->nick_l = (unsigned char) nick_l;
   strncpy(ptr->nick, nick, nick_l);
   ptr->pass2_l = 16;
   nickpass_encode(pass, ptr->pass2, 1);
   ptr->nick2_l = 16;
   nickpass_encode(nick, ptr->nick2, 0);
   ptr->unknown1[0] = 0x01;
   ptr->unknown1[1] = 0x2a;
   ptr->ip = sock_addr.sin_addr.s_addr;
   ptr->unknown2[0] = 0x00;
   ptr->unknown2[1] = 0x14;

   if (status == STEP2)
      status = STEP3;

   return 0;
}

static void do_req_users_packet(unsigned char cmd, int argc, char **argv, unsigned char **pkt, int *len)
{
   struct req_users_pkt_s {
      unsigned char id;
      unsigned char cmd;
      unsigned short count;
      unsigned short len;
      unsigned short n_nick;
      char list0;
   } __attribute__ ((packed)) *ptr;
   int i, l, error = 0;
   unsigned char *p;

   /* Compute packet dimension then allocate memory (and do a sanity check */
   *len = 0;
   for (i = 0; i < argc; i++) {
      if ((l = strlen(argv[i])) > 16) {
         MESSAGE(INVALID_NICK, argv[i]);
         error = 1;
      }
      *len += 1+l;
   }
   if (error)
      return;

   *len += sizeof(struct req_users_pkt_s)-1;
   if ((ptr = (struct req_users_pkt_s *) *pkt = malloc(*len)) == NULL) {
      MESSAGE(CANNOT_GET_MEMORY);
      exit(EXIT_FAILURE);
   }

   ptr->id = 0x10;
   ptr->cmd = cmd;
   ptr->count = htons(client_count);
   ptr->len = *len+2;
   ptr->n_nick = htons(argc);
   p = &ptr->list0;
   for (i = 0; i < argc; i++) {
      *p = (unsigned char) l = strlen(argv[i]);
      p++;
      memcpy(p, argv[i], l);
      p += l;

      if (cmd == REQ_USERS)
         MESSAGE(NETFRIEND_ADDED, argv[i]);
      else
         MESSAGE(NETFRIEND_REMOVED, argv[i]);
   }
}

static int do_message_packet(unsigned char type, char *msg, unsigned char **pkt, int *len)
{
   int user_l = strlen(user);
   int nick_l = strlen(tonick);
   int msg_l = strlen(msg);
   struct message_pkt_s {
      unsigned char id;
      unsigned char cmd;
      unsigned short count;
      unsigned short len;
      unsigned char user_l;
      char user[user_l];
      unsigned char unknown1;
      unsigned char unknown2;
      unsigned char nick_l;
      char nick[nick_l];
      unsigned short msg_l;
      unsigned char mode;
      unsigned char unknown3;
      char msg0;      
   } __attribute__ ((packed)) *ptr;

   /* Some sanity checks on nick and user password length*/
   if (msg_l > 512 || msg_l < 1)
      return -1;

   PDEBUG("user: %s", user);
   PDEBUG("nick: %s", tonick);
   PDEBUG("msg: %s", msg);

   *len = sizeof(struct message_pkt_s)+msg_l-1;
   if ((ptr = (struct message_pkt_s *) *pkt = malloc(*len)) == NULL) {
      MESSAGE(CANNOT_GET_MEMORY);
      PDEBUG("cannot get memory to execute command");
      exit(EXIT_FAILURE);
   }

   ptr->id = 0x10;
   ptr->cmd = type;
   ptr->count = htons(client_count);
   ptr->len = htons(*len);
   ptr->user_l = (unsigned char) user_l;
   strncpy(ptr->user, user, user_l);
   ptr->unknown1 = 0x00;
   ptr->unknown2 = 0x01;
   ptr->nick_l = (unsigned char) nick_l;
   strncpy(ptr->nick, tonick, nick_l);
   ptr->msg_l = htons(msg_l+2);
   ptr->mode = 0x02;   /* mormal message */
   ptr->unknown3 = 0x00;
   strncpy(&ptr->msg0, msg, msg_l);

   return 0;
}

static int do_status_packet(char *opt, unsigned char **pkt, int *len)
{
   struct status_pkt_s {
      unsigned char id;
      unsigned char cmd;
      unsigned short count;
      unsigned short len;
      unsigned char status;
   } __attribute__ ((packed)) *ptr;

   *len = sizeof(struct status_pkt_s);
   if ((ptr = (struct status_pkt_s *) *pkt = malloc(*len)) == NULL) {
      MESSAGE(CANNOT_GET_MEMORY);
      PDEBUG("cannot get memory to execute command");
      exit(EXIT_FAILURE);
   }

   ptr->id = 0x10;
   ptr->cmd = CHNG_STATUS;
   ptr->count = htons(client_count);
   ptr->len = htons(1);

   /* Setup the status byte */
   ptr->status = 0;
   if (strcmp(opt, "available") == 0)
      ptr->status = AVAILABLE;
   else if (strcmp(opt, "netfriends") == 0)
      ptr->status = NETFRIENDS;
   else if (strcmp(opt, "busy") == 0)
      ptr->status = BUSY;
   else
      return -1;	/* invalid */
   ptr->status |= CLIENT_ONLINE;

   return 0;
}

static int do_pong_packet(unsigned char **pkt, int *len)
{
   struct pong_pkt_s {
      unsigned char id;
      unsigned char cmd;
      unsigned short count;
      unsigned short len;
   } __attribute__ ((packed)) *ptr;

   *len = sizeof(struct pong_pkt_s);
   if ((ptr = (struct pong_pkt_s *) *pkt = malloc(*len)) == NULL) {
      MESSAGE(CANNOT_GET_MEMORY);
      PDEBUG("cannot get memory to execute command");
      exit(EXIT_FAILURE);
   }

   ptr->id = 0x10;
   ptr->cmd = PONG;
   ptr->count = htons(client_count);
   ptr->len = htons(0);

   /* Now we have to change status */
   status = LOGGED;

   return 0;
}

/* --- Public function ------------------------------------------------------ */

void manage_user_command(void)
{
   static char *cmd = NULL, *p, *msg;
   static int len = 0;
   int n;
   static char **argv = NULL;
   static int argc;

   int pos;
   char *opt;

   unsigned char *packet = NULL;
   int packet_l;

   int i;
   

   do {
      /* Read user command but not if we are on logging in step 2 or pong,
         in this case we reforce login */
      if (status != STEP2 && status != DO_PONG) {
         n = getline(&cmd, &len, stdin);
         if (n < 0) {
            if (!feof(stdin))
               break;				  /* nothing to read */
            if (status == OFFLINE)
               exit(EXIT_SUCCESS);                /* quit */

            MESSAGE(CANNOT_READ_COMMANDS);	  /* else, error! */
            exit(EXIT_FAILURE);
         }

         p = index(cmd, '\n');
         if (p) {
            if (p > 0 && *(p-1) == '\r')   /* remove also the sequence "\r\n" */
               p--;
            *p = '\0';
            n = strlen(cmd);
         }
      }
      else {
         free(cmd);
   
         if (status == STEP2)
            n = len = asprintf(&cmd, "%s %s %s%c",
                               user_cmd_name[LOGIN], user, passwd, '\0');
         else
            n = len = asprintf(&cmd, "%s%c", user_cmd_name[PONG], '\0');
   
         if (len < 0) {
            MESSAGE(CANNOT_GET_MEMORY);
            exit(EXIT_FAILURE);
         }
      }
      if (n == 0)
         return;
   
      /* Convert it into a Unix-style argument vector */
      if (unix_style_conv(cmd, &argc, &argv) < 0) {
         MESSAGE(INVALID_COMMAND);   /* FIXME: does it need new error message? */
         return;
      }
   
      /* Decode it */
      i = -1;
      if (argc > 0) {
         for (i = 0; user_cmd_name[i]; i++)
            if (strncmp(argv[0], user_cmd_name[i], n) == 0)
               break;
      }
   
      if (i >= 0 && user_cmd_name[i]) {
         /* The big switch */
         switch (i) {
            case CONNECT : {
               /* Command syntax: connect <host> <port> */
               if (argc < 3) {
                  MESSAGE(INVALID_COMMAND);
                  PERR("usage: connect <host> <port>");
                  break;
               }
   
               /* No "connect" if status != OFFLINE */
               if (status != OFFLINE) {
                  MESSAGE(ALREADY_CONNECTED);
                  break;
               }
   
               PDEBUG("CONNECT");
   
               /* Do the job */
               do_connect(argv[1], argv[2]);
   
               break;
            }
            case LOGIN : {
               /* Command syntax: login <user> <passwd> */
               if (argc < 3) {
                  MESSAGE(INVALID_COMMAND);
                  PERR("usage: login <user> <passwd>");
                  break;
               }
   
               /* No "login" if status != ONLINE */
               if (status != ONLINE && status != STEP2) {
                  if (status == OFFLINE)
                      MESSAGE(NOT_CONNECTED);
                  if (status == LOGGED)
                      MESSAGE(ALREADY_LOGGED, user);
                  break;
               }
   
               PDEBUG("LOGIN");
   
               /* Do the job */
               if (do_login_packet(argv[1], argv[2], &packet, &packet_l) == -2) {
                  /* In this case we must reset the internal status */
                  reset_status();
               }
   
               break;
            }
            case CLIENT_REQ_EXIT : {
               /* Command syntax: logout */
   
               /* No "logout" if status != LOGGED */
               if (status != LOGGED) {
                  MESSAGE(NOT_LOGGED);
                  break;
               }
   
               PDEBUG("LOGOUT");
   
               /* Mmm... doing like this is simpler! ;-p */
               reset_status();
   
               MESSAGE(CONNECTION_CLOSED);
   
               break;
            }
            case REQ_USERS : {
               /* Command syntax: add <user> [user ...] */
               if (argc < 2) {
                  MESSAGE(INVALID_COMMAND);
                  PERR("usage: add <user> [user ...]");
                  break;
               }
   
               /* No "add" if status != LOGGED */
               if (status != LOGGED) {
                  MESSAGE(NOT_LOGGED);
                  break;
               }
   
               PDEBUG("REQ_USERS");
   
               do_req_users_packet(REQ_USERS, argc-1, &argv[1],
                                      &packet, &packet_l);
   
               break;
            }
            case DEL_USERS : {
               /* Command syntax: remove <user> [user ...] */
               if (argc < 2) {
                  MESSAGE(INVALID_COMMAND);
                  PERR("usage: remove <user> [user ...]");
                  break;
               }
   
               /* No "remove" if status != LOGGED */
               if (status != LOGGED) {
                  MESSAGE(NOT_LOGGED);
                  break;
               }
   
               PDEBUG("DEL_USERS");
   
               do_req_users_packet(DEL_USERS, argc-1, &argv[1],
                                      &packet, &packet_l);
   
               break;
            }
            case OL_MESSAGE : {
               /* Command syntax: message <msg> */
               if (argc < 2) {
                  MESSAGE(INVALID_COMMAND);
                  PERR("usage: message <user> <message>");
                  break;
               }
   
               /* No "message" if status != LOGGED */
               if (status != LOGGED) {
                  MESSAGE(NOT_LOGGED);
                  break;
               }
   
               PDEBUG("OL_MESSAGE");
   
               /* No "message" if no receiver specified */
               if (strcmp(tonick, "") == 0) {
                  MESSAGE(NO_SUCH_RECEIVER);
                  break;
               }
   
               sscanf(cmd, "%*s%n", &pos);
               msg = unescape(cmd+pos);
               do_message_packet(OL_MESSAGE, msg, &packet, &packet_l);
               free(msg);
   
               /* The remote server send us no ack but the C6STP needs
                  an acknowledge */
               MESSAGE(MESSAGE_SENT);
   
               break;
            }
            case CHNG_STATUS : {
               /* Command syntax: status [available | netfriends | busy] */
               if (argc < 2 || sscanf(cmd, "%*s %as", &opt) < 1) {
                  MESSAGE(INVALID_COMMAND);
                  PERR("usage: [available | netfriends | busy]");
                  break;
               }
               if (strcmp(opt, "available") && strcmp(opt, "netfriends") &&
                   strcmp(opt, "busy")) {
                  MESSAGE(INVALID_STATUS, opt);
                  free(opt);
                  break;
               }

               /* No "status" if status != LOGGED */
               if (status != LOGGED) {
                  MESSAGE(NOT_LOGGED);
                  free(opt);
                  break;
               }

               PDEBUG("STATUS");

               do_status_packet(opt, &packet, &packet_l);

               MESSAGE(STATUS_SELECTED, opt);
               free(opt);

               break;
            }
            case PONG : {
               /* Command syntax: pong */
   
               /* No "pong" if status != DO_PONG */
               if (status != DO_PONG) {
                  /* FIXME: should I return something?? :-o */
                  PDEBUG("cannot pong from command line");
                  break;
               }
   
               PDEBUG("PONG");
   
               do_pong_packet(&packet, &packet_l);
   
               break;
            }
            case NICK : {
               /* Command syntax: nick <nick> */
               if (argc < 2) {
                  MESSAGE(INVALID_COMMAND);
                  PERR("usage: nick <nick>");
                  break;
               }
   
               /* No "nick" if status != LOGGED */
               if (status != LOGGED) {
                  MESSAGE(NOT_LOGGED);
                  break;
               }
   
               PDEBUG("NICK");
   
               /* Check for invalid nick */
               if (strlen(argv[1]) > 16) {
                  MESSAGE(INVALID_NICK, argv[1]);
                  break;
               }
   
               /* Store the nick name where we heve to send next message */
               strcpy(tonick, argv[1]);
   
               MESSAGE(RECEIVER_SELECTED, tonick);
   
               break;
            }
            case QUIT : {
               /* Command syntax: quit */
               exit(EXIT_SUCCESS);
   
               break;
            }
            case VERSION : {
               /* Command syntax: version */
               MESSAGE(INFO_VERSION);
   
               break;
            }
            default : {
               /* Do nothing... */
            }
         }
      
         if (packet) {
            /* Send the packet */
            send_packet(packet, packet_l);
   
            free(packet);
            packet = NULL;
         }
      }
      else
         MESSAGE(INVALID_COMMAND);

      /* Watch out!!! This is a __very__ dirty trick and the code is strongly
         not portable... but we already force to use glibc! ;) */
      PDEBUG("_IO_read_ptr %p", stdin->_IO_read_ptr);
      PDEBUG("_IO_read_end %p", stdin->_IO_read_end);
   } while (stdin->_IO_read_end-stdin->_IO_read_ptr != 0);
}

