/* $Id: cmd.c,v 1.16 2004/05/11 15:36:17 aleigh Exp $ */

/*
 * Copyright (c) 1994, 2001 Alex Leigh, All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 *
 * Use, disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

#include "cmd.h"

static char cmdGhostname[64];

#define CMDLEN 256

struct cmdSop {
  const char *op_name;
  int (*op_func)(netTconn *conn, int argc, char **argv);
  const char *helpmsg;
  struct cmdSop *sub_ops;
  struct cmdSop *next;
};
/* typedef in cmd.h */

struct cmdSstate {
  char cmd[CMDLEN];
  int len;
};
typedef struct cmdSstate cmdTstate;

#define CMDPROTO(x) static int x(netTconn *conn, int argc, char **argv)

CMDPROTO(cmdFop_quit);
CMDPROTO(cmdFop_show_version);
CMDPROTO(cmdFop_show_time);
CMDPROTO(cmdFop_reload);
CMDPROTO(cmdFop_halt);

cmdTop *cmdGops = NULL;
lstTset *cmdGusers=NULL;

cmdTop *cmdFroot(void) {
    return cmdGops;
}

void cmdFbind_stub(cmdTop *parent,
		   const char *cmd, 
                   const char *helpmsg) {
    cmdTop *newop;

    newop=(cmdTop *)malloc(sizeof(cmdTop));
    newop->op_name=strFcopy(cmd);
    newop->sub_ops=NULL;
    newop->helpmsg=strFcopy(helpmsg);

    if(parent->sub_ops==NULL) {
        newop->next=NULL;
        parent->sub_ops=newop;
    } else {
        newop->next=parent->sub_ops;
        parent->sub_ops=newop;
    }
}

void cmdFbind_command(cmdTop *parent,
                     const char *cmd,
                      int(*op_func)(netTconn *conn, int argc, char **argv), const char *helpmsg) {
    cmdTop *newop;

    newop=(cmdTop *)malloc(sizeof(cmdTop));
    newop->op_func=op_func;
    newop->op_name=strFcopy(cmd);
    newop->sub_ops=NULL;
    newop->helpmsg=strFcopy(helpmsg);

    if(parent->sub_ops==NULL) {
        newop->next=NULL;
        parent->sub_ops=newop;
    } else {
        newop->next=parent->sub_ops;
        parent->sub_ops=newop;
    }
}

int cmdFinit(lstTset *opt)
{
    cmdTop *op;
    cmdTop *newop;
    
    logFmsg(0,"mod/cmd: Command Interpreter");
    logFmsg(0,"Copyright (c) 2004, Alex Leigh");
    
    gethostname(cmdGhostname,64);

    cmdGusers=lstFset_init();

    newop=(cmdTop *)malloc(sizeof(cmdTop));
    memFclear((void *)newop,sizeof(cmdTop));
    newop->op_name=strFcopy("root");
    cmdGops=newop;

    cmdFbind_command(cmdGops,"quit",cmdFop_quit, "Disconnect session.");
    cmdFbind_command(cmdGops,"reload",cmdFop_reload, "Reload this Continuity instance.");
    cmdFbind_command(cmdGops,"halt",cmdFop_halt, "Halt this Continuity instance.");

    cmdFbind_stub(cmdGops,"show", "Show information.");
    op=cmdFmatch("show",cmdGops);
    cmdFbind_command(op,"version",cmdFop_show_version, "Show version information.");
    cmdFbind_command(op,"time",cmdFop_show_time, "Show current time.");

    cmdFbind_stub(op,"dl", "Dynamic Loader");
    op=cmdFmatch("dl",op);
    cmdFbind_command(op,"loads",dlFop_show_dl_loads, "Show current module loads.");
    cmdFbind_command(op,"symbols",dlFop_show_dl_symbols, "Show currently mapped funcions.");

    return STATUS_OK;
}

