/*   common.c
 *
 *   This file is part of FTP4ALL
 *
 *   Copyright (C) 1996,1997,1998,1999 by Christoph Schwarz
 *
 *   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.
 */

#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>

#include <ix.h>

#include "tweak.h"
#ifdef HAVE_SYS_SELECT_H
  #include <sys/select.h>
#endif
#ifdef HAVE_SYS_TIME_H
  #include <sys/time.h>
#endif
#include "externs.h"

extern signal_handler(int sig);

void strip_crlf(char* buffer)
{ int l;

  l = strlen(buffer);
  while (l && ((*(buffer+l-1)=='\n') || (*(buffer+l-1)=='\r')))
  { l--;
    *(buffer+l) = 0;
  }
}

char skipspace(char** x)
{ while (**x && isspace(**x)) (*x)++;
  return **x;
}

char skipnspace(char** x)
{ while (**x && !isspace(**x)) (*x)++;
  return **x;
}

char skiptocolon(char** x)
{ skipspace(x);
  while (**x && !isspace(**x) && (**x!=':')) (*x)++;
  return **x;
}

char skipspctocolon(char** x)
{ while (**x && isspace(**x) && (**x!=':')) (*x)++;
  if (**x == ':')
  { (*x)++;
    skipspace(x);
  }
  return **x;
}

int parse_command(char* p, char** param, int max, int* n)
{ int q;

  while (skipspace(&p))
  { if (*n >= max) return 1;
    *(param+(*n)) = p;
    (*n)++;
    q = 0;
    while (!(q & 0x04)) switch (*p)
    { case '"'  :
        if (q & 0x02) p++; else
        { q ^= 0x01;
          memmove(p,p+1,strlen(p));
        }
        break;
      case '\'' :
        if (q & 0x01) p++; else
        { q ^= 0x02;
          memmove(p,p+1,strlen(p));
        }
        break;
      case '\\' :
        memmove(p,p+1,strlen(p));
        if (*p) p++;
        break;
      case '\t' :
      case ' ' :
        if (q) p++; else q |= 0x04;
        break;
      case '\0' :
        q |= 0x04;
        break;
      default :
        p++;
        break;
    }
    if (*p) *(p++) = '\0';
  }
  return 0;
}

void sprint_kb(char* str, long kb, int aligned)
{ if ((kb <= -1024) || (kb >= 1024))
  { if (kb >= 1048576) sprintf(str,aligned ? "%7.3f GB" : "%5.3f GB",
      (float)kb/1048576); else
      sprintf(str,aligned ? "%7.1f MB" : "%3.1f MB",(float)kb/1024);
  } else sprintf(str,aligned ? "%7li KB" : "%li KB",kb);
}

/***************************************************************/
/* enhancement for replacing color-cookies with ansi-sequences */
/***************************************************************/

