/*   configd.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 <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <ix.h>

#include "tweak.h"
#ifdef DES_ENCRYPTION
  #include "des.h"
#endif
#include "externsd.h"

void* alloc(void* buf, long size)
{ if (buf) return realloc(buf,size); else return malloc(size);
}

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 readline(char* line, int linelen, FILE* file)
{ char* r;

  r = fgets(line,linelen,file);
  strip_crlf(line);
  return (r != NULL);
}

void config_error(int lineno, char* line)
{ fprintf(stderr,"syntax error in configuration file on line %i, aborting.\n",lineno);
  fprintf(stderr,"\"%s\"\n",line);
}

int get_integer(char* line, int* i, int min, int max)
{ int n;

  n = sscanf(line,"%i",i);
  if (n == 1) if ((*i>=min) && (*i<=max)) return 0; else return -1;
    else return -1;
}

int get_long(char* line, long* i, long min, long max)
{ int n;

  n = sscanf(line,"%li",i);
  if (n == 1) if ((*i>=min) && (*i<=max)) return 0; else return -1;
    else return -1;
}

int get_string(char* p, char** buffer)
{ char* buf;
  char* q;

  q = p;
  skipnspace(&q);
  buf = alloc(*buffer,q-p+1);
  if (!buf)
  { fprintf(stderr,"get_string: out of memory\n");
    return -1;
  }
  *buffer = buf;
  strncpy(buf,p,q-p);
  *(buf+(q-p)) = '\0';
  if (skipspace(&q)) return -1; else return 0;
}

int get_dirstring(char* p, char** buffer)
{ char* buf;
  int   len = 0;
  char* q = p;
  int   size;

  skipnspace(&q);
  size = q-p;
  if (*p != '/')
  { if (!server_dir)
    { fprintf(stderr,"get_dirstring: specify ServerDir first\n");
      return -1;
    }
    len = strlen(server_dir);
    size += len;
  }
  buf = alloc(*buffer,size+1);
  if (!buf)
  { fprintf(stderr,"get_dirstring: out of memory\n");
    return -1;
  }
  *buffer = buf;
  if (len) strcpy(buf,server_dir);
  strncpy(buf+len,p,size-len);
  *(buf+size) = '\0';
  if (skipspace(&q)) return -1; else return 0;
}

int get_line(char* p, char** buffer)
{ char* buf;

  buf = alloc(*buffer,strlen(p));
  if (!buf)
  { fprintf(stderr,"get_line: out of memory\n");
    return -1;
  }
  *buffer = buf;
  strcpy(buf,p);
  return 0;
}

int get_onoff(char* p, char* boolean)
{ char  c;
  char* q;

  q = p;
  c  = skipnspace(&q);
  *q = '\0';
  if (!strcasecmp(p,"on")) *boolean = 1; else
    if (!strcasecmp(p,"off")) *boolean = 0; else return -1;
  *q = c;
  if (skipspace(&q)) return -1; else return 0;
}

int get_text(FILE* file, char* line, int linelen, char* q, int* lineno,
	     int* buffer_len,int* buffer_size, char** buffer, char* quitstr)
{ char* buf;
  int   end = 0;
  int   ok;

  if (*q) return -1;
  *buffer_len = 0;
  ok = readline(line,linelen,file);
  (*lineno)++;
  while (ok && !end)
  { if (strcasecmp(line,quitstr))
    { strcat(line,"\n");
      if (*buffer_len+strlen(line) >= *buffer_size)
      { buf = alloc(*buffer,*buffer_size+1024);
	if (!buf)
	{ fprintf(stderr,"get_text: out of memory\n");
	  fclose(file);
	  return -1;
	}
	*buffer = buf;
	*buffer_size += 1024;
      }
      strcpy((*buffer)+(*buffer_len),line);
      (*buffer_len) += strlen(line);
      ok = readline(line,linelen,file);
      (*lineno)++;
    } else end = 1;
  }
  if (end) return 0; else return -1;
}

int get_ident(char** ipr, char* ident)
{ char* p;
  int	i;
  
  p = *ipr;
  i = 0;

  /* remove the ident if there is no @ in the whole ipstring */
  if (strchr(*ipr,'@') == NULL)
  { ident[0]='\0';
    return 0;
  }
  while (*p && (*p!='@') && (*p!='.') && (*p!=',') && (*p!=':')) {i++; p++; }
  if (!i) return -1;
  if (*p=='@')
  { *ipr=p+1;
    if (i>11) {p=p-i+11; i=11;}
    *p='\0';
    if (!sscanf(p-i,"%s",ident)) return -1;
  }
  if (ident[0]=='*') ident[0]='\0'; /* we dont need *@, so we remove the ident ... */
  return 0;
}

int get_oneip(char** ipr, char* l, char* h)
{ char  c;
  int   i;
  char* p;

  p = *ipr;
  if (!skipspace(&p)) return -1;
  if (*p == '*')
  { *l = 0x00;
    *h = 0xFF;
    p++;
    skipspace(&p);
    *ipr = p;
    return 0;
  }
  i = 0;
  while (*p && isdigit(*p) && (i<3)) { i++; p++; }
  if (!i) return -1;
  c = *p;
  *p = '\0';
  if (!sscanf(p-i,"%i",&i)) return -1;
  if ((i<0) || (i>255)) return -1;
  *l = i;
  *p = c;
  if (skipspace(&p) != '-')
  { *h = i;
    *ipr = p;
    return 0;
  }
  p++;
  if (!skipspace(&p)) return -1;
  i = 0;
  while (*p && isdigit(*p) && (i<3)) { i++; p++; }
  if (!i || isdigit(*p)) return -1;
  c = *p;
  *p = '\0';
  if (!sscanf(p-i,"%i",&i)) return -1;
  if ((i<0) || (i>255)) return -1;
  *p = c;
  *h = i;
  skipspace(&p);
  *ipr = p;
  return 0;
}

