/**
 * Scout - The Amiga System Monitor
 *
 *------------------------------------------------------------------
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * You must not use this source code to gain profit of any kind!
 *
 *------------------------------------------------------------------
 *
 * @author Andreas Gelhausen
 * @author Richard Körber <rkoerber@gmx.de>
 */

#include "system_headers.h"

extern APTR    AP_Scout;
extern STRPTR  _ProgramName;

/********************************************************************/
/*                      Benötigte Prototypes                        */
/********************************************************************/

#define NAMELEN      32
#define LINELEN      82
#define TRENNCHAR    '\0'

UBYTE *CMD_BEGIN = "BEGIN";
UBYTE *CMD_USER = "USER";
UBYTE *CMD_PASSWORD = "PASSWORD";
UBYTE *CMD_END = "END";
UBYTE *CMD_DONE = "DONE";
UBYTE *CMD_ERROR = "ERROR";
UBYTE *CMD_CONNECTED = "CONNECTED";

static struct RDArgs *rdargs;
static struct RDArgs *myrdargs;

static int server_socket = -1;
static int client_socket = -1;
static int connected = FALSE;
static long type = SOCK_STREAM;

static long sinlen;
static struct sockaddr_in sin;

BOOL clientstate = FALSE;
BOOL serverstate = FALSE;
BOOL shellstate = FALSE;

char username[10+1];
char password[_PASSWORD_LEN+1];

void failtcp( void )
{
    if (client_socket >= 0) {
        if (connected) shutdown(client_socket, 2);
        CloseSocket(client_socket);
    }

    if (UserGroupBase) CloseLibrary(UserGroupBase);

    if (SocketBase) CloseLibrary(SocketBase);
}

int inittcp( void )
{
    if (!(SocketBase = OpenLibrary("bsdsocket.library", 4L))) return FALSE;
    if (!(UserGroupBase = OpenLibrary("usergroup.library", 4))) return FALSE;

    return TRUE;
}

static int sgetc( int sock )
{
    unsigned char c;
    fd_set rd,ex;
    ULONG flgs;
    int n;
    struct timeval t;

    t.tv_sec = 10L;
    t.tv_usec = 0;

    FD_ZERO(&rd);
    FD_ZERO(&ex);

    FD_SET(sock,&rd);
    FD_SET(sock,&ex);

    if (clientstate) {
        flgs = 0;
        WaitSelect(16,&rd,0L,&ex,&t,&flgs);
    } else {
        flgs = SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D;
        WaitSelect(16,&rd,0L,&ex,NULL,&flgs);
    }

    if (FD_ISSET(sock,&rd)) {
        n = recv(sock, &c, 1, 0);
        if (n == 1) {
            return c;
        } else {
            return -1;
        }
    } else {
        return -1;
    }
}

int sgets( int sock,
           char *string,
           int maxchars )
{
    int nchars = 0;
    char c;

    while (nchars < maxchars && (c = sgetc(sock)) != EOF && c != TRENNCHAR) {
        string[nchars] = c;
        nchars++;
    }
    string[nchars] = '\0';
    return nchars;
}

static char NetHelpText[] = "\n" \
    " Available lists:\n" \
    " ----------------\n" \
    " Allocations     Assigns         BoopsiClasses   Commands\n" \
    " Commodities     Devices         Expansions      Fonts\n" \
    " InputHandlers   Interrupts      Libraries       Locks\n" \
    " LowMemory       Memory          Mounts          Ports\n" \
    " Residents       Resources       ScreenMode      Semaphores\n" \
    " System          Tasks           Timer           Vectors\n" \
    " Windows\n\n" \
    " Other commands:\n" \
    " ---------------\n" \
    " Help";

void PrintNetHelp( void )
{
    if (serverstate || shellstate) {
        int i = 0;
        UBYTE *tmp;

        if (tmp = AllocVec(256, MEMF_ANY)) {
            SendResultString(NetHelpText);

            while (arexx_list[i].mc_Name) {
               strcpy(tmp, " ");
               strcat(tmp, arexx_list[i].mc_Name);
               if (arexx_list[i].mc_Parameters) {
                   strcat (tmp, " ");
                   strcat (tmp, arexx_list[i].mc_Template);
               }
               SendResultString(tmp);
               i++;
            }

            FreeVec(tmp);
        }
    }
}

