/*****************************************************************************\
* Copyright (c) 2002 Pelle Johansson.                                         *
* All rights reserved.                                                        *
*                                                                             *
* This file is part of the moftpd package. Use and distribution of            *
* this software is governed by the terms in the file LICENCE, which           *
* should have come with this package.                                         *
\*****************************************************************************/

/* $moftpd: confparse.c 1264 2005-04-06 13:32:27Z morth $ */

#include "system.h"

#include "confparse.h"
#include "user.h"

#include "config.tab.h"
#include "utf8fs/memory.h"

static int confFd;
static char *confBuf, *confP;
static int confLine;

extern int confExpectingToken;

typedef struct identifier
{
  const char *name;
  int rule;
} identifier_t;

static identifier_t identifiers[] =
{
  {"abort"           , I_ABORT           },
  {"AcceptTLS"       , I_ACCEPTTLS       },
  {"AccounterSock"   , I_ACCOUNTERSOCK   },
  {"Admin"           , I_ADMIN           },
  {"Alias"           , I_ALIAS           },
  {"all"             , I_ALL             },
  {"Allow"           , I_ALLOW           },
  {"AllowForeign"    , I_ALLOWFOREIGN    },
  {"AllowLogin"      , I_ALLOWLOGIN      },
  {"AllowLowPorts"   , I_ALLOWLOWPORTS   },
  {"AllowOutOfRange" , I_ALLOWOUTOFRANGE },
  {"AllowSecLogin"   , I_ALLOWSECLOGIN   },
  {"AllowUnbound"    , I_ALLOWUNBOUND    },
  {"AnonPassMsg"     , I_ANONPASSMSG     },
  {"Anonymous"       , I_ANONYMOUS       },
  {"append"          , I_APPEND          },
  {"Bind"            , I_BIND            },
  {"Chroot"          , I_CHROOT          },
  {"HandleFailedFork", I_HANDLEFAILEDFORK},
  {"createDir"       , I_CREATEDIR       },
  {"createFile"      , I_CREATEFILE      },
  {"delete"          , I_DELETE          },
  {"Deny"            , I_DENY            },
  {"Directory"       , I_DIRECTORY       },
  {"DirectoryMsgFile", I_DIRECTORYMSGFILE},
  {"disconnect"      , I_DISCONNECT      },
  {"encrypted"       , I_ENCRYPTED       },
  {"ExternAccess"    , I_EXTERNACCESS    },
  {"ExternLogin"     , I_EXTERNLOGIN     },
  {"FakeChroot"      , I_FAKECHROOT      },
  {"FakeDirMode"     , I_FAKEDIRMODE     },
  {"FakeFileMode"    , I_FAKEFILEMODE    },
  {"FakeGroup"       , I_FAKEGROUP       },
  {"FakeUser"        , I_FAKEUSER        },
  {"ForkClients"     , I_FORKCLIENTS     },
  {"Gid"             , I_GID             },
  {"HardLink"        , I_HARDLINK        },
  {"Hidden"          , I_HIDDEN          },
  {"Home"            , I_HOME            },
  {"LimitFileSize"   , I_LIMITFILESIZE   },
  {"list"            , I_LIST            },
  {"listing"         , I_LISTING         },
  {"LocaleDir"       , I_LOCALEDIR       },
  {"LoginFailedMsg"  , I_LOGINFAILEDMSG  },
  {"Mask"            , I_MASK            },
  {"MaxConnects"     , I_MAXCONNECTS     },
  {"MaxIdle"         , I_MAXIDLE         },
  {"MaxLoginAttempts", I_MAXLOGINATTEMPTS},
  {"MaxLogins"       , I_MAXLOGINS       },
  {"MaxMmapSize"     , I_MAXMMAPSIZE     },
  {"MaxPasvPort"     , I_MAXPASVPORT     },
  {"MinPasvPort"     , I_MINPASVPORT     },
  {"msg"             , I_MSG             },
  {"overwrite"       , I_OVERWRITE       },
  {"PAMService"      , I_PAMSERVICE      },
  {"PassIfInvalid"   , I_PASSIFINVALID   },
  {"PassRequestMsg"  , I_PASSREQUESTMSG  },
  {"Password"        , I_PASSWORD        },
  {"PasswordNeeded"  , I_PASSWORDNEEDED  },
  {"PidFile"         , I_PIDFILE         },
  {"Port"            , I_PORT            },
  {"Range"           , I_RANGE           },
  {"readFile"        , I_READFILE        },
  {"reading"         , I_READING         },
  {"reload"          , I_RELOAD          },
  {"rename"          , I_RENAME          },
  {"Require"         , I_REQUIRE         },
  {"Reset"           , I_RESET           },
  {"search"          , I_SEARCH          },
  {"Server"          , I_SERVER          },
  {"signed"          , I_SIGNED          },
  {"SleepOnFail"     , I_SLEEPONFAIL     },
  {"SQLConnect"      , I_SQLCONNECT      },
  {"SQLConnectQuery" , I_SQLCONNECTQUERY },
  {"SQLDirQuery"     , I_SQLDIRQUERY     },
  {"SQLPassword"     , I_SQLPASSWORD     },
  {"SQLTLSConnect"   , I_SQLTLSCONNECT   },
  {"SQLUserQuery"    , I_SQLUSERQUERY    },
  {"storing"         , I_STORING         },
  {"TLSAutoLogin"    , I_TLSAUTOLOGIN    },
  {"TLSNoNewUser"    , I_TLSNONEWUSER    },
  {"TLSVerifyClient" , I_TLSVERIFYCLIENT },
  {"TrustedCertsDir" , I_TRUSTEDCERTSDIR },
  {"Uid"             , I_UID             },
  {"unlimit"	     , I_UNLIMIT         },
  {"UnprivGid"       , I_UNPRIVGID       },
  {"UnprivUid"       , I_UNPRIVUID       },
  {"User"            , I_USER            },
  {"UserAlias"       , I_USERALIAS       },
  {"UserInvalidMsg"  , I_USERINVALIDMSG  },
  {"WelcomeMsg"      , I_WELCOMEMSG      },
  {"writing"         , I_WRITING         },
  {"XferBuffSize"    , I_XFERBUFFSIZE    },
};
const int numIdentifiers = sizeof (identifiers) / sizeof (identifier_t);