int get_iprange(char** ipr, char* ident)
{ if (get_ident(ipr, ident) == -1) return -1;
  if (get_oneip(ipr,&ident[12],&ident[13]) == -1) return -1;
  if (**ipr != '.') return -1;
  (*ipr)++;
  if (get_oneip(ipr,&ident[14],&ident[15]) == -1) return -1;
  if (**ipr != '.') return -1;
  (*ipr)++;
  if (get_oneip(ipr,&ident[16],&ident[17]) == -1) return -1;
  if (**ipr != '.') return -1;
  (*ipr)++;
  if (get_oneip(ipr,&ident[18],&ident[19]) == -1) return -1;
  return 0;
}

int get_prog(char* p, char** buffer)
{ char* buf;
  char* q;
  FILE* file;

  q = p;
  skipnspace(&q);
  buf = alloc(*buffer,q-p+1);
  *buffer = buf;
  strncpy(buf,p,q-p); 
  *(buf+(q-p)) = '\0';
  file = fopen(buf,"rt");
  if (file == NULL)
  { perror("ftpd: server_program is not correct. please correct");
    return -1;
  }
  fclose(file);
  if (skipspace(&q)) return -1; else return 0;
}

void inituser(struct user_s* u)
{ if (u->limit)
  { u->credit = (u->kb_uploaded * 100 / u->limit) - u->kb_downloaded;
  } else u->credit = 0;
  memset(u->gids,-1,sizeof(u->gids));
  if (!u->uid) u->class = UC_ROOT; else
    if (!strcasecmp(u->name,"anonymous") || !strcasecmp(u->name,"ftp"))
      u->class = UC_ANON; else u->class = 0x10 | UC_NORMAL;
}

struct user_s* insert_user(struct user_s* new)
{ struct child_s* c;
  int             i = 0;
  int             j;
  int             k = user_count - 1;
  struct user_s*  u;

  if (user_count >= user_size)
  { user_size += 16;
    u = (struct user_s*)alloc(user,user_size*sizeof(struct user_s));
    if (!u)
    { user_size -= 16;
      return NULL;
    }
    c = child;
    for (j=0; j<children; j++, c++) if (c->user) c->user = u+(c->user-user);
    user = u;
  }
  while (i <= k)
  { j = (i + k) >> 1;
    if (new->uid >= (user+j)->uid) i = j + 1; else k = j - 1;
  }
  k++;
  if (k < user_count)
  { memmove(user+k+1,user+k,(user_count-k)*sizeof(*u));
    c = child;
    for (j=0; j<children; j++, c++)
      if (c->user && (c->user != (struct user_s*)-1))
      if (c->user-user >= k) c->user++;
  }
  *(user+k) = *new;
  user_count++;
  return user+k;
}

int get_user(char* p)
{ char          c;
  int           i;
  char*         q;
  struct user_s usr;

  skipspace(&p);
  q = p;
  if (!skiptocolon(&q)) return -1;
  if (q-p<1 || q-p>=sizeof(usr.name)) return -1;
  strncpy(usr.name,p,q-p);
  *(usr.name+(q-p)) = '\0';
  if (!skipspctocolon(&q)) return -1;
  p = q;
  if (!skiptocolon(&q)) return -1;
  c  = *q;
  *q = '\0';
  if (!sscanf(p,"%li",&usr.uid)) return -1;
  *q = c;
  if (!skipspctocolon(&q)) return -1;
  p = q;
  if (!skiptocolon(&q)) return -1;
  c  = *q;
  *q = '\0';
  if (!sscanf(p,"%li",&usr.gid)) return -1;
  *q = c;
  if (!skipspctocolon(&q)) return -1;
  p = q;
  if (!skiptocolon(&q)) return -1;
  if (q-p>=sizeof(usr.password)) return -1;
  strncpy(usr.password,p,q-p);
  *(usr.password+(q-p)) = '\0';
  if (!skipspctocolon(&q)) return -1;
  p = q;
  if (!skiptocolon(&q)) return -1;
  c  = *q;
  *q = '\0';
  if (!sscanf(p,"%li",&usr.kb_downloaded)) return -1;
  *q = c;
  if (!skipspctocolon(&q)) return -1;
  p = q;
  skiptocolon(&q);
  c  = *q;
  *q = '\0';
  if (!sscanf(p,"%li",&usr.kb_uploaded)) return -1;
  *q = c;
  if (!skipspctocolon(&q)) return -1;
  p = q;
  skiptocolon(&q);
  c  = *q;
  *q = '\0';
  if (!sscanf(p,"%li",&usr.last_login)) return -1;
  *q = c;
  if (!skipspctocolon(&q)) return -1;
  for (i=0;i<10;i++) memset(usr.idents[i],'\0',20);
  i = 0;
  while (i < 10)
  { if (get_iprange(&q,usr.idents[i]) == -1) return -1;
    skipspace(&q);
    if (*q == ',')
    { q++;
      if (++i == 10 ) return -1;
    } else i = 10;
  }
  if (!skipspctocolon(&q)) return -1;
  p = q;
  c = skiptocolon(&q);
  *q = '\0';
  if (!sscanf(p,"%i",&usr.limit)) return -1;
  *q = c;
  if (skipspctocolon(&q))
  { p = q;
    c = skiptocolon(&q);
    *q = '\0';
    if (strlen(p) >= sizeof(usr.info)) return -1;
    strcpy(usr.info,p);
    *q = c;
    if (skipspctocolon(&q))
    { p = q;
      skiptocolon(&q);
      c  = *q;
      *q = '\0';
      if (!sscanf(p,"%li",&usr.class)) return -1;
      *q = c;
      if (!skipspctocolon(&q)) return -1;
      p = q;
      skiptocolon(&q);
      c  = *q;
      *q = '\0';
      if (!sscanf(p,"%li",&usr.credit)) return -1;
      *q = c;
      memset(usr.gids,-1,sizeof(usr.gids));
      if (skipspctocolon(&q))
      { i = 0;
	while (i < sizeof(usr.gids) / sizeof(long))
	{ skipspace(&q);
	  p = q;
	  while (*q && (*q != ',') && (*q != ':')) q++;
	  if (q > p)
	  { c = *q;
	    *q = '\0';
	    if (!sscanf(p,"%li",usr.gids+i)) return -1;
	    *q = c;
	    if (c) q++;
	    if (++i == sizeof(usr.gids) / sizeof(long)) return -1;
	  } else i = sizeof(usr.gids) / sizeof(long);
	}
      }
      if (skipspace(&q)) return -1;
    } else inituser(&usr);
  } else
  { strcpy(usr.info,"");
    inituser(&usr);
  }
  usr.kb_up_tmp = usr.kb_down_tmp = 0;
  if (!insert_user(&usr))
  { fprintf(stderr,"get_user: out of memory\n");
    return -1;
  }
  return 0;
}

