/*****************************************************************************\
* 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