int find_identifier (const char *name)
{
  int size = numIdentifiers;
  const identifier_t *ids = identifiers;
  
  while (size)
  {
    int odd = size & 1;
    int res;
    
    size = size / 2;
    res = strcasecmp (name, ids[size].name);
    if (!res)
      return ids[size].rule;
    if (res > 0)
    {
      ids += size + 1;
      if (!odd)
	size--;
    }
  }
  return 0;
}

int read_config(const char *path)
{
  confFd = open(path, O_RDONLY);
  if(confFd < 0)
  {
    syslog (LOG_ERR, "Failed to open config file: %m.");
    return -1;
  }
  confLine = 1;
  
  confBuf = NULL;
  return yyparse();
}

static int fetch_ch(void)
{
  int l;
  
  if(confFd < 0)
    return 0;
  
  if (!confBuf)
  {
    confBuf = talloc (4097);
    if (!confBuf)
      return -1;
  }
  
  while(!confP || !*confP)
  {
    l = read(confFd, confBuf, 4096);
    if(l <= 0)
    {
      close(confFd);
      confFd = -1;
      return l;
    }
    
    confBuf[l] = 0;
    confP = confBuf;
  }
  
  if(*confP == '\n')
    confLine++;
  
  return *confP++;
}

static void unfetch_ch(void)
{
  // Should always be true unless you never called fetch_ch ()
  // or call unfetch_ch() multiple times.
  if(confP && confP > confBuf)
  {
    confP--;
    if(*confP == '\n')
      confLine--;
  }
}