int get_group(char* p)
{ char            c;
  char*           q;
  struct group_s* grp;

  if (group_count >= group_size)
  { group_size += 4;
    grp = (struct group_s*)alloc(group,group_size*sizeof(struct group_s));
    if (!grp)
    { group_size -= 4;
      fprintf(stderr,"get_group: out of memory\n");
      return -1;
    }
  } else grp = group;
  group = grp;
  grp = group+group_count;
  skipspace(&p);
  q = p;
  if (!skiptocolon(&q)) return -1;
  if (q-p<1 || q-p>=sizeof(grp->name)) return -1;
  strncpy(grp->name,p,q-p);
  *((grp->name)+(q-p)) = '\0';
  if (!skipspctocolon(&q)) return -1;
  p = q;
  skipnspace(&q);
  c  = *q;
  *q = '\0';
  if (!sscanf(p,"%li",&grp->gid)) return -1;
  *q = c;
  if (skipspace(&q)) return -1;
  group_count++;
  return 0;
}

#ifdef DES_ENCRYPTION
int load_user_file_des(const char* filename, int force)
{ char*          buffer;
  C_Block        c;
  FILE*          file;
  long           n;
  C_Block        out;
  Key_schedule   s;
  struct user_s* usr;

  file = fopen(filename,"rb");
  if (file == NULL)
  { if (!force) return 0;
    perror("ftpd: cannot open account file");
    return -1;
  }
  fseek(file,0,SEEK_END);
  n = ftell(file) / sizeof(struct user_s);
  rewind(file);
  if (user_count+n >= user_size)
  { user_size = user_count+n+16;
    user = (struct user_s*)alloc(user,user_size*sizeof(struct user_s));
    if (!user)
    { fprintf(stderr,"ftpd: out of memory (user file)\n");
      fclose(file);
      return -1;
    }
  }
  c = user_key;
  des_set_key(&c,&s);
  if (fread(user+user_count,sizeof(struct user_s),n,file) != n)
  { fprintf(stderr,"ftpd: error reading user file (errno=%i)\n",errno);
    fclose(file);
    return -1;
  }
  fclose(file);
  buffer = (char*)(user+user_count);
  user_count += n;
  n *= sizeof(struct user_s);
  while (n >= 8)
  { des_cbc_encrypt((C_Block*)buffer,&out,8,&s,&c,DES_DECRYPT);
    memmove(buffer,&out,8);
    buffer += 8;
    n -= 8;
  }
  for (n=0; n<user_count; n++) (user+n)->kb_down_tmp = (user+n)->kb_up_tmp = 0;
  return 0;
}
#endif

int load_user_file(const char* filename, int force)
{ FILE* file;
  char  line[512];
  int   lineno = 0;
  char* p;

#ifdef DES_ENCRYPTION
  if (user_encrypted) return load_user_file_des(filename,force);
#endif
  file = fopen(filename,"rt");
  if (file == NULL)
  { if (!force) return 0;
    perror("ftpd: cannot open account file");
    return -1;
  }
  while (readline(line,sizeof(line),file))
  { lineno++;
    p = line;
    skipspace(&p);
    if (*p && (*p!='#')) if (get_user(p) == -1)
    { fprintf(stderr,"syntax error in account configuration file on line %i, aborting.\n",lineno);
      fprintf(stderr,"\"%s\"\n",line);
      fclose(file);
      return -1;
    }
  }
  fclose(file);
  return 0;
}

int load_group_file(const char* filename, int force)
{ FILE* file;
  char  line[256];
  int   lineno = 0;
  char* p;

  file = fopen(filename,"rt");
  if (file == NULL)
  { if (!force) return 0;
    perror("ftpd: cannot open group file");
    return -1;
  }
  while (readline(line,sizeof(line),file))
  { lineno++;
    p = line;
    skipspace(&p);
    if (*p && (*p!='#')) if (get_group(p) == -1)
    { fprintf(stderr,"syntax error in group configuration file on line %i, aborting.\n",lineno);
      fprintf(stderr,"\"%s\"\n",line);
      fclose(file);
      return -1;
    }
  }
  fclose(file);
  return 0;
}

