/* a simple commandline client for sics and sea

   Markus Zolliker
     Mar. 2003 first version
     Aug. 2005 visible difference between client for SICS and sea
*/

#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>
#include "term.h"
#include "coc_util.h"
#include "myc_err.h"
#include "myc_str.h"
#include "instr_hosts.h"

typedef enum { NORMAL, SPY, CLIENT, NMODE } Mode;

#define MAXMSG 8192

static char *clcname="six", *servername="sics";
static int clclen=3;
static char host[128];
static char instr[32];
static int sock[2]; /* main socket and spy socket */
static int level=0; /* undefined level */
static int deflevel=0;
static int remember=1;
static int hidecom=1;
static char user1[32]="";
static char pswd1[32]="";
static char user2[32]="";
static char pswd2[32]="";
static char status[MAXMSG]="Busy";
static int busy[2];
static int port;
static char msg0[MAXMSG];
static char msg1[MAXMSG]; /* contains messages for main and spy */
static int pos0 = 0;
static int pos1 = 0;
static char *prefix[NMODE+1]={"", "| ", "# ", ""};
static int newline[NMODE+1]={1,1,1,1};
static char *sim="";

static char *pw=NULL;
static char *us="";

void Put(Mode mode, char *text) {
  char chr;
  
  while (*text) {
    if (newline[mode]) {
      fputs("\033[0m", stdout);
      fputs(prefix[mode], stdout);
      newline[mode] = 0;
      if (strncmp(text, "ERROR:", 6) == 0) {
        fputs("\033[31;1m", stdout);
      }
    }
    chr = *text; text++;
    fputc(chr, stdout);
    if (chr == '\r' || chr == '\n') {
      newline[mode] = 1;
    }
  }
  fflush(stdout);
}

void PutC(char *text) {
  Put(CLIENT, text);
}

void PutClear(void) {
  Put(NMODE, "");
}

void UsageNote(void) {
  PutC("  (for usage type: ");
  PutC(clcname);
  PutC(" help)\n");
}

void Usage(int cmds_only) {
  if (!cmds_only) {
    PutC(" \n ");
    PutC(clcname);
    PutC(
      " commandline options:\n"
      "     -x         login as spy\n"
      "     -u         login as user\n"
      "     -m         login as manager\n"
      "     help       show this help text\n"
      "     -a or a    ask always for username/password, forget passwords\n"
      "     -c or c    use background color instead of # and |\n"
      "     -s or s    simulation mode (on some instruments)\n"
      "     <instr>    connect to the server for instr\n"
      "     -h <host>  connect to a server on a different host\n"
      "     -p <port>  connect to a server on a different port\n"
      "     -n         do only a minimal login (no check of instrument)\n"
      "     no option  login with default privilege\n"
    );
  }
  PutC(
    "\n"
    " Special commands treated by ");
  PutC(clcname); PutC(" (these are no "); PutC(servername); PutC(" commands!)\n"
    "\n"
    "     quit          return to unix prompt\n"
    "     exit          return to unix prompt\n"
    "     stop          interrupt driving\n"
    "     "); PutC(clcname);
       PutC(" help      show this help text\n"
    "     "); PutC(clcname);
       PutC(" def stop  define a key for stop command\n"
    "     "); PutC(clcname);
       PutC(" save      save stop key and connection (host/port/access)\n"
    "     +             increase privilege\n"
    "     -             decrease privilege\n"
    "\n"
    " The "); PutC(servername);
    PutC(" status is shown, if it is not 'Eager to execute commands'.\n"
    " A shown status does not prohibit to enter commands.\n"
    "\n"
    " When "); PutC(servername);
    PutC(" is busy, a vertical bar '|' is shown at the left of the line.\n"
    " You may then enter more commands, but only under Spy privilege.\n"
    " All messages from the client are prefixed with a hash symbol '#'.\n"
    "\n"
    "                                        Markus Zolliker, Aug. 2005\n"
    "\n"
  );
  Put(-1, ""); /* clear colors */
}