int parse_bool (const char *val)
{
  if(!strcasecmp(val, "true") || !strcasecmp(val, "on") ||
	!strcasecmp(val, "yes"))
    return 1;
  
  if(!strcasecmp(val, "false") || !strcasecmp(val, "off") ||
	!strcasecmp(val, "no"))
    return 0;
  
  return -1;
}

int parse_uid (const char *val)
{
  char *sp;
  int i = strtoll (val, &sp, 0);
  
  if (sp && *sp)
  {
    struct passwd *pwd;
    
    pwd = getpwnam (val);
    if (!pwd)
      return INT_MIN;
    i = pwd->pw_uid;
  }
  return i;
}

int parse_gid (const char *val)
{
  char *sp;
  int i = strtoll (val, &sp, 0);
  
  if (sp && *sp)
  {
    struct group *gr;
    
    gr = getgrnam (val);
    if (!gr)
      return INT_MIN;
    i = gr->gr_gid;
  }
  return i;
}

int parse_access_list (const char *val)
{
  const char *vp, *nvp, *evp;
  int res = 0, l;
  char buf[40];
  
  for (vp = val; vp; vp = nvp)
  {
    while (isspace (*vp & 0xFF))
      vp++;
    nvp = strchr (vp, ',');
    if (nvp)
      evp = nvp++ - 1;
    else
      evp = vp + strlen (vp) - 1;
    while (isspace (*evp & 0xFF) && evp > vp)
      evp--;
    if (evp > vp)
    {
      l = evp - vp;
      if (l >= 40)
	l = 39;
      strncpy (buf, vp, l);
      buf[l] = 0;
      switch (find_identifier (buf))
      {
      case I_SEARCH:
	res |= acSearch;
	break;
      case I_READFILE:
	res |= acReadFile;
	break;
      case I_LISTING:
	res |= acListing;
	break;
      case I_CREATEFILE:
	res |= acCreateFile;
	break;
      case I_CREATEDIR:
	res |= acCreateDir;
	break;
      case I_APPEND:
	res |= acAppend;
	break;
      case I_OVERWRITE:
	res |= acOverwrite;
	break;
      case I_DELETE:
	res |= acDelete;
	break;
      case I_RENAME:
	res |= acRename;
	break;
      case I_ENCRYPTED:
	res |= acEncrypted;
	break;
      case I_SIGNED:
	res |= acSigned;
	break;
      case I_READING:
	res |= acSearch | acReadFile | acListing;
	break;
      case I_WRITING:
	res |= acCreateFile | acCreateDir | acAppend | acOverwrite | acDelete;
	break;
      case I_STORING:
	res |= acCreateFile | acCreateDir | acAppend;
	break;
      case I_ALL:
	res = -1;
	break;
      default:
	syslog (LOG_DEBUG, "Unknown access option: %s", buf);
	break;
      }
    }
  }
  return res;
}

int parse_admin_list (const char *val)
{
  const char *vp, *nvp, *evp;
  int res = 0, l;
  char buf[40];
  
  for (vp = val; vp; vp = nvp)
  {
    while (isspace (*vp & 0xFF))
      vp++;
    nvp = strchr (vp, ',');
    if (nvp)
      evp = nvp++ - 1;
    else
      evp = vp + strlen (vp) - 1;
    while (isspace (*evp & 0xFF) && evp > vp)
      evp--;
    if (evp > vp)
    {
      l = evp - vp;
      if (l >= 40)
	l = 39;
      strncpy (buf, vp, l);
      buf[l] = 0;
      switch (find_identifier (buf))
      {
      case I_LIST:
	res |= admList;
	break;
      case I_MSG:
	res |= admMsg;
	break;
      case I_ABORT:
	res |= admAbort;
	break;
      case I_DISCONNECT:
	res |= admDisconnect;
	break;
      case I_RELOAD:
	res |= admReload;
	break;
      case I_ALL:
	res = -1;
	break;
      default:
	syslog (LOG_DEBUG, "Unknown admin option: %s", buf);
	break;
      }
    }
  }
  return res;
}