int get_cdpath(char* p)
{ char* cd;
  long  gid;
  int   newsize;
  long  perm;
  char* q = p;
  long  uid;

  if (!skipnspace(&q)) return -1;
  *(q++) = '\0';
  if (sscanf(q,"%li %li %li",&uid,&gid,&perm) != 3) return -1;
  while (cdpath_len+strlen(p)+32 >= cdpath_size)
  { newsize = cdpath_size + 1024;
    cd = alloc(cdpath,newsize);
    if (!cd)
    { fprintf(stderr,"ftpd: out of memory (cdpath)\n");
      return -1;
    }
    cdpath = cd;
    cdpath_size = newsize;
  }
  strcpy(cdpath+cdpath_len,p);
  q = cdpath+cdpath_len+((strlen(p)+8) & ~0x07);
  *(long*)q = uid;
  *(long*)(q+sizeof(long)) = gid;
  *(long*)(q+(sizeof(long)*2)) = perm;
  q += sizeof(long) * 3;
  cdpath_len = q-cdpath;
  return 0;
}

int get_check(char* p, struct check_s** check, int* check_count,
  int* check_size)
{ struct check_s* ch;
  char            extension[sizeof(ch->extension)];
  char            name[sizeof(ch->name)];
  char            param[sizeof(ch->param)];
  char*           q = p;

  if (skipnspace(&q))
  { if (q-p >= sizeof(extension)) return -1;
    strncpy(extension,p,q-p);
    *(extension+(q-p)) = '\0';
    if (!skipspace(&q)) return -1;
    p = q;
  } else return -1;
  skipnspace(&q);
  if (q-p >= sizeof(name)) return -1;
  strncpy(name,p,q-p);
  *(name+(q-p)) = '\0';
  if (*q)
  { if (skipspace(&q))
    { p = q;
      skipnspace(&q);
      if (q-p >= sizeof(param)) return -1;
      strncpy(param,p,q-p);
      *(param+(q-p)) = '\0';
      if (skipspace(&q)) return -1;
    } else *param = '\0';
  } else *param = '\0';
  if (*check_count == *check_size)
  { ch = (struct check_s*)alloc((char*)*check,
      (*check_size+4)*sizeof(struct check_s));
    if (!ch)
    { fprintf(stderr,"ftpd: out of memory (check)\n");
      return -1;
    }
    *check = ch;
    *check_size += 4;
  }
  ch = *check + *check_count;
  (*check_count)++;
  strcpy(ch->extension,extension);
  strcpy(ch->name,name);
  strcpy(ch->param,param);
  return 0;
}

int get_dshortcut(char *p, struct dshortcut_s** dshortcut2,
		  int* dshortcut_count, int* dshortcut_size)
{ struct dshortcut_s* dsh = *dshortcut2;
  char                shortname[sizeof(dsh->shortname)];
  char                longname[sizeof(dsh->longname)];
  char*               q = p;

  if (skipnspace(&q))
  { 
    if (q-p >= sizeof(shortname)) 
	return -1;
    strncpy(shortname,p,q-p);
    *(shortname+(q-p)) = '\0';
    if (!skipspace(&q)) 
       return -1;
    p = q;
  } else return -1;
  skipnspace(&q);
  if (q-p >= sizeof(longname)) return -1;
  strncpy(longname,p,q-p);
  *(longname+(q-p)) = '\0';
  if (*dshortcut_count == *dshortcut_size)
  { 
    if (!dsh)
    {  fprintf(stderr,"ftpd: out of memory (dshortcut)\n");
       return -1;
    }
       
    *dshortcut_size += 4;
  }
  dsh = *dshortcut2;
  strcpy(dsh->shortname,shortname);
  strcpy(dsh->longname,longname);
  (*dshortcut_count)++;

  *dshortcut2=dsh;
  return 0;
}


int get_dupe(char* p)
{ char* cd;
  int   newsize;
  char* q = p;

  skipnspace(&q);
  *q = '\0';
  while (dupechk.len+strlen(p) >= dupechk.size)
  { newsize = dupechk.size + 1024;
    cd = alloc(dupechk.msg,newsize);
    if (!cd)
    { fprintf(stderr,"ftpd: out of memory (dupecheck)\n");
      return -1;
    }
    dupechk.msg = cd;
    dupechk.size = newsize;
  }
  strcpy(dupechk.msg+dupechk.len,p);
  dupechk.len += strlen(p);
  *(dupechk.msg+dupechk.len) = '\n';
  dupechk.len++;
  *(dupechk.msg+dupechk.len) = '\0';
  return 0;
}

int get_shortcut(char* p, struct msg_s* msg)
{ char* buffer;
  int   newsize;

  while (msg->len+strlen(p)+2 >= msg->size)
  { newsize = msg->size + 1024;
    buffer = alloc(msg->msg,newsize);
    if (!buffer)
    { fprintf(stderr,"ftpd: out of memory (shortcut)\n");
      return -1;
    }
    msg->msg = buffer;
    msg->size = newsize;
  }
  strcpy(msg->msg+msg->len,p);
  msg->len += strlen(p) + 1;
  strcpy(msg->msg+msg->len-1," ");
  return 0;
}