int CocCreateSockAdr(
    struct sockaddr_in *sockaddrPtr,    /* Socket address */
    const char *host,                   /* Host.  NULL implies INADDR_ANY */
    int port)                           /* Port number */
{
    struct hostent *hostent;            /* Host database entry */
    struct in_addr addr;                /* For 64/32 bit madness */

    (void) memset((char *) sockaddrPtr, '\0', sizeof(struct sockaddr_in));
    sockaddrPtr->sin_family = AF_INET;
    sockaddrPtr->sin_port = htons((unsigned short) (port & 0xFFFF));
    if (host == NULL || host[0]=='\0') {
        addr.s_addr = INADDR_ANY;
    } else {
        hostent = gethostbyname(host);
        if (hostent != NULL) {
            memcpy((char *) &addr,
                (char *) hostent->h_addr_list[0], (size_t) hostent->h_length);
        } else {
            addr.s_addr = inet_addr(host);
            if (addr.s_addr == (unsigned long)-1) {
                return -1;       /* error */
            }
        }
    }
    /*
     * There is a rumor that this assignment may require care on
     * some 64 bit machines.
     */

    sockaddrPtr->sin_addr.s_addr = addr.s_addr;
    return 0;
}

char* readWrite(int tmo, int skip, char *find) {
  int lbuf, go, n, shortTmo, l, socket;
  Mode idx;
  char *msg, *p, *q;
  char chr;
  static char result[MAXMSG];
  int hide;
  static int laststat=0, laststatlen=0;
/*
 * read from registered sockets and write to stdout until a key pressed,
 * a timeout occurred (tmo [msec]), or a TRANSACTIONFINISHED message
 * received. Handle also status and TRANSACTIONSTART messages.
 *
 * skip=0: skip response that does not match
 * skip=1: skip all responses
 * skip=2: skip all responses, quit on match
 *
 * if the response starts with the contents of find
 *   the second part of the line is returned
 * if the response contain the contents of find, but not at the beginning
 *   "1" is returned
 * if the text is not found, or find=NULL, an empty line is returned
 */
  n=0;
  go=0;
  shortTmo = 0;
  str_copy(result, "");
  socket=term_wait_socket(0, tmo);
  while (socket > 0) {
    idx = (socket == sock[1]);
    ERR_SI(lbuf=recv(socket, &chr, 1, 0));
    if (lbuf==0) return "0";
    while (lbuf>0) {
      if (chr == '\n') break;
      if (idx) {
        if (chr != '\r' && pos1 < MAXMSG-1) {  /* cut lines longer than MAXMSG */
          msg1[pos1]=chr;
          pos1++;
        }
      } else {
        if (chr != '\r' && pos0 < MAXMSG-1) {  /* cut lines longer than MAXMSG */
          msg0[pos0]=chr;
          pos0++;
        }
      }
      socket=term_wait_socket(0, tmo);
      if (socket <= 0) break;
      idx = (socket == sock[1]);
      ERR_SI(lbuf=recv(socket, &chr, 1, 0));
    }
    hide=0;
    if (idx) { /* spy mode */
      msg1[pos1] = '\0';
      pos1 = 0;
      msg = msg1;
      if (!busy[1]) { /* skip spy messages if not busy (they will be doubled) */
        hide = 1;
      }
    } else {
      msg0[pos0] = '\0';
      pos0 = 0;
      msg = msg0;
    }
/* printf("[%s]\n", msg); */
    if (strncmp(msg, "TRANSACTIONSTART", 16) == 0) {
      busy[idx] = 1;
      hide = 1;
    } else if (strncmp(msg, "TRANSACTIONFINISHED", 19) == 0) {
      busy[idx] = 0;
      if (skip != 2) go = 1;
      shortTmo = 1;
      hide = 1;
    } else if (idx == 0 && strncmp(msg, "status = ", 9) == 0) {
      strcpy(status, msg+9);
      l = strlen(msg);
      if (status[0] == 'E') {
      } else if (laststat!=status[0] || laststatlen != l) {
        laststat = status[0];
        laststatlen = l;
      }
      hide = 1;
    }
    if (idx == 0 && find != NULL) {
      p=strstr(msg, find);
      
      if (p!=NULL) { /* copy message to result */
        if (p==msg) {
          str_copy(result, msg+strlen(find));
          q=strchr(result,'\r');
          if (q != NULL) *q='\0';
        } else {
          str_copy(result, "1");
        }
        hide=1;
        if (skip == 2) go = 1;
      }
    }
    if (hidecom && (skip || hide)) { /* skip text */
      n+=strlen(msg);
    } else {
      term_clear();
      Put(idx, msg);
      if (chr == '\n') {
        Put(idx, "\033[K\n");
      }
    }
    if (go) break;
    socket = term_wait_socket(0, shortTmo?100:tmo);
    shortTmo = 0;
  }
  fflush(stdout);
  if (result==NULL) laststat=0;
  return result;
  OnError: return NULL;
}