/*
** Dieser Hook wird benötigt um die ARexx-Command-Liste für
**  die TCP_Befehle nutzen zu können!
*/
struct NetHook {
   struct   MinNode  h_egal1;
   __asm    ULONG    (*h_Entry) (register __a1 ULONG *);
   ULONG    (*h_egal2)();
   APTR     h_egal3;
};

struct Net_Command {
    char *nc_Name;
    void (*nc_func)(char *);
};

struct Net_Command net_list[] = {
    "Allocations",         PrintAllocations,
    "Assigns",             PrintAssigns,
    "BoopsiClasses",       PrintClass,
    "Commands",            PrintCommands,
    "Commodities",         PrintCx,
    "Devices",             PrintDevices,
    "Expansions",          PrintExpansions,
    "Fonts",               PrintFonts,
    "InputHandlers",       PrintInputHandlers,
    "Interrupts",          PrintInterrupts,
    "Libraries",           PrintLibraries,
    "LowMemory",           PrintLowMemory,
    "Memory",              PrintMemory,
    "Mounts",              PrintMounts,
    "Locks",               PrintLocks,
    "Ports",               PrintPorts,
    "Residents",           PrintResidents,
    "ScreenMode",          PrintSMode,
    "Semaphores",          PrintSemaphores,
    "System",              PrintSystem,
    "Tasks",               PrintTasks,
    "Timer",               PrintTimer,
    "Resources",           PrintResources,
    "Vectors",             PrintVectors,
    "Windows",             PrintWindows,
    "Patches",             PrintPatches,
    "Catalogs",            PrintCatalogs,
    "AudioModes",          PrintAudioModes,
    "ResetHandlers",       PrintResetHandlers,
    "Help",                (void (* ) (char *)) PrintNetHelp,
    "a",                   PrintAllocations,
    "b",                   PrintClass,
    "c",                   PrintCommands,
    "d",                   PrintDevices,
    "e",                   PrintTimer,
    "f",                   PrintFonts,
    "g",                   PrintAssigns,
    "h",                   PrintInputHandlers,
    "i",                   PrintInterrupts,
    "j",                   PrintLowMemory,
    "k",                   PrintCx,
    "l",                   PrintLibraries,
    "m",                   PrintMemory,
    "n",                   PrintMounts,
    "o",                   PrintLocks,
    "p",                   PrintPorts,
    "r",                   PrintResidents,
    "s",                   PrintSemaphores,
    "t",                   PrintTasks,
    "u",                   PrintResources,
    "v",                   PrintVectors,
    "w",                   PrintWindows,
    "x",                   PrintExpansions,
    "y",                   PrintSystem,
    "z",                   PrintSMode,
    "GetAlcList",          (void (* ) (char *))SendAlcList,
    "GetAssList",          (void (* ) (char *))SendAssList,
    "GetClassList",        (void (* ) (char *))SendClassList,
    "GetComList",          (void (* ) (char *))SendComList,
    "GetCxList",           (void (* ) (char *))SendCxList,
    "GetDevList",          (void (* ) (char *))SendDevList,
    "GetExpList",          (void (* ) (char *))SendExpList,
    "GetFontList",         (void (* ) (char *))SendFontList,
    "GetInputList",        (void (* ) (char *))SendInputList,
    "GetIntList",          (void (* ) (char *))SendIntList,
    "GetLibList",          (void (* ) (char *))SendLibList,
    "GetLockList",         (void (* ) (char *))SendLockList,
    "GetLowMemList",       (void (* ) (char *))SendLowMemory,
    "GetMemList",          (void (* ) (char *))SendMemList,
    "GetMountList",        (void (* ) (char *))SendMountList,
    "GetPortList",         (void (* ) (char *))SendPortList,
    "GetResList",          (void (* ) (char *))SendResList,
    "GetResiList",         (void (* ) (char *))SendResiList,
    "GetSemList",          (void (* ) (char *))SendSemList,
    "GetSModeList",        (void (* ) (char *))SendSModeList,
    "GetSysList",          (void (* ) (char *))SendSystemList,
    "GetTaskList",         (void (* ) (char *))SendTaskList,
    "GetTimerList",        (void (* ) (char *))SendTimerList,
    "GetVectorList",       (void (* ) (char *))SendVectorList,
    "GetWinList",          (void (* ) (char *))SendWinList,
    "GetPatchList",        (void (* ) (char *))SendPatchList,
    "GetCatalogList",      (void (* ) (char *))SendCatalogList,
    "GetAudioModeList",    (void (* ) (char *))SendAudioModeList,
    "GetResetHandlerList", (void (* ) (char *))SendResetHandlersList,
    NULL
};