void expand_color(char** p, int* len, char* buf, char* str)
{
  if (!(usr.class & 0x080))
  {
    if (strchr("abcdefghABCDEFGH012345678X", **p) == NULL)
    { strncpy(buf,*p-1,2);
      *(buf+2) = '\0';
      return;
    } 
    *(buf) = '\0';
    return ;
  }

  switch (**p)
  { 

    /* all attributes off */
    case '0' :
      sprintf (buf,"%c[0m", 27);
      break;

    /*  a standard ! */
    case '!' :
      sprintf (buf,"!");
      break;

    /* blinking */
    case 'X' :
      sprintf (buf,"%c[5m", 27);
      break;

    /*********************/
    /* FOREGROUND COLORS */
    /*********************/

    /* FG black */
    case 'a' :
      sprintf(buf,"%c[0;30m", 27);
      break;

    /* FG red */
    case 'b' :
      sprintf(buf,"%c[0;31m", 27);
      break;

    /* FG green */
    case 'c' :
      sprintf(buf,"%c[0;32m", 27);
      break;

    /* FG brown */
    case 'd' :
      sprintf(buf,"%c[0;33m", 27);
      break;

    /* FG blue */
    case 'e' :
      sprintf(buf,"%c[0;34m", 27);
      break;

    /* FG magenta */
    case 'f' :
      sprintf(buf,"%c[0;35m", 27);
      break;

    /* FG cyan */
    case 'g' :
      sprintf(buf,"%c[0;36m", 27);
      break;

    /* FG light gray */
    case 'h' :
      sprintf(buf,"%c[0;37m", 27);
      break;

    /* FG dark gray */
    case 'A' :
      sprintf(buf,"%c[1;30m", 27);
      break;

    /* FG light red */
    case 'B' :
      sprintf(buf,"%c[1;31m", 27);
      break;

    /* FG light green */
    case 'C' :
      sprintf(buf,"%c[1;32m", 27);
      break;

    /* FG yellow */
    case 'D' :
      sprintf(buf,"%c[1;33m", 27);
      break;

    /* FG light blue */
    case 'E' :
      sprintf(buf,"%c[1;34m", 27);
      break;

    /* FG light magenta */
    case 'F' :
      sprintf(buf,"%c[1;35m", 27);
      break;

    /* FG light cyan */
    case 'G' :
      sprintf(buf,"%c[1;36m", 27);
      break;

    /* FG white */
    case 'H' :
      sprintf(buf,"%c[1;37m", 27);
      break;

    /*********************/
    /* BACKGROUND COLORS */
    /*********************/

    /* BG black */
    case '1' :
      sprintf(buf,"%c[1;40m", 27);
      break;

    /* BG red */
    case '2' :
      sprintf(buf,"%c[1;41m", 27);
      break;

    /* BG green */
    case '3' :
      sprintf(buf,"%c[1;42m", 27);
      break;

    /* BG brown */
    case '4' :
      sprintf(buf,"%c[1;43m", 27);
      break;

    /* BG blue */
    case '5' :
      sprintf(buf,"%c[1;44m", 27);
      break;
    
    /* BG magenta */
    case '6' :
      sprintf(buf,"%c[1;45m", 27);
      break;
    
    /* BG cyan */
    case '7' :
      sprintf(buf,"%c[1;46m", 27);
      break;
    
    /* BG white */
    case '8' :
      sprintf(buf,"%c[1;47m", 27);
      break;

    default :
      strncpy(buf,*p-1,2);
      *(buf+2) = '\0';
      break;
  }  
}


