/*   site.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 <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <ix.h>

#include "tweak.h"
#include "externs.h"

struct file_s* find_name(struct file_s*,char*,int,int*);
struct dirent* findfirst(DIR**,char*,char*);
struct dirent* findnext(DIR*,char*);

int cmd_site_adduser_syntax(int rno)
{ return send_reply(rno,
  "Syntax: ADDUSER <name> <group|gid> <password> <1:ratio|limit|leech> <ip0> [<ip1> [<ip2> [...]]]\n"
  "Adds a new user to the userbase.\n");
}

int cmd_site_adduser(char** p, int n)
{ int i;
  char str[128];

  printf("ADU");
  for (i=0; i<n; i++) printf(" %s",*(p+i));
  printf("\n");
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_bind_syntax(int rno)
{ return send_reply(rno,
    "Syntax: BIND <port>\n"
    "Binds FTP server to another port. Use 0 to unbind.");
}

int cmd_site_bind(char** p, int n)
{ int  port;
  char str[128];

  if (!superuser()) return send_reply(550,"Only superuser can do BIND.");
  if (n != 1 || !sscanf(*p,"%i",&port)) return cmd_site_bind_syntax(501);
  printf("BIN %i\n",port);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_chmod_syntax(int rno)
{ return send_reply(rno,
    "Syntax: CHMOD <who>{+|-|=}<permission> [<directory>|<file>|<wildcard>]\n"
    "<who> is one or more of 'ugo*':\n"
    "u=user, g=group, o=others, *=user, group and others\n"
    "{+|-|=}: '+'=add right, '-'=remove right, '='=set right\n"
    "For DIRECTORIES, <permission> is one or more of 'agpoclmn*':\n"
    "a=access (chmod+rmdir), g=get (read) file, p=put (create) file, o=overwrite file,\n"
    "c=change to dir, l=list dir, m=make subdir, n=no limit, *=everything\n"
    "For FILES, <permission> is one or more of 'rwx': r=read, w=write, x=chmod\n"
    "Changes the permissions for the specified file or directory\n"
    "See also SITE CHMODR");
 }

int cmd_site_chmod(char** p, int n)
{ char           cmd;
  DIR*           d;
  struct dirent* de;
  long           dperm;
  char           dir[4] = "";
  int            file = 0;
  long           gid;
  long           mode = 0;
  char*          name;
  int            notfile = 0;
  char*          path;
  long           perm;
  char*          q;
  int            se = 0;
  struct stat    st;
  long           uid;
  char           wd[256];
  int            who = 0;
  char           wildcard[64];

  if ((!chmodallowed) && (!superuser())) return send_reply(550,"You are not allowed to CHMOD.");
  if (n == 1) *(p+1) = dir;
  if (n<1 || n>2) se = 1; else
  { q = *p;
    while (*q && *q!='+'&& *q!='-' && *q!='=')
    { if (tolower(*q) == 'u') if (who & 0x01) se = 1; else who |= 0x01; else
      if (tolower(*q) == 'g') if (who & 0x02) se = 1; else who |= 0x02; else
      if (tolower(*q) == 'o') if (who & 0x04) se = 1; else who |= 0x04; else
      if (tolower(*q) == '*') if (who) se = 1; else who |= 0x07; else se = 1;
      q++;
    }
    if (!who) se = 1;
  }
  if (!se)
  { cmd = *q;
    if (cmd!='+' && cmd!='-' && cmd!='=') se = 1;
  }
  if (!se)
  { q++;
    while (*q) switch (tolower(*(q++)))
    { case 'a' :
        notfile = 1;
        if (mode & P_CHMOD) se = 1; else mode |= P_CHMOD;
        break;
      case 'g' :
        notfile = 1;
        if (mode & P_READ) se = 1; else mode |= P_READ;
        break;
      case 'p' :
        notfile = 1;
        if (mode & P_WRITE) se = 1; else mode |= P_WRITE;
        break;
      case 'o' :
        notfile = 1;
        if (mode & P_OVERWRITE) se = 1; else mode |= P_OVERWRITE;
        break;
      case 'c' :
        notfile = 1;
        if (mode & P_CHDIR) se = 1; else mode |= P_CHDIR;
        break;
      case 'l' :
        notfile = 1;
        if (mode & P_LIST) se = 1; else mode |= P_LIST;
        break;
      case 'm' :
        notfile = 1;
        if (mode & P_MKDIR) se = 1; else mode |= P_MKDIR;
        break;
      case 'n' :
        notfile = 1;
        if (mode & P_NOLIMIT) se = 1; else mode |= P_NOLIMIT;
        break;
      case '*' :
        notfile = 1;
        if (mode) se = 1; else mode |= (P_ALL & ~P_NOLIMIT);
        break;
      case 'r' :
        file = 1;
        if (mode & FP_READ) se = 1; else mode |= FP_READ;
        break;
      case 'w' :
        file = 1;
        if (mode & FP_WRITE) se = 1; else mode |= FP_WRITE;
        break;
      case 'x' :
        file = 1;
        if (mode & FP_CHMOD) se = 1; else mode |= FP_CHMOD;
        break;
      default  :
        se = 1;
    }
    if (!mode || (file == notfile)) se = 1;
    if (file && (n != 2)) se = 1;
  }
  if (se) return cmd_site_chmod_syntax(501);
  if (add_dir(cwd,*(p+1),wd,sizeof(wd)) == -1)
    return send_error(550,"chmod",0);
  client_log_start(LG_CHMOD,"CHMOD %s %s",*p,wd);
  client_log_finish(LG_CHMOD,NULL);
  split_dir(wd,&path,&name,0);
  if (*name) strcpy(wildcard,name); else strcpy(wildcard,".");
  if (file)
  { if (permission_ok(path,P_CHDIR) == -1)
    { errno = EACCES;
      return send_error(550,"chmod",0);
    }
  }
  de = findfirst(&d,path ? path+1 : path,wildcard);
  while (de)
  { if (file)
    { se = read_file_permission(path ? path : "/",de->d_name,&uid,&gid,&perm);
      if ((se != -1) && !superuser() && ((uid != usr.uid) ||
        !((perm >> 16) & FP_CHMOD)))
      { errno = EACCES;
        se = -1;
      }
      if (se != -1)
      { strcpy(name,de->d_name);
        *(name-1) = '/';
        if (stat(wd+1,&st) == -1) st.st_mode = S_IFDIR;
        *(name-1) = '\0';
        if (!S_ISDIR(st.st_mode))
        { printf("FCM- %i %c %li %s/%s\n",who,cmd,mode,
            path ? path : "",de->d_name);
          fgets(*p, 1, stdin);
          if (**p == '+') send_message(250,0,"%s: success",de->d_name);
            else send_message(250,0,"%s: failed (server)",de->d_name);
        }
      }
    } else
    { strcpy(name,de->d_name);
      *(name-1) = '/';
      if (stat(wd+1,&st) == -1) st.st_mode = 0;
      if (S_ISDIR(st.st_mode))
      { se = read_permission_file(wd,&uid,&gid,&perm,NULL,NULL,NULL);
        if ((se == -1) && superuser())
        { uid = gid = 0;
          perm = umask_value;
          se = 0;
        }
        if (!se && !superuser() && ((usr.uid != uid) ||
          !(perm & (P_CHMOD << 16)) || (mode & P_NOLIMIT)))
        { errno = EACCES;
          se = -1;
        }
        if (!se)
        { switch (cmd)
          { case '+' :
            case '-' :
              dperm = 0;
              if (who & 0x01) dperm |= mode<<16;
              if (who & 0x02) dperm |= mode<<8;
              if (who & 0x04) dperm |= mode;
              if (cmd == '+') perm |= dperm; else perm &= ~dperm;
              break;
            case '=' :
              if (who & 0x01) perm = (perm & 0x00FFFF) | (mode<<16);
              if (who & 0x02) perm = (perm & 0xFF00FF) | (mode<<8);
              if (who & 0x04) perm = (perm & 0xFFFF00) | mode;
              break;
          }
          if (!superuser()) if (!(perm & (P_CHMOD<<16))) perm |= (P_CHMOD<<16);
          se = write_permission_file(wd,uid,gid,perm);
          if (!se) send_message(250,0,"%s: success",name);
        }
      } else se = 0;
      *(name-1) = '\0';
    }
    if (se == -1)
      send_message(250,0,"%s: failed (permission denied)",de->d_name);
    de = findnext(d,wildcard);
  }
  endfind(d);
  return send_reply(250,"CHMOD command finished.");
}

int site_chmod_rec(char* path, int file, int who, char cmd, long mode)
{ DIR*           d;
  struct dirent* de;
  long           dperm;
  long           gid;
  char*          name;
  long           perm;
  int            se;
  struct stat    st;
  char           temp[8];
  long           uid;

  de = findfirst(&d,strcmp(path,"/") ? path+1 : ".","*");
  if (strcmp(path,"/")) strcat(path,"/");
  name = path + strlen(path);
  while (de)
  { if (file)
    { *name = '\0';
      se = read_file_permission(path,de->d_name,&uid,&gid,&perm);
      if ((se != -1) && !superuser() && ((uid != usr.uid) ||
        !((perm >> 16) & FP_CHMOD)))
      { errno = EACCES;
        se = -1;
      }
      if (se != -1)
      { strcpy(name,de->d_name);
        if (stat(path+1,&st) == -1) st.st_mode = S_IFDIR;
        if (!S_ISDIR(st.st_mode))
        { printf("FCM- %i %c %li %s\n",who,cmd,mode,path);
          fgets(temp, sizeof(temp), stdin);
          if (*temp == '+') send_message(250,0,"%s: success",path);
            else send_message(250,0,"%s: failed (server)",path);
        }
      }
    } else if (strcmp(de->d_name,".."))
    { strcpy(name,de->d_name);
      if (stat(path+1,&st) == -1) st.st_mode = 0;
      if (S_ISDIR(st.st_mode))
      { se = read_permission_file(path,&uid,&gid,&perm,NULL,NULL,NULL);
        if ((se == -1) && superuser())
        { uid = gid = 0;
          perm = umask_value;
          se = 0;
        }
        if (!se && !superuser() && ((usr.uid != uid) ||
          !(perm & (P_CHMOD << 16)) || (mode & P_NOLIMIT)))
        { errno = EACCES;
          se = -1;
        }
        if (!se)
        { switch (cmd)
          { case '+' :
            case '-' :
              dperm = 0;
              if (who & 0x01) dperm |= mode<<16;
              if (who & 0x02) dperm |= mode<<8;
              if (who & 0x04) dperm |= mode;
              if (cmd == '+') perm |= dperm; else perm &= ~dperm;
              break;
            case '=' :
              if (who & 0x01) perm = (perm & 0x00FFFF) | (mode<<16);
              if (who & 0x02) perm = (perm & 0xFF00FF) | (mode<<8);
              if (who & 0x04) perm = (perm & 0xFFFF00) | mode;
              break;
          }
          if (!superuser()) if (!(perm & (P_CHMOD<<16))) perm |= (P_CHMOD<<16);
          se = write_permission_file(path,uid,gid,perm);
          if (!se) send_message(250,0,"%s: success",path);
        }
      } else se = 0;
    }
    if (se == -1)
      send_message(250,0,"%s: failed (permission denied)",path);
    else if (S_ISDIR(st.st_mode) && strcmp(de->d_name,".") &&
      strcmp(de->d_name,"..")) site_chmod_rec(path,file,who,cmd,mode);
    de = findnext(d,"*");
  }
  endfind(d);
}

int cmd_site_chmodr_syntax(int rno)
{ return send_reply(rno,
    "Syntax: CHMODR <who>{+|-|=}<permission> [<directory>]\n"
    "<who> is one or more of 'ugo*':\n"
    "u=user, g=group, o=others, *=user, group and others\n"
    "{+|-|=}: '+'=add right, '-'=remove right, '='=set right\n"
    "For DIRECTORIES, <permission> is one or more of 'agpoclmn*':\n"
    "a=access (chmod+rmdir), g=get (read) file, p=put (create) file, o=overwrite file,\n"
    "c=change to dir, l=list dir, m=make subdir, n=no limit, *=everything\n"
    "For FILES, <permission> is one or more of 'rwx': r=read, w=write, x=chmod\n"
    "Recursively changes the permissions for the specified file or directory\n"
    "See also SITE CHMOD");
}


int cmd_site_chmodr(char** p, int n)
{ char           cmd;
  char           dir[4] = "";
  int            file = 0;
  long           mode = 0;
  int            notfile = 0;
  char*          q;
  int            se = 0;
  char           wd[256];
  int            who = 0;

  if ((!chmodallowed) && (!superuser())) return send_reply(550,"You are not allowed to CHMODR.");
  if (n == 1) *(p+1) = dir;
  if (n<1 || n>2) se = 1; else
  { q = *p;
    while (*q && *q!='+'&& *q!='-' && *q!='=')
    { if (tolower(*q) == 'u') if (who & 0x01) se = 1; else who |= 0x01; else
      if (tolower(*q) == 'g') if (who & 0x02) se = 1; else who |= 0x02; else
      if (tolower(*q) == 'o') if (who & 0x04) se = 1; else who |= 0x04; else
      if (tolower(*q) == '*') if (who) se = 1; else who |= 0x07; else se = 1;
      q++;
    }
    if (!who) se = 1;
  }
  if (!se)
  { cmd = *q;
    if (cmd!='+' && cmd!='-' && cmd!='=') se = 1;
  }
  if (!se)
  { q++;
    while (*q) switch (tolower(*(q++)))
    { case 'a' :
        notfile = 1;
        if (mode & P_CHMOD) se = 1; else mode |= P_CHMOD;
        break;
      case 'g' :
        notfile = 1;
        if (mode & P_READ) se = 1; else mode |= P_READ;
        break;
      case 'p' :
        notfile = 1;
        if (mode & P_WRITE) se = 1; else mode |= P_WRITE;
        break;
      case 'o' :
        notfile = 1;
        if (mode & P_OVERWRITE) se = 1; else mode |= P_OVERWRITE;
        break;
      case 'c' :
        notfile = 1;
        if (mode & P_CHDIR) se = 1; else mode |= P_CHDIR;
        break;
      case 'l' :
        notfile = 1;
        if (mode & P_LIST) se = 1; else mode |= P_LIST;
        break;
      case 'm' :
        notfile = 1;
        if (mode & P_MKDIR) se = 1; else mode |= P_MKDIR;
        break;
      case 'n' :
        notfile = 1;
        if (mode & P_NOLIMIT) se = 1; else mode |= P_NOLIMIT;
        break;
      case '*' :
        notfile = 1;
        if (mode) se = 1; else mode |= (P_ALL & ~P_NOLIMIT);
        break;
      case 'r' :
        file = 1;
        if (mode & FP_READ) se = 1; else mode |= FP_READ;
        break;
      case 'w' :
        file = 1;
        if (mode & FP_WRITE) se = 1; else mode |= FP_WRITE;
        break;
      case 'x' :
        file = 1;
        if (mode & FP_CHMOD) se = 1; else mode |= FP_CHMOD;
        break;
      default  :
        se = 1;
    }
    if (!mode || (file == notfile)) se = 1;
  }
  if (se) return cmd_site_chmod_syntax(501);
  if (add_dir(cwd,*(p+1),wd,sizeof(wd)) == -1)
    return send_error(550,"chmod",0);
  client_log_start(LG_CHMOD,"CHMODR %s %s",*p,wd);
  client_log_finish(LG_CHMOD,NULL);
  site_chmod_rec(wd,file,who,cmd,mode);
  return send_reply(250,"CHMOD command finished.");
}

int cmd_site_chown_syntax(int rno)
{ return send_reply(rno,
    "Syntax: CHOWN [<user>]:[<group>] [<directory>]\n"
    "<user> can be user id or user name, <group> can be group id or group name\n"
    "at least one of <user> and <group> must be given.");
}

int cmd_site_chown(char** p, int n)
{ long  gid;
  long  new_gid;
  long  new_uid;
  long  perm;
  char* q;
  int   se = 0;
  long  uid;
  char  wd[256];

  if (!superuser()) return send_reply(550,"Only superuser can do CHOWN.");
  if (n<1 || n>2) se = 1; else
  { q = strchr(*p,':');
    if (q) *q = '\0';
    if (**p)
    { if ((new_uid = get_uid(*p)) == -1)
        return send_reply(501,"User not found.");
    } else new_uid = -1;
    if (q && *(q+1))
    { if ((new_gid = get_gid(q+1)) == -1)
        return send_reply(501,"Group not found.");
    } else new_gid = -1;
    if (new_uid == new_gid == -1) se = 1;
  }
  if (se) return cmd_site_chown_syntax(501);
  se = read_permission_file(n == 2 ? *(p+1) : "",&uid,&gid,&perm,
    NULL,NULL,NULL);
  if ((se == -1) && (errno == ENOTDIR))
  { if (n != 2) return send_error(550,"chown",0);
    add_dir(cwd,*(p+1),wd,sizeof(wd));
    printf("FCO- %li %li %s\n",new_uid,new_gid,wd);
    fgets(wd, sizeof(wd), stdin);
    return send_reply(*wd == '+' ? 250 : 550,wd+1);
  }
  if (se == -1)
  { uid = gid = 0;
    perm = umask_value;
  }
  if (new_uid != -1) uid = new_uid;
  if (new_gid != -1) gid = new_gid;
  if (n == 2) se = write_permission_file(*(p+1),uid,gid,perm);
    else se = write_permission_file("",uid,gid,perm);
  if (se == -1) return send_error(550,"chown",0); else
    return send_reply(250,"CHOWN command successful.");
}

int cmd_site_close_syntax(int rno)
{ return send_reply(rno,"Syntax: CLOSE\n"
    "Forces all existing non-sysop connections to close and refuses\n"
    "non-sysop login attempts until OPEN command is given.");
}

int cmd_site_close(char** p, int n)
{ char str[128];

  if (n) return cmd_site_close_syntax(501);
  printf("SCL\n");
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

/************************/
/* SITE COLOR extension */
/************************/

