/*****************************************************************************\ * 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'; }