/********************************************************************/
/*                              Client                              */
/********************************************************************/

int SendDaemon( UBYTE *fmt, ... )
{
    int result = FALSE;
    UBYTE *buf;

    if (buf = tbAllocVecPooled(globalPool, LINELEN)) {
        va_list args;

        va_start(args,fmt);
        _vsnprintf(buf, LINELEN, fmt, args);
        va_end (args);

        if (send(client_socket, buf, strlen(buf) + 1, 0) == strlen(buf) + 1) {
            result = TRUE;
        }

        tbFreeVecPooled(globalPool, buf);
    }

    return result;
}

static ULONG NetCommand( UBYTE *text )
{
    ULONG rc = RETURN_FAIL;
    UBYTE *buf;

    if (buf = tbAllocVecPooled(globalPool, LINELEN)) {
        if (send(client_socket, text, strlen(text) + 1, 0) == strlen(text) + 1) {
            while (sgets(client_socket, buf, LINELEN)) {
                if (strcmp(buf, CMD_DONE) == 0) {
                    rc = RETURN_OK;
                    break;
                } else if (strcmp(buf, CMD_ERROR) == 0) {
                    break;
                }
            }
        }

        tbFreeVecPooled(globalPool, buf);
    }

    return rc;
}

BOOL ReceiveDecodedEntry( UBYTE *structure,
                          ULONG length )
{
    BOOL rc = FALSE;

    if (sgets(client_socket, structure, length)) {
        if (strcmp(structure, CMD_ERROR) != 0 && strcmp(structure, CMD_DONE) != 0) {
            ULONG i = 0;

            while (i < length - 1) {
                if (structure[i] == '\1') structure[i] = '\0';
                i++;
            }

            rc = TRUE;
        }
    }

    return rc;
}

int ConnectToServer( void )
{
   UBYTE hostname[NAMELEN];
   UBYTE buffer[LINELEN+2];
   struct hostent *host;
   struct sockaddr_in server;

   if (!inittcp()) {
       aprintf(msgCantInitTCPIP);
       return FALSE;
   }

   if (opts.User) {
       stccpy(username, opts.User, sizeof(username));
   } else {
       stccpy(username, getlogin(), sizeof(username));
   }

   if (opts.Password) {
       stccpy(password, opts.Password, sizeof(password));
   } else {
       stccpy(password, getpass(msgAskForPassword), sizeof(password));
       if (password[0] == '\0') {
           aprintf(msgCantGetPassword);
           return FALSE;
       }
   }

   gethostname(hostname, sizeof(hostname));

   if (host = gethostbyname(opts.Host)) {
       bzero((char *)&server, sizeof(server));
       server.sin_port = 6543;
       bcopy(host->h_addr, (char *)&server.sin_addr, host->h_length);
       server.sin_family = host->h_addrtype;

       client_socket = socket(AF_INET, type, 0);
       if (client_socket >= 0) {
           if (connect (client_socket, (struct sockaddr *) &server, sizeof (server)) != -1) {
               connected = TRUE;
               if (SendDaemon(CMD_BEGIN)) {
                   if (SendDaemon("%s %s", CMD_USER, username) && SendDaemon("%s %s", CMD_PASSWORD, password)) {
                       if (sgets(client_socket, buffer, LINELEN) && strcmp(buffer, CMD_CONNECTED) != 0) {
                          aprintf(buffer);
                      } else {
                          return TRUE;
                      }
                   }
               }
           } else {
              aprintf(msgCantConnectToServer);
           }
       } else {
           aprintf(msgCantCreateSocket);
       }
   } else {
       aprintf(msgCantFindHost, opts.Host);
   }
   return FALSE;
}