void expand_variable(char** p, int* len, char* buf, char* str)
{ struct sockaddr_in addr;
  struct hostent*    hp;
  int                n;
  time_t             t;

  switch (**p)
  { case '%' :
      strcpy(buf,"%");
      break;
    case 'a' :
      sprintf(buf,"%i",num_anon);
      break;
    case 'A' :
      sprintf(buf,"%i",max_anon);
      break;
    case 'c' :
      if (usr.limit == -1)    /* limit == -1 -> don't change credits anymore */
        strcpy(buf,"upload-only");
      else if (usr.limit)
        sprint_kb(buf,usr.credit,0);
      else
        strcpy(buf,"unlimited");
      break;
    case 'd' :
      sprint_kb(buf,bytes_downloaded / 1024 + mbytes_downloaded * 1024,0);
      break;
    case 'D' :
      sprint_kb(buf,usr.kb_downloaded,0);
      break;
    case 'h' :
      addr.sin_addr.s_addr = htonl(ipaddr);
      hp = gethostbyaddr((char*)&addr.sin_addr,sizeof(addr.sin_addr),
        AF_INET);
      if (hp) strcpy(buf,hp->h_name); else
        strcpy(buf,inet_ntoa(addr.sin_addr));
      break;
    case 'i' :
      addr.sin_addr.s_addr = htonl(ipaddr);
      strcpy(buf,inet_ntoa(addr.sin_addr));
      break;
    case 'k' :
      sprintf(buf,"%3.1f",((float)last_speed)/1024);
      break;
    case 'l' :
      strftime(buf,32,"%a %b %d, %Y %H:%M",localtime(&usr.last_login));
      break;
    case 'L' :
      sprintf(buf,"%i",usr.limit);
      break;
    case 'n' :
      sprintf(buf,"%i",num_anon+num_users);
      break;
    case 'N' :
      sprintf(buf,"%i",max_anon+max_users);
      break;
    case 'p' :
      sprint_kb(buf,bytes_uploaded / 1024 + mbytes_uploaded * 1024,0);
      break;
    case 'P' :
      sprint_kb(buf,usr.kb_uploaded,0);
      break;
    case 'q' :
      if (((0 == mbytes_downloaded) && (mbytes_uploaded > bytes_downloaded)) ||
          ((0 == mbytes_uploaded) && (mbytes_downloaded > bytes_uploaded)) ||
          ((0 == mbytes_uploaded) && (0 == bytes_uploaded)))
        sprintf(buf,"n/a");
      else
        sprintf(buf,"%3.1f",(float)(1024*1024*mbytes_downloaded+bytes_downloaded)/(1024*1024*mbytes_uploaded+bytes_uploaded));
      break;
    case 'Q' :
      if (!usr.kb_uploaded) strcpy(buf,"n/a"); else
        sprintf(buf,"%3.1f",(float)usr.kb_downloaded/usr.kb_uploaded);
      break;
    case 'r' :
      if (((0 == mbytes_downloaded) && (mbytes_uploaded > bytes_downloaded)) ||
          ((0 == mbytes_uploaded) && (mbytes_downloaded > bytes_uploaded)) ||
          ((0 == mbytes_downloaded) && (0 == bytes_downloaded)))
        sprintf(buf,"n/a");
      else
        sprintf(buf,"%3.1f",(float)100*(1024*1024*mbytes_uploaded+bytes_uploaded)/(1024*1024*mbytes_downloaded+bytes_downloaded));
      break;
    case 'R' :
      if (!usr.kb_downloaded) strcpy(buf,"n/a"); else
        sprintf(buf,"%3.1f",(float)usr.kb_uploaded*100/usr.kb_downloaded);
      break;
    case 's' :
      if (str) strcpy(buf,str); else
      { strcpy(buf,"%s");
        *(buf+2) = '\0';
      }
      break;
    case 'S' :
      strcpy(buf,usr.name);
      break;
    case 't' :
      time(&t);
      strftime(buf,32,"%a %b %d, %Y %H:%M",localtime(&t));
      break;
    case 'T' :
      if ((*len >= 3) && (*(*p+1) >= '0' && *(*p+1) <= '9'))
      { n = *(*p+1) - '0';
        if (!n) n = 10;
        n--;
        switch (*(*p+2))
        { case 'd' :
            switch (*(*p+3))
            { case 'a' : sprint_kb(buf,top[n].d_amount,1); break;
              case 'n' : sprintf(buf,"%-9s",top[n].d_name); break;
              case 'i' : sprintf(buf,"%-15s",top[n].d_info); break;
              case 'm' : strcpy(buf,top[n].d_name);
                         sprintf(buf+strlen(buf)," %-*s",24-strlen(buf),
                           top[n].d_info);
                         break;
              default  : strncpy(buf,*p-1,5); *(buf+5) = '\0'; break;
            }
            break;
          case 'e' :
            switch (*(*p+3))
            { case 'a' : sprintf(buf,"%7.1f",top[n].e_amount); break;
              case 'n' : sprintf(buf,"%-9s",top[n].e_name); break;
              case 'i' : sprintf(buf,"%-15s",top[n].e_info); break;
              case 'm' : strcpy(buf,top[n].e_name);
                         sprintf(buf+strlen(buf)," %-*s",24-strlen(buf),
                           top[n].e_info);
                         break;
              default  : strncpy(buf,*p-1,5); *(buf+5) = '\0'; break;
            }
            break;
          case 'l' :
            switch (*(*p+3))
            { case 'a' : sprintf(buf,"%7.1f",top[n].l_amount); break;
              case 'n' : sprintf(buf,"%-9s",top[n].l_name); break;
              case 'i' : sprintf(buf,"%-15s",top[n].l_info); break;
              case 'm' : strcpy(buf,top[n].l_name);
                         sprintf(buf+strlen(buf)," %-*s",24-strlen(buf),
                           top[n].l_info);
                         break;
              default  : strncpy(buf,*p-1,5); *(buf+5) = '\0'; break;
            }
            break;
          case 'u' :
            switch (*(*p+3))
            { case 'a' : sprint_kb(buf,top[n].u_amount,1); break;
              case 'n' : sprintf(buf,"%-9s",top[n].u_name); break;
              case 'i' : sprintf(buf,"%-15s",top[n].u_info); break;
              case 'm' : strcpy(buf,top[n].u_name);
                         sprintf(buf+strlen(buf)," %-*s",24-strlen(buf),
                           top[n].u_info);
                         break;
              default  : strncpy(buf,*p-1,5); *(buf+5) = '\0'; break;
            }
            break;
          default  : strncpy(buf,*p-1,5); *(buf+5) = '\0'; break;
        }
        *len -= 3;
        *p += 3;
      } else
      { strncpy(buf,*p-1,2);
        *(buf+2) = '\0';
      }
      break;
    case 'u' :
      sprintf(buf,"%i",num_users);
      break;
    case 'U' :
      sprintf(buf,"%i",max_users);
      break;
    default :
      strncpy(buf,*p-1,2);
      *(buf+2) = '\0';
      break;
  }
}