void PrintCmd(char *buf, int mode) {
  Put(mode, "\r\033[K\n");
  Put(mode, "\033[1;34m>> ");
  Put(mode, buf);
  Put(mode, "\033[K\033[0m\n");
}

int sendCmd(int socket, char *cmd) {
  int l;
  char buf[256];
  
  snprintf(buf, sizeof buf, "fulltransact %s\n", cmd);
  ERR_SI(l=send(socket, buf, strlen(buf), 0));
  return l;
  OnError: return -1; 
}

int scramble(char *buf) {
  int i, n, cnt, chr;
  int x;
/* Scrambles a string. Twice scramble gives original.
   It does never convert a plain char to a ctrl char
   and all standard ascii-codes to special chars and vice versa.
   returns the number of special chars on output
*/

  n=strlen(buf);
  x=23;
  cnt=0;
  for (i=0; i<n; i++) {
    chr=(buf[i] & 255) ^ (x % 32 + 128);
    if (chr & 128) cnt++;
    x=(x*13+5) % 256;
    buf[i]=chr;
  }
  return cnt;
}

void getscrambled(char *buf, int size, FILE *fil) {
  term_fgets(buf, size, fil);
  if (scramble(buf) > 0) scramble(buf);
}

void putscrambled(char *buf, FILE *fil) {
  char cvt[256];
  str_copy(cvt, buf);
  scramble(cvt);
  fprintf(fil, "%s\n", cvt);
}