ULONG netshellclient( void )
{
    ULONG rc = RETURN_FAIL;
    UBYTE recvbuffer[LINELEN+2];

    if (clientstate = ConnectToServer()) {
        if (SendDaemon("%s ", opts.Command)) {
            while (sgets(client_socket, recvbuffer, LINELEN) && strcmp(recvbuffer, CMD_ERROR) != 0 && strcmp(recvbuffer, CMD_DONE) != 0) {
               aprintf(recvbuffer);
            }
            if (strcmp(recvbuffer, CMD_DONE) == 0) {
                rc = RETURN_OK;
            } else {
                rc = RETURN_ERROR;
            }
            SendDaemon(CMD_END);
        }
    }

    return rc;
}

/********************************************************************/
/*                              Daemon                              */
/********************************************************************/

int isNetCall (void) {
   int   rc = TRUE;

   if (! inittcp()) {
      return (FALSE);
   }

   server_socket = init_inet_daemon();
   if (server_socket >= 0) {
      set_socket_stdio (server_socket);
      sinlen = sizeof (sin);
      if (getpeername (0, (struct sockaddr *) &sin, &sinlen) == -1) {
//         logprint ("scout: getpeername() failed\n");
         rc = FALSE;
      }
   } else {
//      logprint ("scout: init_inet_daemon() failed\n");
      rc = FALSE;
   }
   return (rc);
}

int isCommand (char *buffer, char *command) {
   int len = strlen (command);
   int rc = FALSE;

   if ((strnicmp (buffer, command, len) == 0) \
     && ((isspace (buffer[len])) || (buffer[len] == '\0'))) {
      rc = len + 1;
   }
   return (rc);
}

int SendClient (char *fmt, ...)
{
    int   result = FALSE;
    UBYTE *buf;

    if (buf = tbAllocVecPooled(globalPool, TMP_STRING_LENGTH)) {
        va_list args;

        va_start(args,fmt);
        _vsnprintf(buf, TMP_STRING_LENGTH, fmt, args);

        if (send(0, buf, strlen(buf) + 1, 0) == strlen(buf) + 1) {
            result = TRUE;
        }

        va_end(args);

        tbFreeVecPooled(globalPool, buf);
    }

    return result;
}

BOOL SendEncodedEntry( UBYTE *structure, ULONG length )
{
    ULONG i = 0;

    while (i < length - 1) {
        if (structure[i] == '\0') structure[i] = '\1';
        i++;
    }

    if (SendClient(structure)) {
        return FALSE;
    }

    return TRUE;
}

long SendResultString( UBYTE *fmt, ... )
{
   UBYTE *buf;

   if (buf = tbAllocVecPooled(globalPool, TMP_STRING_LENGTH)) {
       va_list args;

       va_start(args, fmt);
       _vsnprintf(buf, TMP_STRING_LENGTH, fmt, args);

       if (serverstate) {
           strcat(buf, "\n");
           SendClient(buf);
       } else if (shellstate) {
           strcat(buf, "\n");
           Printf(buf);
       } else {
           set(AP_Scout, MUIA_Application_RexxString, buf);
       }

       va_end (args);

       tbFreeVecPooled(globalPool, buf);
    }

    return (TRUE);
}

void PrintFOneLine( BPTR hd,
                    UBYTE *fmt, ... )
{
    UBYTE *buf;

    if (buf = tbAllocVecPooled(globalPool, TMP_STRING_LENGTH)) {
        va_list args;

        va_start(args, fmt);
        _vsnprintf(buf, TMP_STRING_LENGTH, fmt, args);
        va_end(args);

        if (serverstate) {
            SendClient(buf);
        } else {
            Write(hd, buf, strlen(buf));
        }

        tbFreeVecPooled(globalPool, buf);
    }
}