int send_message_part(int rno, char* p, int len, char* str, int finish)
{ char  buf[1024];
  int   n;
  char* param[32];

  if ((len > 3) && !strncmp(p,"%#!",3))
  { len -= 3;
    p += 3;
    strcpy(buf,"");
    while (len) 
      if ((*p == '%') && (len > 1))
      { len--;
        p++;
        expand_variable(&p,&len,buf+strlen(buf),str);
        if (len)
        { len--;
          p++;
        }
      } else if  ((*p == '!') && (len > 1))
      { len--;
        p++;
        expand_color(&p,&len,buf+strlen(buf),str);
        if (len)
        { len--;
          p++;
        }
      } else
      { switch (*p)
        { case '\n' :
            strcat(buf," ");
          case '\r' :
            break;
          default :
            *(buf+strlen(buf)+1) = '\0';
            *(buf+strlen(buf)) = *p;
        }
        len--;
        p++;
      }
      n = 0;
      if (parse_command(buf,param,32,&n))
        fprintf(sockf,"%i%c%s\r\n",rno,finish ? '-' : ' ',buf);
        else run_program(*param,param,n,rno,finish,cwd+1,&n,NULL);
      return 0;
    }

  fprintf(sockf,"%i%c",rno,finish ? ' ' : '-');
  while (len)
    if ((*p == '%') && (len > 1))
    { len--;
      p++;
      expand_variable(&p,&len,buf,str);
      fprintf(sockf,"%s",buf);
      if (len)
      { len--;
        p++;
      }
    } else if ((*p == '!') && (len > 1))
    { len--;
      p++;
      expand_color(&p,&len,buf,str);
      fprintf(sockf,"%s",buf);
      if (len)
      { len--;
        p++;
      }
    } else 
    { switch (*p)
      { case '\n' :
          fprintf(sockf,"\r\n");
          break;
        case '\r' :
          break;
        default :
          fputc(*p,sockf);
      }
      len--;
      p++;
    }
    if (*(p-1) != '\n') fprintf(sockf,"\r\n");
    return 0;
}