int setrights(int gotolevel) {
  char *p;
  char prefhead[128], buf[128];
  FILE *fil;
  int ask;
  static char user[32]="";
  static char pswd[32]=""; 
  int savepw;
  
  if (pw == NULL) pw=pswd;
  str_copy(prefhead, clcname);
  str_append(prefhead, ".");
  str_append(prefhead, instr);

  if (remember) {
    fil=term_open_pref(0, prefhead, "r");
    if (fil != NULL) {
      term_fgets(buf, sizeof(buf), fil);
      getscrambled(user1, sizeof(user1), fil);
      getscrambled(pswd1, sizeof(pswd1), fil);
      getscrambled(user2, sizeof(user2), fil);
      getscrambled(pswd2, sizeof(pswd2), fil);
      fclose(fil);
    }
    if (deflevel==0) {
      deflevel=buf[0]-'0';
      if (deflevel<1 || deflevel>3) deflevel=2;
    }
  } else {
    deflevel=2;
  }
  if (gotolevel == 0) gotolevel = deflevel;
  if (deflevel == 0) deflevel = 3;
  if (level != gotolevel) {
    if (gotolevel==1) {
      if (user1[0]=='\0') {
        if (strcmp(clcname, "six") == 0) {
          str_copy(user1, "lnsmanager");
        } else {
          str_copy(user2, servername);
          str_append(user2, "manager");
        }
      }
      us=user1;
      pw=pswd1;
    } else if (gotolevel==2) {
      if (user2[0]=='\0') {
        if (strcmp(clcname, "six") == 0) {
          str_copy(user2, instr);
          str_lowcase(user2, user2);
          str_append(user2, "user");
        } else {
          str_copy(user2, servername);
          str_append(user2, "user");
        }
      }
      us=user2;
      pw=pswd2;
    } else if (gotolevel==3) {
      us="Spy";
      pw="007";
    }
    ask=1;
    savepw = 1;
    if (us[0]!='\0' && pw[0]!='\0' && remember) {
      sprintf(buf, "config rights %s %s", us, pw);
      ERR_I(sendCmd(sock[0], buf));
      ERR_P(p=readWrite(12000,0,"Acknowledged"));
      PutC(".");
      if (*p=='\0') {
        if (0==strcmp(us, user1)) {
          user1[0]='\0';
          pswd1[0]='\0';
        }
        if (0==strcmp(us, user2)) {
          user2[0]='\0';
          pswd2[0]='\0';
        }
      } else {
        ask=0;
        savepw = 0;
      }
    }
    if (ask) {
      PutC("\r");
      PutC(servername);
      PutC(" username");
      if (us[0]!='\0') {
        PutC(" [");
        PutC(us);
        PutC("]");
      }
      PutC(": ");
      term_fgets(user, sizeof(user), stdin);
      newline[CLIENT]=1;
      if (0==strcmp(user, "quit")) return 1;
      if (0==strcmp(user, "exit")) return 1;
      if (user[0]=='\0') {
        str_copy(user, us);
      }
      PutC("password: ");
      term_fgets(pswd, sizeof(pswd), stdin);
      newline[CLIENT]=1;
      if (0==strcmp(pswd, "quit")) return 1;
      if (0==strcmp(pswd, "exit")) return 1;
      if (pswd[0]!='\0') {
        sprintf(buf, "config rights %s %s", user, pswd);
        ERR_I(sendCmd(sock[0], buf));
        ERR_P(p=readWrite(12000,0,"Acknowledged"));
        if (*p=='\0') { /* no success */
          gotolevel=3;
          if (0==strcmp(user, user1)) {
            user1[0]='\0';
            pswd1[0]='\0';
          }
          if (0==strcmp(user, user2)) {
            user2[0]='\0';
            pswd2[0]='\0';
          }
          pw=NULL;
        } else { /* success */
          if (0==strcmp(user, user1)) {
            str_copy(pswd1, pswd);
          }
          if (0==strcmp(user, user2)) {
            str_copy(pswd2, pswd);
          }
          us=user;
          pw=pswd;
        }
      } else {
        gotolevel=3;
      }
    }
    level=3;
    ERR_I(sendCmd(sock[0], "config myrights"));
    ERR_P(p=readWrite(12000,1,"UserRights = "));
    PutC(".");
    if (*p!='\0') {
      level=*p-'0';
    }
    if (level==3) {
      us="Spy";
    }
  }
  if (pw!=NULL && savepw) {
    if (level==1) {
      str_copy(user1, us);
      str_copy(pswd1, pw);
      if (0==strcmp(user1, user2)) {
        user2[0]='\0';
        pswd2[0]='\0';
      }
    } else if (level==2) {
      str_copy(user2, us);
      str_copy(pswd2, pw);
      if (0==strcmp(user2, user1)) {
        user1[0]='\0';
        pswd1[0]='\0';
      }
    }
  }
  fil=term_open_pref(0, prefhead, "w");
  if (fil!=NULL) {
    fprintf(fil, "%d\n", deflevel);
    if (remember) {
      putscrambled(user1, fil);
      putscrambled(pswd1, fil);
      putscrambled(user2, fil);
      putscrambled(pswd2, fil);
    }
    fclose(fil);
  }
  return 0;
  OnError: return -1;
}

int SavePrefs(void) {
  char prefhead[128], name[128];
  FILE *fil;
  
  ERR_SI(gethostname(name, sizeof name)); /* get ip name of this host */
  str_copy(prefhead, clcname);
  str_append(prefhead, "_");
  str_append(prefhead, sim);
  str_append(prefhead, name);

  ERR_SP(fil=term_open_pref(0, prefhead, "w"));
  fprintf(fil, "keys\n");
  term_save_keys(fil, PutC);
  fclose(fil);
  return 0;
  OnError: return -1;
}

int LoadPrefs(void) {
  char prefhead[128], name[128], line[128];
  FILE *fil;
  
  ERR_SI(gethostname(name, sizeof name)); /* get ip name of this host */
  str_copy(prefhead, clcname);
  str_append(prefhead, "_");
  str_append(prefhead, sim);
  str_append(prefhead, name);
  fil=term_open_pref(0, prefhead, "r");
  if (fil) {
    while (term_fgets(line, sizeof(line), fil)) {
      if (0 == strcmp(line, "keys")) {
        term_load_keys(fil, PutC);
      }
    }
    fclose(fil);
  }
  return 0;
  OnError: return -1;
}