int yylex(void)
{
  int ch = fetch_ch(), sch;
  char strbuf[1000], *sp;
  int expTok;
  
  expTok = confExpectingToken;
  confExpectingToken = 0;
  
  if (ch < 0)
  {
    yyerror ("Error reading");
    return -1;
  }
  if (!ch)
    return 0;
  
  if(isspace(ch) || ch == '#')
  {
    do
    {
      while(isspace(ch))
	ch = fetch_ch();
      if(ch == '#')
      {
	while((ch = fetch_ch()) != '\n')
	{
	  if (ch < 0)
	  {
	    yyerror ("Error reading");
	    return -1;
	  }
	  if (!ch)
	    return 0;
	}
      }
    } while(isspace(ch));
    if(ch <= 0)
      return ch;
  }
  
  if(ch == '>' || ch == ';' || ch == ',')
    return ch;
  if (!expTok && ch == '<')
  {
    ch = fetch_ch ();
    if (ch == '/')
      return I_ETAG;
    unfetch_ch ();
    return '<';
  }
  
  if(ch == '"' || ch == '\'')
  {
    sch = ch;
    sp = strbuf;
    while(sp - strbuf < 1000)
    {
      do
      {
	ch = fetch_ch();
	if(ch <= 0)
	  return -1;
	*sp++ = ch;
      } while(ch != sch && sp - strbuf < 1000);
      
      ch = fetch_ch();
      if(ch != sch)
      {
	while(isspace(ch))
	  ch = fetch_ch();
	if(ch == sch)
	  sp--;
	else
	{
	  unfetch_ch();
	  break;
	}
      }
    }
    
    *(sp - 1) = 0;
    strcpy(yylval.str, strbuf);
    
    return D_STRING;
  }
  
  sp = strbuf;
  while(!isspace(ch) && ch != ',' && ch != '>' && ch != ';' && sp - strbuf <
	1000)
  {
    *sp++ = ch;
    ch = fetch_ch();
    if(ch < 0)
    {
      yyerror ("Error reading");
      return -1;
    }
    if(!ch)
      break;
  }
  
  *sp = 0;
  if(ch)
    unfetch_ch();
  
  switch (expTok)
  {
  case D_STRING:
    strcpy(yylval.str, strbuf);
    return D_STRING;
  case D_UID:
    yylval.num = parse_uid (strbuf);
    if (yylval.num == INT_MIN)
      return -1;
    return D_UID;
  case D_GID:
    yylval.num = parse_gid (strbuf);
    if (yylval.num == INT_MIN)
      return -1;
    return D_GID;
  }
  
  if(!strlen(strbuf))
  {
    yyerror ("Unknown character");
    return -1;
  }
  
  expTok = find_identifier (strbuf);
  if (expTok)
    return expTok;
  
  yylval.num = parse_bool (strbuf);
  if (yylval.num != -1)
    return D_BOOL;
  
  yylval.num = strtoll(strbuf, &sp, 0);
  if(sp == strbuf)
  {
    yyerror ("Syntax error");
    return -1;
  }
  while(sp && *sp)
  {
    switch(*sp++)
    {
    case 'K':
      yylval.num *= 1024;
      break;
    case 'M':
      yylval.num *= 1024 * 1024;
      break;
    case 'G':
      yylval.num *= 1024 * 1024 * 1024;
      break;
    case 'm':
      yylval.num *= 60;
      break;
    case 'h':
      yylval.num *= 60 * 60;
      break;
    case 'd':
      yylval.num *= 60 * 60 * 24;
      break;
    default:
      yyerror ("Syntax error");
      return -1;
    }
  }
  return D_NUMBER;
}

void yyerror(const char *err)
{
  char *eol;
  
  // Trick to make sure there's data loaded if available.
  fetch_ch();
  unfetch_ch();
  
  eol = strchr(confP, '\n');
  if(eol)
    *eol = 0;
  
  syslog (LOG_ERR, "Config file line %d: %s near %.12s.", confLine,
	err, confP? eol == confP? "End-of-Line": confP : "End-of-File");
  
  if(eol)
    *eol = '\n';
}


syntax highlighted by Code2HTML, v. 0.9.1