int send_message(int rno, int finish, char* buffer, char* str)
{ char* p = buffer;

/*fprintf(stderr,"ftps(%i): sending <%s> <%s>\n",getpid(),buffer,str);*/
  if (!buffer) if (finish) return -1; else return 0;
  do
  { while (*p && (*p != '\n')) p++;
    if (*p) p++;
    if (send_message_part(rno,buffer,p-buffer,str,finish && !*p) == -1)
      return -1;
    buffer = p;
  } while (*p);
  return 0;
}

int send_reply(int rno, char* reply)
{ return send_message(rno,1,reply,NULL);
}

void get_errormsg(char* msg)
{ switch (errno)
  { case EPERM   : strcpy(msg,"Operation not permitted"); break;
    case ENOENT  : strcpy(msg,"No such file or directory"); break;
    case EINTR   : strcpy(msg,"Interrupted system call"); break;
    case EIO     : strcpy(msg,"I/O error"); break;
    case EBADF   : strcpy(msg,"Bad file number"); break;
    case ENOMEM  : strcpy(msg,"Out of memory"); break;
    case EACCES  : strcpy(msg,"Permission denied"); break;
    case EFAULT  : strcpy(msg,"Bad address"); break;
    case EISDIR  : strcpy(msg,"Target is a directory"); break;
    case EBUSY   : strcpy(msg,"Resource busy"); break;
    case EEXIST  : strcpy(msg,"File already exists"); break;
    case EXDEV   : strcpy(msg,"Cross device link"); break;
    case ENOTDIR : strcpy(msg,"One component is not a directory"); break;
    case EINVAL  : strcpy(msg,"Invalid argument"); break;
    case ETXTBSY : strcpy(msg,"Text file busy"); break;
    case ENOSPC  : strcpy(msg,"No space left on device"); break;
    case EROFS   : strcpy(msg,"Read only file system"); break;
    case EMLINK  : strcpy(msg,"Too many links"); break;
    case EPIPE   : strcpy(msg,"Broken pipe"); break;
    case ENAMETOOLONG : strcpy(msg,"Filename too long"); break;
  #ifndef _AIX
    case ENOTEMPTY    : strcpy(msg,"Directory is not empty"); break;
  #endif
    case ELOOP   : strcpy(msg,"Too many symbolic links"); break;
    case EADDRINUSE   : strcpy(msg,"Address already in use"); break;
    case EADDRNOTAVAIL: strcpy(msg,"Cannot assign requested address"); break;
    case ENETDOWN: strcpy(msg,"Network is down"); break;
    case ENETUNREACH  : strcpy(msg,"Network is unreachable"); break;
    case ECONNRESET   : strcpy(msg,"Connection reset by peer"); break;
    case ENOBUFS : strcpy(msg,"No buffer space available"); break;
    case ENOTCONN: strcpy(msg,"Not connected"); break;
    case ETIMEDOUT    : strcpy(msg,"Connection timed out"); break;
    case ECONNREFUSED : strcpy(msg,"Connection refused"); break;
    case EHOSTUNREACH : strcpy(msg,"No route to host"); break;
    case EMFILE       : strcpy(msg,"Too many open files"); break;
    default           : sprintf(msg,"Unknown error (errno=%i)",errno); break;
  }
}

void client_log(long lno, char* str, ...)
{ va_list ap;

  if (logc & lno)
  { va_start(ap,str);
    vprintf(str,ap);
  }
}

void client_log_start(long lno, char* str, ...)
{ va_list ap;

  if (logc & lno)
  { va_start(ap,str);
    printf("LOG '");
    vprintf(str,ap);
  }
}

void client_log_finish(long lno, char* str, ...)
{ va_list ap;
  char    dummy[6];

  if (logc & lno)
  { if (str)
    { va_start(ap,str);
      vprintf(str,ap);
    }
    printf("'\n");
    fgets(dummy, sizeof(dummy), stdin);
  }
}

void client_log_error(long lno)
{ char str[128];

  if (logc & lno)
  { strcpy(str," FAILED: ");
    get_errormsg(str+strlen(str));
    client_log_finish(lno,str);
  }
}