static void cmdFecho_on(netTconn *conn) 
{
   char off_string[] =
    {
      (char) IAC,
      (char) WONT,
      (char) TELOPT_ECHO,
      (char) TELOPT_NAOFFD,
      (char) TELOPT_NAOCRD,
      (char)  0,
   };
   
   netFconn_write(conn, off_string, sizeof(off_string));
}

static void cmdFecho_off(netTconn *conn) 
{
   char off_string[] =
    {
      (char) IAC,
      (char) WILL,
      (char) TELOPT_ECHO,
      (char) TELOPT_NAOFFD,
      (char) TELOPT_NAOCRD,
      (char)  0,
   };
   
   netFconn_write(conn, off_string, sizeof(off_string));
}

static cmdTstate *cmdFstate_init(void) {
  cmdTstate *s;

  s=(cmdTstate *)malloc(sizeof(cmdTstate));
  assert(s!=NULL);

  memFclear(s,sizeof(cmdTstate));

  return s;
}

static int cmdFgather(netTconn *conn, cmdTstate *state)
{
  char c;
  ssize_t ret;
  size_t ignore=0;

  while(1) {
     ret=read(conn->sd,&c,1);
     if(ret==0) {
         return -1;
     }
     
     if(ret!=1) {
         return -1;
     }

     if(ignore>0) {
         ignore--;
         continue;
     }

     switch(c) {
         case '\r':
             continue;
         case '\n':
             state->cmd[state->len]=0;
             logFmsg(3,"cmd: %s", state->cmd);
             state->len=0;
             return 0;
         case (char)IAC:
             ignore+=2;
             continue;
     }
     
     //     logFmsg(3,"read char: %c %u", c, c);
     
     state->cmd[state->len]=c;
     state->len++;
  }
}

static int cmdFuser_auth(const char *un, const char *pw) {
    assert(un!=NULL);
    assert(pw!=NULL);

    char *goodpw=lstFset_get(cmdGusers,un);

    if(goodpw==NULL) return 0;

    logFmsg(3,"%s/%s", goodpw, pw);

    if(strcmp(goodpw,pw)!=0) return 0;

    return 1;
}

cmdTop *cmdFmatch(const char *cmd, cmdTop *ops) {
    int partials=0;
    cmdTop *partial_op, *op;

    assert(cmd!=NULL);
    assert(ops!=NULL);

    //    logFmsg(3,"searching for cmd: ->%s<-", cmd);
    
    size_t clen=strlen(cmd);
    
    for(op=ops->sub_ops;op!=NULL;op=op->next) {
      
      //      logFmsg(3,"searching for %s: eval %s", cmd, op->op_name);
      
        if(strcmp(cmd,op->op_name)==0) {
            return op;
        }
        
        if(strncmp(cmd,op->op_name,clen)==0) {
            partials++;
            partial_op=op;
        }
    }
    
    if(partials==0) {
            /* Command not found */
        return NULL;
    } else {
        if(partials==1) {
            return partial_op;
        }
    }

        /* Ambiguous expansion */
    return NULL;
}

static void cmdFdo_help(netTconn *conn, cmdTop *op) {

  netFconn_writef(conn,"\r\nHelp\r\n----\r\n");
 
  for(;op!=NULL;op=op->next) {
    netFconn_writef(conn, "%s\t%s\r\n", op->op_name, op->helpmsg);
  }
}