ULONG ExecuteCommand (char *text) {
   ULONG    rc = RETURN_OK;
   char     buffer[256];
   int      i, len;

   #define  CMDOPT_TEMPLATE "CMD1/A"
   #define  CMDOPT_COUNT    4
   LONG     cmdopts[CMDOPT_COUNT];

   _snprintf(buffer, sizeof(buffer), "%s ", text);

   i = 0;
   while (arexx_list[i].mc_Name) {
      if (len = isCommand (buffer, arexx_list[i].mc_Name)) {

         if (myrdargs = AllocDosObject (DOS_RDARGS,NULL)) {
            myrdargs->RDA_Source.CS_Buffer = buffer + len;
            myrdargs->RDA_Source.CS_Length = strlen (buffer) - len;

            if ((arexx_list[i].mc_Parameters && (rdargs = ReadArgs (arexx_list[i].mc_Template, (LONG *) &cmdopts, myrdargs))) \
              || (!arexx_list[i].mc_Parameters)) {

               rc = (*((struct NetHook *) arexx_list[i].mc_Hook)->h_Entry) ((ULONG *) &cmdopts[0]);
               FreeArgs (rdargs);
            } else {

               Fault (IoErr(), NULL, (char *) buffer, LINELEN);
               SendResultString (buffer);
               rc = RETURN_FAIL;
            }
            FreeDosObject (DOS_RDARGS,myrdargs);
         }
         break;
      }
      i++;
   }

   if (! (arexx_list[i].mc_Name)) {
      i = 0;
      while (net_list[i].nc_Name) {
         if (len = isCommand (buffer, net_list[i].nc_Name)) {

            if (myrdargs = AllocDosObject (DOS_RDARGS,NULL)) {
               myrdargs->RDA_Source.CS_Buffer = buffer;
               myrdargs->RDA_Source.CS_Length = strlen (buffer);

               if (rdargs = ReadArgs (CMDOPT_TEMPLATE, (LONG *) &cmdopts, myrdargs)) {

                  (*net_list[i].nc_func) (NULL);
                  FreeArgs (rdargs);
               } else {

                  Fault (IoErr(), NULL, (char *) buffer, LINELEN);
                  SendResultString (buffer);
                  rc = RETURN_FAIL;
               }
               FreeDosObject (DOS_RDARGS,myrdargs);
            }
            break;
         }
         i++;
      }
      if (! (net_list[i].nc_Name)) {
         SendResultString(msgUnknownOption);
         rc = RETURN_FAIL;
      }
   }
   return (rc);
}

ULONG netdaemon (VOID) {
   ULONG    rc = TRUE;
   char     buffer[LINELEN+2];
   int      len, done = FALSE;

   struct   passwd   *pw, *rootpw;
   long     rootid = 0;

/*
** inittcp() wurde bereits durch isNetCall() aufgerufen!
*/

   serverstate = TRUE;

   if (rootpw = getpwnam ("root"))
      rootid = rootpw->pw_gid;

   if ((sgets (0, buffer, LINELEN)) && (strcmp (buffer, CMD_BEGIN) == 0)) {
//logprint ("BEGIN\n");
      if ((sgets (0, buffer, LINELEN)) && (len = isCommand (buffer, CMD_USER)) \
        && (pw = getpwnam (buffer + len))) {
//logprint ("USER %s\n", buffer + len);

         if (((rootpw) && (rootid == pw->pw_gid)) || (! rootpw)) {

            if ((sgets (0, buffer, LINELEN)) && (len = isCommand (buffer, CMD_PASSWORD))
              && (strcmp (crypt (buffer + len, pw->pw_passwd), pw->pw_passwd) == 0)) {
//logprint ("PASSWORD %s\n", buffer + len);

               if (SendClient (CMD_CONNECTED)) {
//logprint ("'CONNECTED' send!\n");
                  while (sgets (0, buffer, LINELEN)) {
//logprint ("COMMAND '%s'\n", buffer);
                     if (strcmp (buffer, CMD_END) == 0) {
                        break;
                     }

                     if (ExecuteCommand (buffer)) {
                        SendClient (CMD_ERROR);
                     } else {
                        SendClient (CMD_DONE);
                     }
                     done = TRUE;
                  }
               }
            } else {
               SendClient(msgWrongPassword);
            }
         } else {
            SendClient(msgNoPrivilege);
         }
      } else {
         SendClient(msgUnknownUser);
      }
   } else {
      SendClient(msgNoBEGINReceived);
   }
   if (! done)
      SendClient (CMD_ERROR);

   serverstate = FALSE;
   return (rc);
}

int MyDoCommand (char *fmt, ...) {
   int   result = FALSE;
   char  buf[LINELEN];

   va_list args;
   va_start(args,fmt);
   _vsnprintf(buf, LINELEN, fmt, args);

   if (clientstate) {
      if (! NetCommand (buf))
         result = TRUE;
   } else {
      if (! ExecuteCommand (buf))
         result = TRUE;
   }
   if (! result) {
      MyRequest (msgErrorContinue, msgErrorOccured);
   }
   va_end (args);
   return (result);
}