void client_activity(char* str, ...)
{ va_list ap;
  char    dummy[6];

  printf("UAC ");
  va_start(ap,str);
  vprintf(str,ap);
  printf("\n");
  fgets(dummy, sizeof(dummy), stdin);
}

int send_error(int rno, char* cmd, long lno)
{ char str[128];

  if (lno) client_log_error(lno);
  get_errormsg(str);
  sprintf(str+strlen(str)," (%s).",cmd);
  return send_reply(rno,str);
}

int run_program(char* prog, char** param, int n, int rno, int finish, char* cd,
  int* exitcode, char* buffer)
{ char* c;
  int   err;
  FILE* file;
  pid_t pid;
  int   stdinpipe[2];
  char  str[256];
  char  str2[256];
  int   stdoutpipe[2];
  char  envbuffer[5][32];

  err = pipe(stdinpipe);
  if (!err) err = pipe(stdoutpipe);
  if (err) return -1;
  fflush(stdout);
  sprintf(envbuffer[0],"F4A_USER=%s",usr.name);
  putenv(envbuffer[0]);
  sprintf(envbuffer[1],"F4A_CLASS=%s",superuser() ? "s" : anonymous ? "a" : "n");
  putenv(envbuffer[1]);
  sprintf(envbuffer[2],"F4A_SUBCLASS=%s",groupop() ? "g" : useredit() ? "e" : "-");
  putenv(envbuffer[2]);
  sprintf(envbuffer[3],"F4A_INVISIBLE=%s",invisible() ? "i" : "-");
  putenv(envbuffer[3]);
  sprintf(envbuffer[4],"F4A_COLOR=%s",coloruser() ? "c" : "-");
  putenv(envbuffer[4]);
  sprintf(envbuffer[5],"F4A_GROUP=%s",get_groupname(usr.gid));
  putenv(envbuffer[5]);
  pid = ix_vfork();
  if (pid == -1) return -1;
  if (pid)
  { close(stdinpipe[1]);
    close(stdoutpipe[0]);
    file = fdopen(stdinpipe[0],"rt");
    if (!file) return -1;
    if (buffer) *buffer = '\0';
    if (finish)
    { c = fgets(str,sizeof(str)-1,file);
      if (!c) strcpy(str,"(no output from program)");
    } else c = (char*)-1;
    while (c)
    { c = fgets(str2,sizeof(str2)-1,file);
      if (c && strlen(str2))
      { if (buffer) strcat(buffer,str2); else
        { if (finish)
          { if (send_message(rno,0,str,NULL) == -1) return -1;
            strcpy(str,str2);
          } else
          { if (send_message(rno,0,str2,NULL) == -1) return -1;
          }
        }
      }
    }
    fclose(file);
    pid = wait(exitcode);
    if (finish) return send_reply(rno,str);
    return 0;
  } else
  { fclose(sockf);
    fclose(sockfr);
    if (cd && *cd) chdir(cd);
    close(stdinpipe[0]);
    close(stdoutpipe[1]);
    dup2(stdoutpipe[0],0);
    dup2(stdinpipe[1],1);
    dup2(stdinpipe[1],2);
    close(stdinpipe[1]);
    close(stdoutpipe[0]);
    *(param+n) = NULL;
    execvp(prog,param);
    exit(-1);
  }
}