long get_rights(char *p, long* value, char *line)
{ long v;
  int n;
  char *q;
  
  *value=0;
  
  while (skipspace(&p))
  { v = 0;
    q = p;
    if (skipnspace(&q)) *(q++) = '\0';
    if (n = (*p == '!')) p++;
    if (!strcasecmp(p,"all")) v = R_ALL; else
    if (!strcasecmp(p,"enable")) v |= R_ENABLE; else
    if (!strcasecmp(p,"disable")) v |= R_DISABLE; else
    if (!strcasecmp(p,"group/all")) v |= R_GROUP_ALL; else
    if (!strcasecmp(p,"group/add")) v |= R_GROUP_ADD; else
    if (!strcasecmp(p,"group/change")) v |= R_GROUP_CHANGE; else
    if (!strcasecmp(p,"group/remove")) v |= R_GROUP_REMOVE; else
    if (!strcasecmp(p,"group/list")) v |= R_GROUP_LIST; else
    if (!strcasecmp(p,"groups")) v |= R_GROUPS; else
    if (!strcasecmp(p,"kick")) v |= R_KICK; else
    if (!strcasecmp(p,"max")) v |= R_MAX; else
    if (!strcasecmp(p,"user/all")) v |= R_USER_ALL; else
    if (!strcasecmp(p,"user/add")) v |= R_USER_ADD; else
    if (!strcasecmp(p,"user/passwd")) v |= R_USER_PASSWD; else
    if (!strcasecmp(p,"user/change/all")) v |= R_USER_CHANGE; else
    if (!strcasecmp(p,"user/change/ip")) v |= R_USER_CHANGE_IP; else
    if (!strcasecmp(p,"user/change/stats")) v |= R_USER_CHANGE_STATS; else
    if (!strcasecmp(p,"user/change/ratio")) v |= R_USER_CHANGE_RATIO; else
    if (!strcasecmp(p,"user/change/other")) v |= R_USER_CHANGE_OTHER; else
    if (!strcasecmp(p,"user/list")) v |= R_USER_LIST; else
    if (!strcasecmp(p,"user/remove")) v |= R_USER_REMOVE; else
    if (!strcasecmp(p,"user/reset")) v |= R_USER_RESET; else
    { sprintf(line,"Unknown right: %s",p);
      return -1;
    }
    if (n) *value &= ~v; else *value |= v;
    p = q;
  }
}