int Connect(void) {
  int sock;
  struct sockaddr_in sadr;
  char *p;
  
  ERR_I(CocCreateSockAdr(&sadr, host, port));
  ERR_SI(sock=socket(AF_INET, SOCK_STREAM, 0));
  term_reg_socket(sock);
  ERR_SI(connect(sock, (struct sockaddr *)&sadr, sizeof(sadr)));
  ERR_SI(send(sock, "Spy 007\n", 8, 0));
  ERR_P(p=readWrite(12000,2,"Login O"));
  if (*p!='K') { /* looking for the 'K' of 'Login OK' */
    PutC("rejected\n");
    return 0;
  }
  return sock;
  OnError: return -1;
}

int main (int argc, char *argv[]) {
  struct hostent *ent;
  int iret, pos;
  fd_set mask;
  int i, j, gotolevel, sicslogin;
  int home;
  int savehist = 0;
  char buf[512], lbuf[16];
  char stdPrompt[128]="", prompt[256]="";
  char *p;
  char *bar;
  char *pnam[4]={"0", "MANAGER", "user", "spy"};
  char *hostArg;
  char *subcmd;
  char *service;
  
  atexit(term_off);
  port=-1;
  home=0;
  sicslogin=1;
  deflevel=0;
  gotolevel=0;
  hostArg=NULL;
  sock[1]=0; /* do not yet connect 2nd connection */
  for (i=1; i<argc; i++) {
    if (0==strcmp(argv[i], "-s") || 0==strcmp(argv[i], "s")) {
      sim="sim-";
    } else if (0==strcmp(argv[i], "-sea") || 0==strcmp(argv[i], "sea")) {
      clcname="seacmd";
      servername="sea";
      clclen=strlen(clcname);
    } else if (0==strcmp(argv[i], "-graph") || 0==strcmp(argv[i], "graph")) {
      clcname="graphcmd";
      servername="graph";
      clclen=strlen(clcname);
    } else if (0==strcmp(argv[i], "-a") || 0==strcmp(argv[i], "a")) {
      remember=0;
    } else if (0==strcmp(argv[i], "-c") || 0==strcmp(argv[i], "c")) {
      prefix[NORMAL] = "\033[47m";
      prefix[SPY] = "\033[43m";
      prefix[CLIENT] = "\033[0m";
    } else if (0==strcmp(argv[i], "-d") || 0==strcmp(argv[i], "d")) {
      hidecom=0;
    } else if (0==strcmp(argv[i], "+") || 0==strcmp(argv[i], "-m")) {
      gotolevel=1;
    } else if (0==strcmp(argv[i], "-") || 0==strcmp(argv[i], "-x")) {
      gotolevel=3;
    } else if (0==strcmp(argv[i], "0") || 0==strcmp(argv[i], "-u")) {
      gotolevel=2;
    } else if (0==strcmp(argv[i], "++")) {
      gotolevel=1;
      deflevel=1;
    } else if (0==strcmp(argv[i], "--")) {
      gotolevel=3;
      deflevel=3;
    } else if (0==strcmp(argv[i], "00")) {
      gotolevel=2;
      deflevel=2;
    } else if (0==strcmp(argv[i], "help")) {
      Usage(0);
      return 0;
    } else if (0==strcmp(argv[i], "-h")) {
      i++;
      if (i>=argc) {
        PutC("missing host");
        UsageNote(); return 0;
      }
      hostArg = argv[i];
    } else if (0==strcmp(argv[i], "-p")) {
      i++;
      if (i>=argc) {
        PutC("missing port");
        UsageNote(); return 0;
      }
      port=atoi(argv[i]);
      if (port == 0) {
        PutC("illegal port");
        UsageNote(); return 0;
      }
    } else if (0==strcmp(argv[i], "-n")) {
      sicslogin=0;
    } else {
      if (strlen(argv[i])>=32) {
        PutC("argument too long");
        UsageNote(); return 0;
      } else if (argv[i][0] == '-') {
        PutC("unknown option: ");
        PutC(argv[i]);
        UsageNote(); return 0;
      } else if (argv[i][1] == '\0' && isdigit(argv[i][0])) {
        port = atoi(argv[i]);
      } else {
        hostArg = argv[i];
      }
    }
  }
  if (*sim != '\0') {
    service = "simsics";
  } else {
    service = servername;
  }
  home = InstrHost(service, hostArg, instr, sizeof instr
                  , host, sizeof host, &port);
  ent = gethostbyname(host);
  if (!ent) {
    if (hostArg == NULL || hostArg[0] <= ' ') {
      snprintf(host, sizeof host, "%s", getenv("HOST"));
      hostArg = host;
    }
    PutC(service);
    PutC(" on ");
    PutC(hostArg);
    PutC(" not found in InstrumentHostList\n");
    p=getenv("InstrumentHostList");
    if (p) {
      PutC("(");
      PutC(p);
      PutC(")\n");
    }
    return 0;
  }
  snprintf(host, sizeof host, "%s", ent->h_name);
  LoadPrefs();

  PutC(  "---------------------------------------------------\n");
  if (strcmp(clcname, "six") == 0) {
    PutC("six, a fast sics commandline client (doc: six help)\n");
    if (port == -1) {
      if (*sim == '\0') {
        port = 2911;
      } else {
        port = 2927;
      }
      home = 1;
    }      
  } else if (strcmp(clcname, "seacmd") == 0) {
    PutC("seacmd, a sea commandline client (doc: seacmd help)\n");
    if (port == -1) {
      port = 8641;
    } else if (port > 0 && port < 9) {
      port += 8640;
    }
    home = 1;
  } else {
    PutC("graphcmd, a graph commandline client (doc: graphcmd help)\n");
    if (port == -1) {
      port = 8741;
    } else if (port > 0 && port < 9) {
      port += 8740;
    }
    home = 1;
  }
  PutC(  "---------------------------------------------------\n");
  PutC(".");
  ERR_I(sock[0]=Connect());
  PutC(".");
  if (sock[0] == 0) return 0;
  if (sicslogin) {
  
    ERR_I(sendCmd(sock[0], "Instrument"));
    ERR_P(p=readWrite(12000,0,"Instrument = "));
    PutC(".");
    str_copy(instr, p);
    if (*instr=='\0') {
      PutC("can not detect instrument\n");
      return 0;
    }
    str_lowcase(instr,instr);
    p=strchr(instr,' ');
    if (p!=NULL) *p='\0';
    if (0==strcmp(instr,"SANS-II")) {
      str_copy(instr, "SANS2");
    }
    if (!home) {
      deflevel = 3;
      gotolevel = 3;
    }
    ERR_I(i=setrights(gotolevel));
    PutC(".");
    PutC("\rlogged in to "); PutC(servername); PutC(" as ");
    PutC(pnam[level]);
    PutC(" on ");
    PutC(instr);
    PutC("\n");

    sprintf(stdPrompt, "%s[%s] ", clcname, instr);

    ERR_I(sendCmd(sock[0], "status interest"));
    ERR_P(readWrite(12000,0,"OK"));
  
    ERR_I(sendCmd(sock[0], "status"));
    ERR_P(readWrite(12000,0,NULL));

    term_read_hist(clcname, instr);
  } else {
    sprintf(stdPrompt, "%s[%s] ", clcname, host);
    status[0]='E'; status[1]='\0';
    term_read_hist(clcname, host);
  }

  iret=1;
  buf[0]='\0';
  pos=0;

  savehist = 1;

  while (1) {
    if (busy[0]) {
      bar = prefix[SPY];
    } else {
      bar = prefix[NORMAL];
    }
    if (status[0] == 'E') { /* Eager to ... */
      sprintf(prompt, "%s%s", bar, stdPrompt);
    } else {
      sprintf(prompt, "%s%s(%s) ", bar, stdPrompt, status);
    }
    FD_ZERO(&mask);
    FD_SET(sock[0], &mask);
    iret=term_get_line(buf, sizeof(buf)-2, &pos, prompt, &mask);
    if (iret==STDIN_FILENO) { /* input line terminated */
      str_lowcase(lbuf, buf);
      if (0==strcmp(lbuf,"quit") || 0==strcmp(lbuf,"exit")) {
        PrintCmd(buf, CLIENT);
        break;
      }
      p="";
      if (0==strcmp(lbuf,"stop")) {
        PrintCmd(buf, CLIENT);
        ERR_SI(send(sock[0], "INT1712 3\n", 10, 0));
        buf[0]='\0';
      } else if (0==strcmp(buf, "-") ||
                 0==strcmp(buf, "--") ||
                 0==strcmp(buf, "+") ||
                 0==strcmp(buf, "++")) {
        PrintCmd(buf, CLIENT);
        if (level == 0) {
          gotolevel = 3;
        } else {
          gotolevel = level;
        }
        if (buf[0]=='-') {
          if (gotolevel<3) gotolevel++;
        } else {
          if (gotolevel>1) gotolevel--;
        }
        if (strlen(buf)==2) {
          deflevel=gotolevel;
        }
        term_off();
        j = level;
        ERR_I(i=setrights(gotolevel)); /* level might be changed */
        if (j!=level) {
          PutC("\rswitched to ");
        } else {
          PutC("\rremain at ");
        }
        PutC(pnam[level]);
        PutC(" privilege\n");
        buf[0]='\0';
      } else if (0==strncmp(buf, "help ", 5) && 0==strcmp(buf+5, clcname)) {
        PrintCmd(buf, CLIENT);
        Usage(1);
        buf[0]='\0';
      } else if (0==strncmp(buf, clcname, clclen) && buf[clclen] == ' ') {
        subcmd = buf + clclen;
        while (*subcmd <= ' ' && *subcmd != 0) {
          subcmd++;
        }
        if (0==strcmp(subcmd, "help")) {
          PrintCmd(buf, CLIENT);
          Usage(0);
          buf[0]='\0';
        } else if (0==strncmp(subcmd, "def ", 4)) {
          PrintCmd(buf, CLIENT);
          term_define_key(subcmd+4, 0);
          buf[0]='\0';
        } else if (0==strcmp(subcmd, "def")) {
          PrintCmd(buf, CLIENT);
          term_define_key("", 0);
          buf[0]='\0';
        } else if (0==strcmp(subcmd, "save")) {
          PrintCmd(buf, CLIENT);
          PutC("defaults for next calls to ");
          PutC(clcname); PutC(":\n");
          ERR_I(SavePrefs());
          if (level == 0) {
            gotolevel = 3;
          } else {
            gotolevel = level;
          }
          deflevel = gotolevel;
          ERR_I(setrights(gotolevel));
          PutC("  login as ");
          PutC(us);
          PutC(" with ");
          PutC(pnam[level]);
          PutC(" privilege\n");
          buf[0]='\0';
        } else {
          PrintCmd(buf, CLIENT);
          PutC("ERROR: unknown command\n");
          buf[0]='\0';
        }
      } else if (buf[0] == '\0') { /* empty command */
        PrintCmd(buf, CLIENT);
        ERR_P(p=readWrite(200,0,NULL)); /* just wait a little */
      }
      if (buf[0] != '\0') {
        if (busy[0]) {
          if (!sock[1]) {
            ERR_I(sock[1] = Connect());
            if (!sock[1]) {
              PutC("spy connection rejected\n");
            }
          }
          if (busy[1]) {
            /* print in red bold */
            PrintCmd(buf, CLIENT);
            PutC("\033[1;31mBUSY (already 2 commands pending)\033[0m\n");
            buf[0]='\0';
          } else if (sock[1]) {
            PrintCmd(buf, SPY);
            ERR_I(sendCmd(sock[1], buf));
          } else {
            PrintCmd(buf, CLIENT);
            buf[0]='\0';
          }
        } else {
          PrintCmd(buf, NORMAL);
          ERR_I(sendCmd(sock[0], buf));
        }
      }
      if (buf[0] != '\0') {
        ERR_P(p=readWrite(500,0,NULL));
      }
      buf[0]='\0';
      pos=0;
    } else if (iret>0) { /* something arrived from sockets */
      if (iret == sock[0] || iret == sock[1]) {
        ERR_P(p=readWrite(500,0,NULL));
      }
    } else if (iret<0) {
      ERR_MSG("term_get_line failed");
    }
    if (strcmp(p, "0") == 0) {
      term_clear();
      PutC("\nconnection lost\n");
      goto SaveHist;
    }
  }
  if (savehist) term_save_hist(1); /* save history without last line */
  goto Bye;

  OnError:
    ErrShow("end");
  SaveHist:
    if (savehist) term_save_hist(0); /* save history with last line */
  Bye:
    term_off();
    if (stdPrompt[0]) {
      PutC("\nexit ");
      PutC(stdPrompt);
      PutC("\n");
      PutClear(); /* clear colors */
    }
    return 0;
}