int cmd_site_color_syntax(int rno)
{ return send_reply(rno,"Syntax: COLOR\n"
                        "Switches between Color- and Monochrome-Mode");
}

int cmd_site_color(char** p, int n)
{ char str[128];

 if (n) return cmd_site_color_syntax(501);
  /* change it in the current session */
  if (usr.class & 0x080 )
  {
    usr.class ^= 0x080;
  } else 
  {
    usr.class |= 0x080;
  }

 /* change it in the users data */
 printf("COL\n");
 if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
 return send_reply(*str == '+' ? 200 : 500,str+1);


}

int cmd_site_count_syntax(int rno)
{ return send_reply(rno,
    "Syntax: COUNT\n"
    "Returns the number of connected users");
}

int cmd_site_count(char** p, int n)
{ char str[128];

  printf("CNT\n");
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_version_syntax(int rno)
{ return send_reply(rno,
    "Syntax: VERSION\n"
    "Returns the current FTP4ALL-Version of the server");
}

int cmd_site_version(char** p, int n)
{ char str[128];
  printf("VER\n");
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

long del_files_r(char* path)
{ struct dirent* d;
  DIR*           dir;
  char*          name;
  long           size = 0;
  struct stat    st;

  dir = opendir(path);
  if (!dir) return;
  strcat(path,"/");
  name = path + strlen(path);
  while ((d = readdir(dir)) != NULL)
  { strcpy(name,d->d_name);
    if (strcmp(d->d_name,".") && strcmp(d->d_name,"..") && !stat(path,&st))
    { if (S_ISDIR(st.st_mode))
      { size += del_files_r(path);
        strcpy(name,d->d_name);
        rmdir(path);
      } else
      { unlink(path);
        if (*d->d_name != '.') size += st.st_size;
      }
    }
  }
  closedir(dir);
  return size;
}

int cmd_site_del_syntax(int rno)
{ return send_reply(rno,
    "Syntax: DEL {<username>|<uid>} {<filename>|<directory>} [<factor>]\n"
    "Deletes file or all files in the directory and reduces the upload\n"
    "amount of the specified user by the size of the deleted file(s)\n"
    "multiplied with the <factor>.");
}

int cmd_site_del(char** p, int n)
{ float          factor = 1.0;
  int            ok;
  long           size;
  struct stat    st;
  char           wd[256];

  if (!superuser()) return send_reply(550,"Only superuser can do SITE DEL.");
  if ((n < 2) || (n > 3)) return cmd_site_del_syntax(501);
  if (n == 3) if (!sscanf(*(p+2),"%f",&factor))
    return cmd_site_del_syntax(501);
  if (get_uid(*p) == -1) return send_reply(550,"User does not exist.");
  ok = add_dir(cwd,*(p+1),wd,sizeof(wd));
  client_log_start(LG_DEL,"SITE DEL %s %s",*p,ok == -1 ? *(p+1) : wd);
  if (ok != -1) ok = stat(wd+1,&st);
  if (ok != -1)
  { if (S_ISDIR(st.st_mode))
    { ok = remove_permission_file(wd);
      if (ok != -1) size = del_files_r(wd+1);
      if (ok != -1) ok = add_dir(cwd,*(p+1),wd,sizeof(wd));
      if (ok != -1) rmdir(wd+1);
      sprintf(wd,"Deleted %s (total %li bytes * %3.1f)",*(p+1),size,factor);
    } else
    { size = st.st_size;
      ok = unlink(wd+1);
      sprintf(wd,"Deleted %s (%li bytes * %3.1f)",*(p+1),size,factor);
    }
  }
  if (ok == -1) return send_error(550,"delete",LG_DEL);
  client_log_finish(LG_DEL," : %li bytes removed * factor %3.1f",size,factor);
  printf("UUR %s %li\n",*p,(long)(size*factor));
  if (!fgets(wd+128,sizeof(wd)-128,stdin)) strcpy(wd+128,"-pipe error");
  if (*(wd+128) == '-')
  { client_log_finish(LG_DEL," FAILED: %s",wd+129);
    return send_reply(550,wd+129);
  }
  return send_reply(200,wd);
}

int cmd_site_disable_syntax(int rno)
{ return send_reply(rno,"Syntax: DISABLE <username>\n"
    "Disables user account.");
}

int cmd_site_disable(char** p, int n)
{ char str[128];

  if (n != 1) return cmd_site_disable_syntax(501);
  printf("ADI %s\n",*p);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_dump_syntax(int rno)
{ return send_reply(rno,"Syntax: DUMP <u|g> <filename>\n"
    "Dumps user file (u) or group file (g) as <filename>.");
}

int cmd_site_dump(char** p, int n)
{ char str[256];

  if (n != 2) return cmd_site_dump_syntax(501);
  if (add_dir(cwd,*(p+1),str,sizeof(str)) == -1)
    return send_error(550,"dump",0);
  if (!strcasecmp(*p,"u")) printf("DUF %s\n",str+1); else
  if (!strcasecmp(*p,"g")) printf("DGF %s\n",str+1); else
    return cmd_site_dump_syntax(501);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_enable_syntax(int rno)
{ return send_reply(rno,"Syntax: ENABLE <username>\n"
    "Enables user account.");
}

int cmd_site_enable(char** p, int n)
{ char str[128];

  if (n != 1) return cmd_site_enable_syntax(501);
  printf("AEN %s\n",*p);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_exec_syntax(int rno)
{ return send_reply(rno,"Syntax: EXEC <command> [<parameters>]\n"
    "Executes a shell command.");
}

int cmd_site_exec(char** p, int n)
{ char* c;
  int   err;
  FILE* file;
  pid_t pid;
  int   stdinpipe[2];
  char  str[128];
  int   stdoutpipe[2];

  if (!n) return cmd_site_exec_syntax(501);
  if (!superuser()) return send_reply(550,"You are not superuser.");
  err = pipe(stdinpipe);
  if (!err) err = pipe(stdoutpipe);
  if (err) return send_error(550,"pipe",0);
  pid = ix_vfork();
  if (pid == -1) return send_error(550,"fork",0);
  if (pid)
  { close(stdinpipe[1]);
    close(stdoutpipe[0]);
    close(stdoutpipe[1]);
    file = fdopen(stdinpipe[0],"rt");
    if (!file) return send_reply(550,"fdopen");
    do
    { c = fgets(str,sizeof(str)-1,file);
      if (c && strlen(str))
        if (send_message(200,0,str,NULL) == -1) return -1;
    } while (c);
    fclose(file);
    pid = wait(&err);
    sprintf(str,"EXEC finished with exitcode %i.",err);
    return send_reply(200,str);
  } else
  { fclose(sockf);
    if (strcmp(cwd,"/")) chdir(cwd+1);
    close(stdinpipe[0]);
    close(stdoutpipe[1]);
    err = dup2(stdinpipe[1],1);
    if (err != -1) err = dup2(stdinpipe[1],2);
    if (err != -1) err = dup2(stdoutpipe[0],0);
    *(p+n) = NULL;
    if (err != -1) err = execvp(*p,p);
    exit(err);
  }
}

int cmd_site_group_syntax(int rno)
{ return send_reply(rno,
    "Implemented GROUP commands, syntax: SITE GROUP <command> <arguments>\n"
    "ADD    <gid> <name>   - add new group\n"
    "CHANGE <gid> <name>   - change group name\n"
    "LIST                  - list all existing groups\n"
    "REMOVE {<gid>|<name>} - remove group");
}

int cmd_site_group(char** p, int n)
{ long gid;
  int  se = 0;
  char str[128];

  if (!n) se = 1; else if (!strcasecmp(*p,"ADD"))
  { if (n != 3) se = 1; else printf("GAD %s %s\n",*(p+1),*(p+2));
  } else if (!strcasecmp(*p,"CHANGE"))
  { if (n != 3) se = 1; else printf("GCH %s %s\n",*(p+1),*(p+2));
  } else if (!strcasecmp(*p,"LIST"))
  { if (n != 1) se = 1; else
    { printf("GLI\n");
      if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
      while (*str=='+' && strcmp(str,"+\n"))
      { if (send_message(211,0,str+1,NULL) == -1) return -1;
        if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-");
      }
      if (*str == '+') return send_reply(211,"<end of list>");
      if (strcmp(str,"-")) return send_reply(550,str+1);
        else return send_reply(211,"pipe error, aborting list");
    }
  } else if (!strcasecmp(*p,"REMOVE"))
  { if (n != 2) se = 1; else
    { if ((gid = get_gid(*(p+1))) == -1) return
        send_reply(501,"Group not found.");
      printf("GRM %li\n",gid);
    }
  } else se = 1;
  if (se) return cmd_site_group_syntax(501);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_groups_syntax(int rno)
{ return send_reply(rno,
    "Syntax: GROUPS [<user> [{+|-}<group>]]\n"
    "Without parameters, display the names of the groups you are in. The\n"
    "superuser can also add/remove users to/from groups.");
}

int cmd_site_groups(char** p, int n)
{ int   i;
  char* name;
  char  str[128];

  if (!n)
  { strcpy(str,"Primary group: ");
    name = get_groupname(usr.gid);
    if (name) strcat(str,name); else sprintf(str+strlen(str),"%li",usr.gid);
    if (send_message(200,0,str,NULL) == -1) return -1;
    strcpy(str,"Secondary groups:");
    for (i=0; i<8; i++) if (usr.gids[i] != -1)
    { strcat(str," ");
      name = get_groupname(usr.gids[i]);
      if (name) strcat(str,name); else
        sprintf(str+strlen(str),"%li",usr.gids[i]);
    }
    if (strlen(str) == 17) strcat(str," (none)");
    return send_reply(200,str);
  }
  if (n == 1)
  { printf("GRP %s\n",*p);
    fgets(str, sizeof(str), stdin);
    if (*str == '-') return send_reply(550,str+1);
    if (send_message(200,0,str+1,NULL) == -1) return -1;
    fgets(str, sizeof(str), stdin);
    return send_reply(200,str+1);
  }
  if (n == 2)
  { printf("GRP %s %s\n",*p,*(p+1));
    fgets(str, sizeof(str), stdin);
    if (*str == '-') return send_reply(550,str+1);
    return send_reply(200,str+1);
  }
  return cmd_site_groups_syntax(501);
}

int cmd_site_idle_syntax(int rno)
{ return send_reply(rno,"Syntax: IDLE <timeout>\nChanges your maximum idletime.");
}

int cmd_site_idle(char **p, int n)
{ return send_reply(550,"Currently not implemented.");
}

int cmd_site_info_syntax(int rno)
{ return send_reply(rno,"Syntax: INFO <user_information>\n"
    "<user_information> = current user's new user information\n"
    "This command sets the user information of the current user. The user\n"
    "information is displayed in top ten lists. The length is limited.");
}

int cmd_site_info(char** p, int n)
{ char str[128];

  if (n > 1) return cmd_site_info_syntax(501);
  if (!n) printf("UIN\n"); else printf("UIN %s\n",*p);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_kick_syntax(int rno)
{ return send_reply(rno,"Syntax: KICK <user> [<signal_no>]\n"
    "<user> = name of logged in user   <signal_no> = number of signal to send\n"
    "This sends a signal to the user's process, thereby kicking the user from\n"
    "the server. The default signal is SIGQUIT.");
}

int cmd_site_kick(char** p, int n)
{ char str[128];

  if ((n < 1) || (n > 2)) return cmd_site_kick_syntax(501);
  printf("KCK %s %s\n",*p,n == 2 ? *(p+1) : "");
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_max_syntax(int rno)
{ return send_reply(rno,"Syntax: MAX <anon> <user>\n"
    "<anon> = maximum number of anonymous users\n"
    "<user> = maximum number of registered users\n"
    "Changes will take place immediately, but nobody will be forced to log out.");
}

int cmd_site_max(char** p, int n)
{ char str[128];

  if (n != 2) return cmd_site_max_syntax(501);
  printf("MAX %s %s\n",*p,*(p+1));
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_mod_syntax(int rno)
{ return send_reply(rno,"Syntax: MOD [<directory>]\n"
    "Shows the permissions of a directory.");
}

void cat_permissions(char* s, long p)
{ if (p & P_NOLIMIT) strcat(s," (no limit)");
  if ((p & P_ALL) == P_ALL)
  { strcat(s," (full rights)");
    return;
  }
  if (!p)
  { strcat(s," (no rights)");
    return;
  }
  if (p & P_CHDIR) strcat(s," cd");
  if (p & P_LIST) strcat(s," ls");
  if (p & P_MKDIR) strcat(s," mkdir");
  if (p & P_CHMOD) strcat(s," chmod");
  if (p & P_READ) strcat(s," get");
  if (p & P_WRITE) strcat(s," put");
  if (p & P_OVERWRITE) strcat(s," overwrite");
}

void cat_file_permissions(char* s, long p)
{ if (!p)
  { strcat(s," (no rights)");
    return;
  }
  if (p & FP_READ) strcat(s," read");
  if (p & FP_WRITE) strcat(s," write");
  if (p & FP_CHMOD) strcat(s," chmod");
}

int cmd_site_mod(char** p, int n)
{ char*          dir;
  long           gid;
  char*          name;
  long           perm;
  int            se;
  char           str[64];
  long           uid;

  if (n > 1) return cmd_site_mod_syntax(501);
  if (n == 1)
  { se = permission_ok(*p,P_CHDIR);
    if ((se == -1) && (errno == ENOTDIR))
    { split_dir(*p,&dir,&name,0);
      se = permission_ok(dir,P_CHDIR);
      if (se != -1)
      { se = read_file_permission(dir,name,&uid,&gid,&perm);
        if (se != -1)
        { strcpy(str,"owner [");
          name = get_username(uid);
          if (name) sprintf(str+strlen(str),"%-9s",name); else
            sprintf(str+strlen(str),"%-9li",uid);
          strcat(str,"] :");
          cat_file_permissions(str,(perm >> 16) & 0xFF);
          if (send_message(250,0,str,NULL) == -1) return -1;
          strcpy(str,"group [");
          name = get_groupname(gid);
          if (name) sprintf(str+strlen(str),"%-9s",name); else
            sprintf(str+strlen(str),"%-9li",gid);
          strcat(str,"] :");
          cat_file_permissions(str,(perm >> 8) & 0xFF);
          if (send_message(250,0,str,NULL) == -1) return -1;
          strcpy(str,"others            :");
          cat_file_permissions(str,perm & 0xFF);
          return send_reply(250,str);
        } else errno = EACCES;
      }
    }
    if (!se) se = read_permission_file(*p,&uid,&gid,&perm,NULL,NULL,NULL);
  } else
  { se = permission_ok("",P_CHDIR);
    if (!se) se = read_permission_file("",&uid,&gid,&perm,NULL,NULL,NULL);
  }
  if (!se)
  { strcpy(str,"owner [");
    name = get_username(uid);
    if (name) sprintf(str+strlen(str),"%-9s",name); else
      sprintf(str+strlen(str),"%-9li",uid);
    strcat(str,"] :");
    cat_permissions(str,(perm >> 16) & 0xFF);
    if (send_message(250,0,str,NULL) == -1) return -1;
    strcpy(str,"group [");
    name = get_groupname(gid);
    if (name) sprintf(str+strlen(str),"%-9s",name); else
      sprintf(str+strlen(str),"%-9li",gid);
    strcat(str,"] :");
    cat_permissions(str,(perm >> 8) & 0xFF);
    if (send_message(250,0,str,NULL) == -1) return -1;
    strcpy(str,"others            :");
    cat_permissions(str,perm & 0xFF);
    return send_reply(250,str);
  }
  return send_error(550,"mod",0);
}

int cmd_site_open_syntax(int rno)
{ return send_reply(rno,"Syntax: OPEN\n"
    "Non-sysop logins will be permitted again.");
}

int cmd_site_open(char** p, int n)
{ char str[128];

  if (n) return cmd_site_open_syntax(501);
  fputs("SOP\n",stdout);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_passwd_syntax(int rno)
{ return send_reply(rno,"Syntax: PASSWD <password> [<user>]\n"
    "Changes password for you or for specified <user>. You must be superuser\n"
    "in order to change someone others password.");
}

int cmd_site_passwd(char ** p, int n)
{ char str[128];

  if (n<1 || n>2) return cmd_site_passwd_syntax(501);
  if (n == 1) printf("UCP %s\n",*p); else
    printf("UCP %s %s\n",*p,*(p+1));
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_reconfig_syntax(int rno)
{ return send_reply(rno,"Syntax: RECONFIG <config_filename>\n"
    "Reconfigures the server by reading the configuration file again. You\n"
    "must give the full path to the configuration file.");
}

int cmd_site_reconfig(char ** p, int n)
{ char str[128];

  if (n != 1) return cmd_site_reconfig_syntax(501);
  printf("REC %s\n",*p);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_run_syntax(int rno)
{ return send_reply(rno,"Syntax: RUN <script> [<parameters>]\n"
    "Runs a user script.");
}

int cmd_site_run_script(char** p, int n, int display_exit)
{ 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];

  if(!superuser() && display_exit) 
  return send_reply(550,"Only superusers may use this command.");
  if (!n) return cmd_site_run_syntax(501);
  if (!*script_path) return send_reply(550,"No script path set.");
  err = pipe(stdinpipe);
  if (!err) err = pipe(stdoutpipe);
  if (err) return send_error(550,"pipe",0);
  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 send_error(550,"fork",0);
  if (pid)
  { close(stdinpipe[1]);
    close(stdoutpipe[0]);
    file = fdopen(stdinpipe[0],"rt");
    if (!file) return send_reply(550,"fdopen");
    if (display_exit) c = (char*)-1; else
    { c = fgets(str,sizeof(str)-1,file);
      if (!c) strcpy(str,"(no output from program)");
    }
    while (c)
    { c = fgets(str2,sizeof(str2)-1,file);
      if (c && strlen(str2))
      { if (display_exit)
      { if (send_message(200,0,str2,NULL) == -1) return -1;
      } else
      { if (send_message(200,0,str,NULL) == -1) return -1;
        strcpy(str,str2);
      }}
    }
    fclose(file);
    pid = wait(&err);
    if (WIFEXITED(err))
    { if (WEXITSTATUS(err) == 255) sprintf(str,"Script does not exist.");
        else if (display_exit)
          sprintf(str,"Script finished with exitcode %i.",WEXITSTATUS(err));
    } else if (display_exit)
    { sprintf(str,"Script terminated on signal %i.",WTERMSIG(err));
    }
    return send_reply(200,str);
  } else
  { fclose(sockf);
    if (strcmp(cwd,"/")) chdir(cwd+1);
    close(stdinpipe[0]);
    close(stdoutpipe[1]);
    err = dup2(stdinpipe[1],1);
    if (err != -1) err = dup2(stdinpipe[1],2);
    if (err != -1) err = dup2(stdoutpipe[0],0);
    *(p+n) = NULL;
    if (err != -1)
    { strcpy(str,script_path);
      if (strcmp(str,"/")) strcat(str,"/");
      strcat(str,*p);
      execv(str,p);
      strcpy(str,script_path);
      if (strcmp(str,"/")) strcat(str,"/");
      c = get_groupname(usr.gid);
      if (c)
      { strcat(str,c);
        strcat(str,"/");
        strcat(str,*p);
        err = execv(str,p);
      }
    }
    exit(-1);
  }
}

int cmd_site_run(char** p, int n)
{ return cmd_site_run_script(p,n,1);
}

int cmd_site_shutdown_syntax(int rno)
{ return send_reply(rno,
    "Syntax: SHUTDOWN\n"
    "Force all connections to close and ftp server process to terminate.\n"
    "Note that you will be disconnected immediately, too.");
}

int cmd_site_shutdown(char** p, int n)
{ char str[128];

  if (n) return cmd_site_shutdown_syntax(501);
  fputs("SSD\n",stdout);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  if (*str == '-') return send_reply(550,str+1);
  quit(CL_SHUTDOWN);
  return send_reply(421,str+1);
}

int cmd_site_user_syntax(int rno)
{ return send_reply(rno,
    "Implemented USER commands, syntax: SITE USER <command> [<arguments>]\n"
    "                               or: SITE USR <command> [<arguments>]\n"
    "ADD <name> [<template>]                 - add user\n"
    "ADDSU <name> [<template>]               - add superuser\n"
    "CHANGE <name> NAME=<s>|UID=<n>|GID=<n>|KBDOWN[+|-]=<n>|KBUP[+|-]=<n>|\n"
    "              MBDOWN[+|-]=<n>|MBUP[+|-]=<n>|LIMIT=<n>|IP[0-9]=<r>|\n"
    "              INFO=<s>|CREDIT[+|-]=<n>|MBCRED[+|-]=<n>|CLASS=[a|n|r|s|t]|\n"
    "              LOGINS=<n>|SUBCLASS={g|e|-}|EXTRA[+|-]={c|i|p|*|-}\n"
    "                                        - change user attributes\n"
    "LIST [<options>] [<user>|<group>]       - list all or specified user(s)\n"
    "LISTA                                   - list all logged in users\n"
    "REMOVE <name>                           - remove user\n"
    "RESET { up | down | credit }            - resets counter for all users");
}

int cmd_site_user(char** p, int n)
{ int  se = 0;
  char str[1024];

  if (!n) se = 1; else if (!strcasecmp(*p,"ADD"))
  { if ((n < 2) || (n > 3)) se = 1; else
      printf("UAD %s %s\n",*(p+1),n == 3 ? *(p+2) : "");
  } else if (!strcasecmp(*p,"ADDSU"))
  { if ((n < 2) || (n > 3)) se = 1; else
      printf("UAS %s %s\n",*(p+1),n == 3 ? *(p+2) : "");
  } else if (!strcasecmp(*p,"CHANGE"))
  { if (n < 3) se = 1; else
    { printf("UCH %s %s",*(p+1),*(p+2));
      n -= 3;
      p += 3;
      while (n--)
      { printf(" %s",*p);
        p++;
      }
      printf("\n");
    }
  } else if (!strcasecmp(*p,"LISTDUMP"))
  { printf("ULD\n");
    do
    { if (!fgets(str,sizeof(str),stdin)) strcpy(str,"\n");
      if (strcmp(str,"\n")) if (send_message(211,0,str,NULL) == -1) return -1;
    } while (strcmp(str,"\n"));
    return send_reply(211,"<end of list>");
  } else if(!strcasecmp(*p,"LIST") || !strcasecmp(*p,"LISTA"))
  { if (n > 3) se = 1; else
    { if (!strcasecmp(*p,"LIST"))
        printf("ULI %s %s\n",n>=2 ? *(p+1) : "", n==3 ? *(p+2) : "");
      else printf("ULL %s %s\n",n==2 ? *(p+1) : "", n==3 ? *(p+2) : "");
      if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
      while (*str=='+' && strcmp(str,"+\n"))
      { if (send_message(211,0,str+1,NULL) == -1) return -1;
        if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-");
      }
      if (*str == '+') return send_reply(211,"<end of list>");
      if (strcmp(str,"-")) return send_reply(550,str+1);
        else return send_reply(211,"pipe error, aborting list");
    }
  } else if (!strcasecmp(*p,"REMOVE"))
  { if (n != 2) se = 1; else printf("URM %s\n",*(p+1));
  } else if (!strcasecmp(*p,"RESET"))
  { if (n != 2) se = 1; else printf("URS %s\n",*(p+1));
  } else se = 1;
  if (se) return cmd_site_user_syntax(501);
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  return send_reply(*str == '+' ? 200 : 550,str+1);
}

int cmd_site_w_syntax(int rno)
{ return send_reply(rno,
    "Syntax: SITE W\n"
    "    or: SITE WHO\n" 
    "Lists user activity.");
}

int cmd_site_w(char** p, int n)
{ char str[512];
  if (n) return cmd_site_w_syntax(501);
  printf("WHO\n");
  if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-pipe error");
  while (*str=='+' && strcmp(str,"+\n"))
  { if (send_message(211,0,str+1,NULL) == -1) return -1;
    if (!fgets(str,sizeof(str),stdin)) strcpy(str,"-");
  }
  if (*str == '+') return send_reply(211," ");
  if (strcmp(str,"-")) return send_reply(550,str+1);
    else return send_reply(211,"pipe error, aborting list");
}

int cmd_site_help_syntax(int rno)
{ if (superuser() || groupop() || useredit()) return send_reply(rno,
    "Implemented SITE commands, syntax: SITE <command> [<argument(s)>]\n"
    "ADDUSER   BIND      CHMOD     CHMODR    CHOWN     CLOSE     COLOR\n"
    "COUNT     DEL       DISABLE   DUMP      ENABLE    EXEC      FF   \n"
    "GROUP     GROUPS    HELP      INFO      KICK      MAX       MOD  \n"
    "OPEN      PASSWD    RECONFIG  RUN       SHUTDOWN  USR/USER  W/WHO\n"
    "IDLE\n"
    "Type SITE HELP <command> to get more help.");
  return send_reply(rno,
    "Implemented SITE commands, syntax: SITE <command> [<argument(s)>]\n"
    "CHMOD     CHMODR    COLOR     COUNT     FF        GROUPS    HELP \n"
    "INFO      MOD       PASSWD    RUN       W/WHO     IDLE\n"
    "Type SITE HELP <command> to get more help.");
}

int cmd_site_ff(char**,int);
int cmd_site_ff_syntax(int);
int cmd_site_help(char**,int);

struct sitecmd_s
{ char command[10];
  int  (*function)(char**,int);
  int  (*syntaxfnc)(int);
} site_command[] =
{ { "ADDUSER",  cmd_site_adduser,  cmd_site_adduser_syntax  },
  { "BIND",     cmd_site_bind,     cmd_site_bind_syntax     },
  { "CHMOD",    cmd_site_chmod,    cmd_site_chmod_syntax    },
  { "CHMODR",   cmd_site_chmodr,   cmd_site_chmodr_syntax   },
  { "CHOWN",    cmd_site_chown,    cmd_site_chown_syntax    },
  { "CLOSE",    cmd_site_close,    cmd_site_close_syntax    },
  { "COLOR",    cmd_site_color,    cmd_site_color_syntax    },
  { "COUNT",    cmd_site_count,    cmd_site_count_syntax    },
  { "DEL",      cmd_site_del,      cmd_site_del_syntax      },
  { "DISABLE",  cmd_site_disable,  cmd_site_disable_syntax  },
  { "DUMP",     cmd_site_dump,     cmd_site_dump_syntax     },
  { "ENABLE",   cmd_site_enable,   cmd_site_enable_syntax   },
  { "EXEC",     cmd_site_exec,     cmd_site_exec_syntax     },
  { "FF",       cmd_site_ff,       cmd_site_ff_syntax       },
  { "GROUP",    cmd_site_group,    cmd_site_group_syntax    },
  { "GROUPS",   cmd_site_groups,   cmd_site_groups_syntax   },
  { "HELP",     cmd_site_help,     cmd_site_help_syntax     },
  { "IDLE",     cmd_site_idle,     cmd_site_idle_syntax     },
  { "INFO",     cmd_site_info,     cmd_site_info_syntax     },
  { "KICK",     cmd_site_kick,     cmd_site_kick_syntax     },
  { "MAX",      cmd_site_max,      cmd_site_max_syntax      },
  { "MOD",      cmd_site_mod,      cmd_site_mod_syntax      },
  { "OPEN",     cmd_site_open,     cmd_site_open_syntax     },
  { "PASSWD",   cmd_site_passwd,   cmd_site_passwd_syntax   },
  { "RECONFIG", cmd_site_reconfig, cmd_site_reconfig_syntax },
  { "RUN",      cmd_site_run,      cmd_site_run_syntax      },
  { "SHUTDOWN", cmd_site_shutdown, cmd_site_shutdown_syntax },
  { "USER",     cmd_site_user,     cmd_site_user_syntax     },
  { "USR",      cmd_site_user,     cmd_site_user_syntax     },
  { "VERSION",  cmd_site_version,  cmd_site_version_syntax  },
  { "W",        cmd_site_w,        cmd_site_w_syntax        },
  { "WHO",      cmd_site_w,        cmd_site_w_syntax        } };

int cmd_site_help(char** p, int n)
{ int i = 0;
  int j;
  int k = (sizeof(site_command) / sizeof(struct sitecmd_s)) - 1;
  int r;

  if (n != 1) return cmd_site_help_syntax(501);
  while (i <= k)
  { j = (i + k) >> 1;
    r = strcasecmp(*p,site_command[j].command);
    if (r < 0) k = j - 1; else if (r > 0) i = j + 1; else
      return site_command[j].syntaxfnc(214);
  }
  return cmd_site_help_syntax(501);
}

int cmd_site(char** p, int n)
{ int   i = 0;
  int   j;
  int   k = (sizeof(site_command) / sizeof(struct sitecmd_s)) - 1;
  char* q;
  int   r;
  char* s;

  if (!n) return cmd_site_help_syntax(501);
  if (shortcut && strcasecmp(*p,"SITE"))
  { r = strlen(*p);
    q = shortcut;
    do
    { skipspace(&q);
      s = q;
      skipnspace(&q);
      if ((r == q-s) && !strncasecmp(*p,s,r))
        return cmd_site_run_script(p,n,0);
    } while (*q);
  }
  if ((n >= 2) && !strcasecmp(*p,"SITE"))
  { p++;
    n--;
  }
  while (i <= k)
  { j = (i + k) >> 1;
    r = strcasecmp(*p,site_command[j].command);
    if (r < 0) k = j - 1; else if (r > 0) i = j + 1; else
      return site_command[j].function(p+1,n-1);
  }
  return send_reply(501,"Unknown SITE command.");
}

/* EOF */