static void cmdFloop(netTconn *conn, cmdTstate *state)
{
    char *stack[32];
    const cmdTop *op;
    int argc;
    
    while(1) {
        netFconn_writef(conn,"%s -> ", cmdGhostname);
        
        if(cmdFgather(conn,state)==-1) {
            return;
        }
        
        argc=strFsplit(state->cmd, stack, 32, ' ', '"');
        
        cmdTop *p;
        
        p=cmdGops;
        
        for(int i=0;i<argc;i++) {
            logFmsg(3,"searching for cmd: ->%s<-", stack[i]);
            
            if(strcmp(stack[i],"?")==0) {
                cmdFdo_help(conn,p->sub_ops);
                continue;
            }
            
            op=cmdFmatch(stack[i],p);	  
            
            if(op!=NULL) {
                if(op->sub_ops!=NULL) {
                    p=op;
                    continue;
                }
                
                if(op->op_func(conn,argc-i,&stack[i])==STATUS_EXIT) {
                    return;
                } else {
                    break;
                }
            } else {
                netFconn_writef(conn,"Invalid command.\r\n");
                break;
            }
        }
    }
}

    
void cmdFhandler(netTconn *conn)
{
  cmdTstate *state=cmdFstate_init();
  char username[16];
  
  assert(conn!=NULL);
  assert(state!=NULL);

  while(1) {
      netFconn_writef(conn,"\r\nlogin: ");
      
      if(cmdFgather(conn,state)==-1) {
          break;
      }
      
      strncpy(username,state->cmd,16);

      cmdFecho_off(conn);
      netFconn_writef(conn,"Password: ");
      
      if(cmdFgather(conn,state)==-1) {
          break;
      }

      cmdFecho_on(conn);
      
      if(cmdFuser_auth(username,state->cmd)==0) {
          netFconn_writef(conn,"login denied.\r\n");
          continue;
      }

      logFmsg(3,"logged in.");
      netFconn_writef(conn,"\r\nLogin accepted.\r\n");
      netFconn_writef(conn,"\r\n%s\r\n", BUILD_STRING);

      cmdFloop(conn,state);
      break;
  }
  
  logFmsg(3,"Shutting down connection.");
  
  netFconn_close(conn);
}

static int cmdFop_quit(netTconn *conn, int argc, char **argv)
{
  netFconn_writef(conn,"Goodbye.\r\n");

  return STATUS_EXIT;
}

static int cmdFop_show_version(netTconn *conn, int argc, char **argv) {
    netFconn_writef(conn,"\r\nVersion\r\n-------\r\n");
    op_show_version(conn);
    return STATUS_OK;
}

static int cmdFop_reload(netTconn *conn, int argc, char **argv) {
  if(argc<2) {
    netFconn_writef(conn,"Usage: RELOAD YES\r\n");
    return STATUS_OK;
  }
  
  if(strcasecmp(argv[1],"yes")!=0) {
    netFconn_writef(conn,"Usage: RELOAD YES\r\n");
    return STATUS_OK;
  }
  
  netFconn_writef(conn,"Reloading instance...\r\n");

  exit(0);
}

static int cmdFop_halt(netTconn *conn, int argc, char **argv) {
  if(argc<2) {
    netFconn_writef(conn,"Usage: HALT YES\r\n");
    return STATUS_OK;
  }
  
  if(strcasecmp(argv[1],"yes")!=0) {
    netFconn_writef(conn,"Usage: HALT YES\r\n");
    return STATUS_OK;
  }
  
  netFconn_writef(conn,"Halting instance...\r\n");

  /* Instruct cdog to die if we are running under it */
  exit(5);
}

static int cmdFop_show_time(netTconn *conn, int argc, char **argv) {
  netFconn_writef(conn,"%s\r\n", utlFcommonlog_time());
  return STATUS_OK;
}

static int cmdFop_show_uptime(netTconn *conn, int argc, char **argv) {
  netFconn_writef(conn,"Uptime\r\n------\r\n");
  return STATUS_OK;
}

int cmdFadd_user(void *p, lstTset *opt) {
  const char *un, *pw;

  un=lstFset_get(opt,"user");
  pw=lstFset_get(opt,"password");
 
  if(un!=NULL && pw!=NULL) {
    lstFset_add(cmdGusers,un,pw);
  } else {
    logFmsg(2,"Failed.");
    exit(5);
  }

  return STATUS_OK;
}