int run_prog_xt(char* prog, char **param, int* exitcode, char* returner)
{ int   err;
  FILE* file;
  pid_t pid;
  int   stdinpipe[2];
  char  str[255];
  int   stdoutpipe[2];
  char  envbuffer[5][32];
  char* c;
  char* p;

  err = pipe(stdinpipe);
  if (!err) err = pipe(stdoutpipe);
  if (err) return -1;
  fflush(stdout);
  sprintf(envbuffer[0],"F4A_USER=%s",usr.name);
  putenv(envbuffer[0]);
  sprintf(envbuffer[1],"F4A_CLASS=%s",superuser() ? "s" : anonymous ? "a" : "n");
  putenv(envbuffer[1]);
  sprintf(envbuffer[2],"F4A_SUBCLASS=%s",groupop() ? "g" : useredit() ? "e" : "-");
  putenv(envbuffer[2]);
  sprintf(envbuffer[3],"F4A_INVISIBLE=%s",invisible() ? "i" : "-");
  putenv(envbuffer[3]);
  sprintf(envbuffer[4],"F4A_COLOR=%s",coloruser() ? "c" : "-");
  putenv(envbuffer[4]);
  pid = ix_vfork();
  if (pid == -1) return -1;
  if (pid)
  { close(stdinpipe[1]);
    close(stdoutpipe[0]);
    file = fdopen(stdinpipe[0],"rt");
    if (!file) return -1;
    c = fgets(str,sizeof(str)-1,file);          /* get one line of the script */
    if (!c) sprintf(str,"no special reason\n");
    if ((p = strrchr(str, '\n'))) *p = 0;
    strcpy(returner,str);  /* paste it to the calling-proc without "\n" */
    fclose(file);
    pid = wait(exitcode);
    return 0;
  } else
  { fclose(sockf);
    fclose(sockfr);
    close(stdinpipe[0]);
    close(stdoutpipe[1]);
    dup2(stdoutpipe[0],0);
    dup2(stdinpipe[1],1);
    dup2(stdinpipe[1],2);
    close(stdinpipe[1]);
    close(stdoutpipe[0]);
    *(param+2) = NULL;
    execvp(prog,param);
    exit(-1);
  }
}

int convert_buffer(char* buffer, int len)
{ int i = 0;

  while (i < len) switch ((unsigned char)*buffer)
  { case 0xF2 :
    case 0xF4 :
    case 0xFF :
      len--;
      if (len > i) memmove(buffer,buffer+1,len-i);
      break;
    default :
      buffer++;
      i++;
  }
  return i;
}

int recvb(char* buffer, int len)
{ fd_set         fd;
  int            i;
  struct timeval t;

  FD_ZERO(&fd);
  FD_SET(fileno(sockfr),&fd);
  t.tv_sec = timeout;
  t.tv_usec = 0;
  i = select(fileno(sockfr)+1,&fd,NULL,NULL,&t);
  if (i == -1) return -1;
  if (!i)
  { quit(CL_TIMEOUT);
    errno = 0;
    return -1;
  }
  if (!fgets(buffer,len-1,sockfr))
  { quit(CL_NORMAL);
    errno = 0;
    return -1;
  }
  return convert_buffer(buffer,strlen(buffer));
}

int read_ftp_command(char* buffer, int bufferlen, char** param, int max, int* n)
{ int   len = 0;
  char* p;
  int   q;

  *n = 0;
  do
  { if ((q = recvb(buffer+len,bufferlen-len)) == -1) return -1;
    len += q;
    if ((len >= bufferlen) && (*(buffer+len-1) != '\n'))
    { do
      { q = recvb(buffer,bufferlen);
        if (q == -1) return -1;
      } while (*(buffer+q-1) != '\n');
      if (send_reply(500,"Command string too long.") == -1) return -1;
      return 1;
    }
  } while (!len && (*(buffer+len-1) != '\n'));
  if (len && (*(buffer+len-1) == '\n')) len--;
  if (len && (*(buffer+len-1) == '\r')) len--;
  *(buffer+len) = '\0';
/*fprintf(stderr,"ftps(%i): received <%s>\n",getpid(),buffer);*/
  p = buffer;
  if (!skipspace(&p))
  { if (send_reply(500,"Command string missing command.") == -1) return -1;
    return 1;
  }
  *param = p;
  *n = 1;
  if (skipnspace(&p)) *(p++) = '\0'; else return 0;
  if (skipspace(&p))
  { *(param+1) = p;
    (*n)++;
  }
  return 0;
}

/* EOF */