int load_config_file(const char* filename, FILE** file, char* line, int* lineno,
  int reconfig)
{ char  n;
  char* p;
  char* q;
  long  v;
  struct dshortcut_s *dsh,*dsh2;

  if ((*file = fopen(filename,"rt")) == NULL)
  { if (strcmp(filename,"ftpd.conf"))
    { perror("ftpd: cannot open configuration file");
      return -2;
    }
    return 0;
  }
  while (readline(line,256,*file))
  { (*lineno)++;
    p = line;
    skipspace(&p);
    if (*p && (*p!='#'))
    { q = p;
      skipnspace(&q);
      if (*q) *(q++) = '\0';
      skipspace(&q);
      if (!strcasecmp(p,"serverdir"))
      { if (get_string(q,&server_dir) == -1) return -1;
	if (*(server_dir+strlen(server_dir)-1) != '/')
	{ fprintf(stderr,"ftpd: ServerDir must end with /\n");
	  return -1;
	}
      } else if (!strcasecmp(p,"alternativeserverprogram"))
      { if (get_string(q,&alternative_srv) == -1) return -1;
      } else if (!strcasecmp(p,"basedir"))
      { if (get_dirstring(q,&base_dir) == -1) return -1;
      } else if (!strcasecmp(p,"cdpath"))
      { if (get_cdpath(q) == -1) return -1;
      } else if (!strcasecmp(p,"cdupmsg"))
      { if (get_text(*file,line,256,q,lineno,&cdup_msg.len,
	    &cdup_msg.size,&cdup_msg.msg,"cdupmsg") == -1) return -1;
      } else if (!strcasecmp(p,"cdupmsgfile"))
      { if (get_dirstring(q,&cdup_msg.filename) == -1) return -1;
	cdup_msg.filedate = 0;
      } else if (!strcasecmp(p,"chdirmsg"))
      { if (get_text(*file,line,256,q,lineno,&chdir_msg.len,
	    &chdir_msg.size,&chdir_msg.msg,"chdirmsg") == -1) return -1;
      } else if (!strcasecmp(p,"chdirmsgfile"))
      { if (get_dirstring(q,&chdir_msg.filename) == -1) return -1;
	chdir_msg.filedate = 0;
      } else if (!strcasecmp(p,"delemsg"))
      { if (get_text(*file,line,256,q,lineno,&dele_msg.len,
            &dele_msg.size,&dele_msg.msg,"delemsg") == -1) return -1;
      } else if (!strcasecmp(p,"mkdirmsg"))
      { if (get_text(*file,line,256,q,lineno,&mkdir_msg.len,
	    &mkdir_msg.size,&mkdir_msg.msg,"mkdirmsg") == -1) return -1;
      } else if (!strcasecmp(p,"mkdirmsgfile"))
      { if (get_dirstring(q,&mkdir_msg.filename) == -1) return -1;
	mkdir_msg.filedate = 0;
      } else if (!strcasecmp(p,"check"))
      { if (get_check(q,&check,&check_count,&check_size) == -1) return -1;
      } else if (!strcasecmp(p,"checkdupe"))
      { if (get_check(q,&dcheck,&dcheck_count,&dcheck_size) == -1) return -1;
      } else if (!strcasecmp(p,"destroylogin"))
      { if (get_string(q,&destroy_login) == -1) return -1;
      } else if (!strcasecmp(p,"destroypassword"))
      { if (get_string(q,&destroy_password) == -1) return -1;
      } else if (!strcasecmp(p,"downloadmsg"))
      { if (get_text(*file,line,256,q,lineno,&download_msg.len,
	    &download_msg.size,&download_msg.msg,"downloadmsg") == -1)
	    return -1;
      } else if (!strcasecmp(p,"downloadmsgfile"))
      { if (get_dirstring(q,&download_msg.filename) == -1) return -1;
	download_msg.filedate = 0;
      } else if (!strcasecmp(p,"dirshortcut"))
      { 
	if(!dsh)
	   dsh=(struct dshortcut_s*)malloc(sizeof(struct dshortcut_s));
	if(!dshortcut)
	{
	   dshortcut = (struct dshortcut_s*)malloc(sizeof(struct dshortcut_s));
	   dshortcut->prev=NULL;
	   dshortcut->next=NULL;
	   dsh=dshortcut;
	}
	else
	{
	   dshortcut->next = (struct dshortcut_s*)malloc(sizeof(struct dshortcut_s));
	   dsh=dshortcut->next;
	   dsh2=dshortcut;
	   dshortcut=dshortcut->next;
	   dsh2->next = dshortcut;
	   dshortcut->prev= dsh2;
	   dshortcut->next = dsh;
	   dsh = dshortcut;
	}
	if (get_dshortcut(q,&dsh,&dshortcut_count,&dshortcut_size) == -1) return -1;
	dshortcut=dsh;
      } else if (!strcasecmp(p,"dupecheck"))
      { if (get_dupe(q) == -1) return -1;
      } else if (!strcasecmp(p,"dupepath"))
      { if (get_string(q,&dupe_path) == -1) return -1;
      } else if (!strcasecmp(p,"errlogfile"))
      { if (get_dirstring(q,&errlog_file) == -1) return -1;
      } else if (!strcasecmp(p,"filenameconversion"))
      { if (get_integer(q,&fconv_value,0,0xFF) == -1) return -1;
      } else if (!strcasecmp(p,"ffpath"))
      { if (get_string(q,&ff_path) == -1) return -1;
      } else if (!strcasecmp(p,"goodbyemsg"))
      { if (get_text(*file,line,256,q,lineno,&goodbye_msg.len,
	    &goodbye_msg.size,&goodbye_msg.msg,"goodbyemsg") == -1) return -1;
      } else if (!strcasecmp(p,"goodbyemsgfile"))
      { if (get_dirstring(q,&goodbye_msg.filename) == -1) return -1;
	goodbye_msg.filedate = 0;
      } else if (!strcasecmp(p,"group"))
      { if (!reconfig) if (get_group(q) == -1) return -1;
      } else if (!strcasecmp(p,"groupfile"))
      { if (get_dirstring(q,&group_file) == -1) return -1;
      } else if (!strcasecmp(p,"groupop"))
      { p = q;
	if (!skipspace(&p)) return -1;
	if (get_rights(p,&groupop_rights,line)) return -1;
      } else if (!strcasecmp(p,"groupoplimit"))
      { if (get_integer(q,&groupop_limit,0,100) == -1) return -1;
      } else if (!strcasecmp(p,"ident"))
      { if (get_onoff(q,&ident_mode) == -1) return -1;
      } else if (!strcasecmp(p,"listmsg"))
      { if (get_text(*file,line,256,q,lineno,&list_msg.len,
	    &list_msg.size,&list_msg.msg,"listmsg") == -1) return -1;
      } else if (!strcasecmp(p,"listmsgfile"))
      { if (get_dirstring(q,&list_msg.filename) == -1) return -1;
	list_msg.filedate = 0;
      } else if (!strcasecmp(p,"log"))
      { p = q;
	if (!skipspace(&p)) return -1;
	while (skipspace(&p))
	{ v = 0;
	  q = p;
	  if (skipnspace(&q)) *(q++) = '\0';
	  if (n = (*p == '!')) p++;
	  if (!strcasecmp(p,"all")) v = LG_ALL; else
	  if (!strcasecmp(p,"cd")) v |= LG_CD; else
	  if (!strcasecmp(p,"chmod")) v |= LG_CHMOD; else
	  if (!strcasecmp(p,"del")) v |= LG_DEL; else
	  if (!strcasecmp(p,"get")) v |= LG_GET; else
	  if (!strcasecmp(p,"login")) v |= LG_LOGIN; else
	  if (!strcasecmp(p,"logout")) v |= LG_LOGOUT; else
	  if (!strcasecmp(p,"ls")) v |= LG_LS; else
	  if (!strcasecmp(p,"md")) v |= LG_MD; else
	  if (!strcasecmp(p,"put")) v |= LG_PUT; else
	  if (!strcasecmp(p,"rd")) v |= LG_RD; else
	  if (!strcasecmp(p,"ren")) v |= LG_REN; else return -1;
	  if (n) logc &= ~v; else logc |= v;
	  p = q;
	}
      } else if (!strcasecmp(p,"logfile"))
      { if (get_dirstring(q,&log_file) == -1) return -1;
      } else if (!strcasecmp(p,"loginkick"))
      { login_kick = 1;
	if (skipspace(&p))
	  if (get_integer(q,&login_kick_signo,1,255) == -1) return -1;
      } else if (!strcasecmp(p,"loginretries"))
      { if (get_integer(q,&login_retries,1,255) == -1) return -1;
      } else if (!strcasecmp(p,"logprogram"))
      { if (get_line(q,&log_program) == -1) return -1;
      } else if (!strcasecmp(p,"maxanon"))
      { if (get_integer(q,&max_anon,0,1024) == -1) return -1;
      } else if (!strcasecmp(p,"maxanondomain"))
      { if (get_integer(q,&max_anon_domain,0,1024) == -1) return -1;
      } else if (!strcasecmp(p,"maxanonip"))
      { if (get_integer(q,&max_anon_ip,0,1024) == -1) return -1;
      } else if (!strcasecmp(p,"maxuser"))
      { if (get_integer(q,&max_users,0,1024) == -1) return -1;
      } else if (!strcasecmp(p,"permissionfile"))
      { if (get_string(q,&permission_file) == -1) return -1;
      } else if (!strcasecmp(p,"pidfile"))
      { if (get_dirstring(q,&pid_file) == -1) return -1;
      } else if (!strcasecmp(p,"port"))
      { if (get_integer(q,&port,0,65535) == -1) return -1;
      } else if (!strcasecmp(p,"private"))
      { if (get_onoff(q,&private_mode) == -1) return -1;
      } else if (!strcasecmp(p,"programname"))
      { if (get_string(q,&program_name) == -1) return -1;
      } else if (!strcasecmp(p,"readmefile"))
      { if (get_string(q,&readme_file) == -1) return -1;
      } else if (!strcasecmp(p,"scriptpath"))
      { if (get_dirstring(q,&script_path) == -1) return -1;
      } else if (!strcasecmp(p,"serverprogram"))
      { if (get_prog(q,&server_program) == -1 ) return -1;
      } else if (!strcasecmp(p,"shortcut"))
      { if (get_shortcut(q,&shortcut) == -1) return -1;
      } else if (!strcasecmp(p,"startupmsg"))
      { if (get_text(*file,line,256,q,lineno,&startup_msg.len,
	    &startup_msg.size,&startup_msg.msg,"startupmsg") == -1) return -1;
      } else if (!strcasecmp(p,"startupmsgfile"))
      { if (get_dirstring(q,&startup_msg.filename) == -1) return -1;
	startup_msg.filedate = 0;
      } else if (!strcasecmp(p,"statfile"))
      { if (get_dirstring(q,&stat_file) == -1) return -1;
      } else if (!strcasecmp(p,"timeoutanon"))
      { if (get_integer(q,&timeout_anon,30,3600) == -1) return -1;
      } else if (!strcasecmp(p,"timeoutuser"))
      { if (get_integer(q,&timeout_user,30,3600) == -1) return -1;
      } else if (!strcasecmp(p,"TooManyAnonMsg"))
      { if (get_text(*file,line,256,q,lineno,&toomanyanon_msg.len,
	    &toomanyanon_msg.size,&toomanyanon_msg.msg,"TooManyAnonMsg") == -1) return -1;
      } else if (!strcasecmp(p,"TooManyUserMsg"))
      { if (get_text(*file,line,256,q,lineno,&toomanyuser_msg.len,
	    &toomanyuser_msg.size,&toomanyuser_msg.msg,"TooManyUserMsg") == -1) return -1;
      } else if (!strcasecmp(p,"TooManyAnonMsgFile"))
      { if (get_dirstring(q,&toomanyanon_msg.filename) == -1) return -1;
	toomanyanon_msg.filedate = 0;
      } else if (!strcasecmp(p,"TooManyUserMsgFile"))
      { if (get_dirstring(q,&toomanyuser_msg.filename) == -1) return -1;
	toomanyuser_msg.filedate = 0;
      } else if (!strcasecmp(p,"touch"))
      { if (get_onoff(q,&touch) == -1) return -1;
      } else if (!strcasecmp(p,"umask"))
      { if (get_integer(q,&umask_value,0,0777) == -1) return -1;
      } else if (!strcasecmp(p,"umaskanon"))
      { if (get_long(q,&umask_anon,0,0x7F7F7F) == -1) return -1;
      } else if (!strcasecmp(p,"umaskroot"))
      { if (get_long(q,&umask_root,0,0x7F7F7F) == -1) return -1;
      } else if (!strcasecmp(p,"umaskuser"))
      { if (get_long(q,&umask_user,0,0x7F7F7F) == -1) return -1;
      } else if (!strcasecmp(p,"uploadmsg"))
      { if (get_text(*file,line,256,q,lineno,&upload_msg.len,
	    &upload_msg.size,&upload_msg.msg,"uploadmsg") == -1) return -1;
      } else if (!strcasecmp(p,"uploadmsgfile"))
      { if (get_dirstring(q,&upload_msg.filename) == -1) return -1;
	upload_msg.filedate = 0;
      } else if (!strcasecmp(p,"user"))
      { if (!reconfig) if (get_user(q) == -1) return -1;
      } else if (!strcasecmp(p,"useredit"))
      { p = q;
	if (!skipspace(&p)) return -1;
	if (get_rights(p,&useredit_rights,line)) return -1;
      } else if (!strcasecmp(p,"usereditlimit"))
      { if (get_integer(q,&useredit_limit,0,100) == -1) return -1;
      } else if (!strcasecmp(p,"userfile"))
      { if (get_dirstring(q,&user_file) == -1) return -1;
#ifdef DES_ENCRYPTION
      } else if (!strcasecmp(p,"userkey"))
      { string_to_key(q,&user_key);
	user_encrypted = 1;
#endif
      } else if (!strcasecmp(p,"waitport"))
      { if (get_onoff(q,&wait_port) == -1) return -1;
      } else if (!strcasecmp(p,"welcomemsg"))
      { if (get_text(*file,line,256,q,lineno,&welcome_msg.len,
	    &welcome_msg.size,&welcome_msg.msg,"welcomemsg") == -1) return -1;
      } else if (!strcasecmp(p,"welcomemsganon"))
      { if (get_text(*file,line,256,q,lineno,&welcome_msg_anon.len,
	    &welcome_msg_anon.size,&welcome_msg_anon.msg,"welcomemsg") == -1)
	  return -1;
      } else if (!strcasecmp(p,"welcomemsgfile"))
      { if (get_dirstring(q,&welcome_msg.filename) == -1) return -1;
	welcome_msg.filedate = 0;
      } else if (!strcasecmp(p,"welcomemsganonfile"))
      { if (get_dirstring(q,&welcome_msg_anon.filename) == -1) return -1;
	welcome_msg_anon.filedate = 0;
      } else if (!strcasecmp(p,"whoonline_msg_top"))
      { if (get_text(*file,line,256,q,lineno,&whoonline_msg_top.len,
	    &whoonline_msg_top.size,&whoonline_msg_top.msg,"whoonline_msg_top") == -1) return -1;
      } else if (!strcasecmp(p,"whoonline_msg_top_file"))
      { if (get_dirstring(q,&whoonline_msg_top.filename) == -1) return -1;
	whoonline_msg_top.filedate = 0;
      } else if (!strcasecmp(p,"whoonline_msg_body"))
      { if (get_text(*file,line,256,q,lineno,&whoonline_msg_body.len,
	    &whoonline_msg_body.size,&whoonline_msg_body.msg,"whoonline_msg_body") == -1) return -1;
      } else if (!strcasecmp(p,"whoonline_msg_body_file"))
      { if (get_dirstring(q,&whoonline_msg_body.filename) == -1) return -1;
	whoonline_msg_body.filedate = 0;
      } else if (!strcasecmp(p,"whoonline_msg_tail"))
      { if (get_text(*file,line,256,q,lineno,&whoonline_msg_tail.len,
	    &whoonline_msg_tail.size,&whoonline_msg_tail.msg,"whoonline_msg_tail") == -1) return -1;
      } else if (!strcasecmp(p,"whoonline_msg_tail_file"))
      { if (get_dirstring(q,&whoonline_msg_tail.filename) == -1) return -1;
	whoonline_msg_tail.filedate = 0;
      } else if (!strcasecmp(p,"other_ip")) 
      { if (get_string(q,&other_ip) == -1) return -1;
      } else if (!strcasecmp(p,"resolver"))
      { if (get_onoff(q,&resolver) == -1) return -1; 
      } else if (!strcasecmp(p,"chmod_allowed"))
      { if (get_onoff(q,&chmodallowed) == -1) return -1;
      } else if (!strcasecmp(p,"bouncer"))
      { if (get_string(q,&bouncer) == -1) return -1;
      } else if (!strcasecmp(p,"pre_upload"))
      { if (get_string(q,&pre_upload) == -1) return -1;
      } else if (!strcasecmp(p,"pre_download"))
      { if (get_string(q,&pre_download) == -1) return -1;
      } else if (!strcasecmp(p,"checktimeout"))
      { if (get_integer(q,&checktimeout,0,100) == -1) return -1;
      } else
      return -1;
    }
  }
  fclose(*file);
  return 0;
}

int load_config(const char* filename, int reconfig)
{ FILE* file;
  char  line[256];
  int   lineno = 0;
  char* param[16];
  int   pipefd[2];
  int   r;

  if (!reconfig) user_count = group_count = 0;
  cdpath_len = 0;
  check_count = 0;
  dupechk.len = 0;
  fconv_value = 0;
  shortcut.len = 0;
#ifdef DES_ENCRYPTION
  user_encrypted = 0;
#endif
  if (base_dir == default_base_dir) base_dir = NULL;
  if (errlog_file == default_errlog_file) errlog_file = NULL;
  if (log_file == default_log_file) log_file = NULL;
  if (permission_file == default_permission_file) permission_file = NULL;
  if (server_program == default_server_program) server_program = NULL;
  if (log_pid)
  { free(log_program);
    log_program = NULL;
    fclose(log_program_f);
    kill(log_pid,SIGTERM);
    log_pid = 0;
  }
  r = load_config_file(filename,&file,line,&lineno,reconfig);
  if (r == -1)
  { fclose(file);
    config_error(lineno,line);
    return -1;
  } else if (r != 0) r = -1;
  umask(umask_value);
  if (!base_dir) base_dir = default_base_dir;
  if (!errlog_file) errlog_file = default_errlog_file;
  if (!permission_file) permission_file = default_permission_file;
  if (!server_program) server_program = default_server_program;
  if (!reconfig)
  { if (user_file)
    { if (load_user_file(user_file,1) == -1) r = -1;
    } else
    { if (load_user_file(".users",1) == -1) r = -1;
    }
    if (group_file)
    { if (load_group_file(group_file,1) == -1) r = -1;
    } else
    { if (load_group_file(".groups",1) == -1) r = -1;
    }
  }
  if (chdir(base_dir) == -1)
  { fprintf(stderr,"ftpd: basedir=<%s>\n",base_dir);
    perror("ftpd: cannot change to base directory");
    r = -1;
  }
  if (logf)
  { fclose(logf);
    logf = NULL;
  }
  if (logc)
  { if (!log_file) log_file = default_log_file;
    if ((logf = fopen(log_file,"a+t")) == NULL)
    { perror("ftpd: cannot open logfile");
      r = -1;
    }
    if (log_program)
    { pipe(pipefd);
      log_pid = ix_vfork();
      switch (log_pid)
      { case  0 :
	  close(pipefd[1]);
	  dup2(pipefd[0],0);
	  r = 0;
	  parse_command(log_program,&param,15,&r);
	  *(param+r) = NULL;
	  r = execvp(*param,param);
	  exit(r);
	case -1 :
	  fprintf(stderr,"ftpd: cannot fork\n");
	  free(log_program);
	  log_program = NULL;
	  log_pid = 0;
	  r = -1;
	  break;
	default :
	  close(pipefd[0]);
	  log_program_f = fdopen(pipefd[1],"wt");
	  if (!log_program_f)
	  { perror("ftpd: fdopen");
	    free(log_program);
	    log_program = NULL;
	    log_pid = 0;
	    r = -1;
	  }
      }
    }
  }
  if (errlogf) errlogf = freopen(errlog_file,"a+t",errlogf);
    else errlogf = freopen(errlog_file,"a+t",stderr);
  if (!errlogf)
  { perror("ftpd: freopen(errlog_file)");
    r = -1;
  }
  return r;
}

void print_configuration(void)
{ printf("Base directory : %s\n",base_dir ? base_dir : "NULL");
  printf("Readme file    : %s\n",readme_file ? readme_file : "(none)");
  printf("Permission file: %s\n",permission_file ? permission_file : "(none)");
  printf("Errlog file    : %s\n",errlog_file ? errlog_file : "(none)");
  printf("Log file       : %s\n",log_file ? log_file : "(none)");
  printf("Log program    : %s\n",log_program ? log_program : "(none)");
  printf("Server program : %s\n",server_program ? server_program : "NULL");
}

/* EOF */
