1212 lines
32 KiB
C++
1212 lines
32 KiB
C++
/*******************************************************************************
|
|
* unixoptions.cpp
|
|
*
|
|
* Written by Christoph Hormann <chris_hormann@gmx.de>
|
|
* Based on 3.6 elements by Nicolas Calimet
|
|
*
|
|
* Processing system for options in povray.conf, command line
|
|
* and environment variables.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
|
|
* Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
|
|
*
|
|
* POV-Ray is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* POV-Ray 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 Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
* ---------------------------------------------------------------------------
|
|
* POV-Ray is based on the popular DKB raytracer version 2.12.
|
|
* DKBTrace was originally written by David K. Buck.
|
|
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
|
|
* ---------------------------------------------------------------------------
|
|
* $File: //depot/public/povray/3.x/vfe/unix/unixoptions.cpp $
|
|
* $Revision: #1 $
|
|
* $Change: 6069 $
|
|
* $DateTime: 2013/11/06 11:59:40 $
|
|
* $Author: chrisc $
|
|
*******************************************************************************/
|
|
|
|
#include "unixoptions.h"
|
|
#include <fstream>
|
|
#include <sys/stat.h>
|
|
#include <boost/format.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
namespace vfePlatform
|
|
{
|
|
using namespace std;
|
|
|
|
extern bool gShelloutsPermittedFixThis;
|
|
|
|
const UnixOptionsProcessor::Option_Info UnixOptionsProcessor::Standard_Options[] =
|
|
{
|
|
// section name, option name, default, has_param, command line parameter, environment variable name, help text
|
|
UnixOptionsProcessor::Option_Info("general", "help", "off", false, "--help|-help|-h|-?", "", "display usage information"),
|
|
UnixOptionsProcessor::Option_Info("general", "temppath", "", true, "", "POV_TEMP_DIR", "directory for temporary files"),
|
|
UnixOptionsProcessor::Option_Info("general", "version", "off", false, "--version|-version|--V", "", "display program version"),
|
|
UnixOptionsProcessor::Option_Info("general", "benchmark", "off", false, "--benchmark|-benchmark", "", "run the standard POV-Ray benchmark"),
|
|
UnixOptionsProcessor::Option_Info("", "", "", false, "", "", "") // has to be last
|
|
};
|
|
|
|
// based on 3.6 unix_create_globals()
|
|
UnixOptionsProcessor::UnixOptionsProcessor(vfeSession *session) :
|
|
m_Session(session)
|
|
{
|
|
char* value;
|
|
value = getenv("HOME");
|
|
m_home = value ? value:"";
|
|
|
|
// Default values for I/O restrictions: everything is allowed.
|
|
// Any restrictions must come from system or user configuration.
|
|
m_file_io = IO_UNSET;
|
|
m_shellout = SHL_UNSET;
|
|
|
|
// system configuration file
|
|
m_conf = "";
|
|
m_sysconf = POVCONFDIR "/povray.conf";
|
|
|
|
// user configuration file
|
|
if (m_home.length() > 0)
|
|
{
|
|
m_user_dir = m_home + "/." PACKAGE "/" VERSION_BASE;
|
|
m_userconf = m_home + "/." PACKAGE "/" VERSION_BASE "/povray.conf";
|
|
}
|
|
else
|
|
{
|
|
m_user_dir = "";
|
|
m_userconf = "";
|
|
}
|
|
|
|
// system ini file
|
|
m_sysini = POVCONFDIR "/povray.ini";
|
|
m_sysini_old = POVCONFDIR_BACKWARD "/povray.ini";
|
|
|
|
// user ini file
|
|
if (m_home.length() > 0)
|
|
m_userini = m_home + "/." PACKAGE "/" VERSION_BASE "/povray.ini";
|
|
else
|
|
m_userini = "";
|
|
|
|
if (m_home.length() > 0)
|
|
m_userini_old = m_home + "/.povrayrc";
|
|
else
|
|
m_userini_old = "";
|
|
|
|
#ifdef UNIX_DEBUG
|
|
cerr << "PATHS" << endl;
|
|
cerr << " HOME = " << m_home << endl;
|
|
cerr << " SYSCONF = " << m_sysconf << endl;
|
|
cerr << " USERCONF = " << m_userconf << endl;
|
|
cerr << " SYSINI = " << m_sysini << endl;
|
|
cerr << " SYSINI_OLD = " << m_sysini_old << endl;
|
|
cerr << " USERINI = " << m_userini << endl;
|
|
cerr << " USERINI_OLD = " << m_userini_old << endl;
|
|
#endif
|
|
#if 0
|
|
cerr << "--- tests ---" << endl;
|
|
cerr << "## unix_getcwd: " << unix_getcwd() << endl;
|
|
cerr << "## basename(SYSCONF): " << basename(m_sysconf) << endl;
|
|
cerr << "## dirname(SYSCONF): " << dirname(m_sysconf) << endl;
|
|
cerr << "## basename(/test/path/): " << basename("/test/path/") << endl;
|
|
cerr << "## dirname(/test/path/): " << dirname("/test/path/") << endl;
|
|
cerr << "## CanonicalizePath(../test/to/file/): " << CanonicalizePath("../test/to/file/") << endl;
|
|
cerr << "## CanonicalizePath(/another/dir/../file): " << CanonicalizePath("/another/dir/../file") << endl;
|
|
cerr << "------------" << endl;
|
|
#endif
|
|
|
|
// register all standard options
|
|
for (int i = 0; Standard_Options[i].Section != ""; i++)
|
|
{
|
|
list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), Standard_Options[i]);
|
|
|
|
// add this option if not already there
|
|
if (iter == m_user_options.end())
|
|
m_user_options.push_back(Standard_Options[i]);
|
|
}
|
|
|
|
// process system and user povray.conf
|
|
process_povray_conf();
|
|
}
|
|
|
|
string UnixOptionsProcessor::GetTemporaryPath(void)
|
|
{
|
|
string path = QueryOptionString("general", "temppath");
|
|
if (path.length() == 0)
|
|
{
|
|
struct stat s;
|
|
path = "/tmp/";
|
|
if (stat (path.c_str(), &s) == 0 && S_ISDIR (s.st_mode) && (s.st_mode & S_IRWXU) == S_IRWXU)
|
|
return path;
|
|
path = unix_getcwd();
|
|
}
|
|
if (path[path.length()-1] != '/')
|
|
path = path + "/";
|
|
return path;
|
|
}
|
|
|
|
void UnixOptionsProcessor::PrintOptions(void)
|
|
{
|
|
cerr << endl;
|
|
cerr << "Platform specific command line options:" << endl;
|
|
|
|
string section("");
|
|
bool section_new = false;
|
|
|
|
for (list<Option_Info>::iterator iter = m_user_options.begin(); iter != m_user_options.end(); iter++)
|
|
{
|
|
if ((*iter).Section != section)
|
|
{
|
|
section = (*iter).Section;
|
|
section_new = true;
|
|
}
|
|
if ((*iter).CmdOption != "")
|
|
{
|
|
if (section_new)
|
|
{
|
|
cerr << endl;
|
|
cerr << " '" << section << "' options:" << endl << endl;
|
|
section_new = false;
|
|
}
|
|
cerr << " " << boost::format("%1% %|32t|%2%") % (*iter).CmdOption % (*iter).Comment << endl;
|
|
}
|
|
}
|
|
cerr << endl;
|
|
}
|
|
|
|
void UnixOptionsProcessor::Register(const Option_Info options[])
|
|
{
|
|
for (int i = 0; options[i].Section != ""; i++)
|
|
{
|
|
list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), options[i]);
|
|
|
|
// add this option if not already there
|
|
if (iter == m_user_options.end())
|
|
m_user_options.push_back(options[i]);
|
|
}
|
|
}
|
|
|
|
void UnixOptionsProcessor::QueryOption(Option_Info &option)
|
|
{
|
|
list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), option);
|
|
|
|
if (iter != m_user_options.end())
|
|
option.Value = (*iter).Value;
|
|
else
|
|
option.Value = "";
|
|
}
|
|
|
|
string UnixOptionsProcessor::QueryOptionString(const string §ion, const string &name)
|
|
{
|
|
Option_Info opt(section, name);
|
|
QueryOption(opt);
|
|
return opt.Value;
|
|
}
|
|
|
|
int UnixOptionsProcessor::QueryOptionInt(const string §ion, const string &name, const int dflt)
|
|
{
|
|
int res;
|
|
try
|
|
{
|
|
res = boost::lexical_cast<int>(QueryOptionString(section, name));
|
|
}
|
|
catch(boost::bad_lexical_cast &)
|
|
{
|
|
res = dflt;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
float UnixOptionsProcessor::QueryOptionFloat(const string §ion, const string &name, const float dflt)
|
|
{
|
|
float res;
|
|
try
|
|
{
|
|
res = boost::lexical_cast<float>(QueryOptionString(section, name));
|
|
}
|
|
catch(boost::bad_lexical_cast &)
|
|
{
|
|
res = dflt;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool UnixOptionsProcessor::isOptionSet(const Option_Info &option)
|
|
{
|
|
list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), option);
|
|
|
|
if (iter != m_user_options.end())
|
|
{
|
|
if (!(*iter).has_param && ((*iter).Value == ""))
|
|
return true;
|
|
string val = (*iter).Value;
|
|
boost::to_lower(val);
|
|
if (val == "yes" || val == "on" || val == "true" || val == "1")
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UnixOptionsProcessor::isOptionSet(const string §ion, const string &name)
|
|
{
|
|
for (list<Option_Info>::iterator iter = m_user_options.begin(); iter != m_user_options.end(); iter++)
|
|
{
|
|
if (((*iter).Section == section) && ((*iter).Name == name))
|
|
return isOptionSet(*iter);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void UnixOptionsProcessor::ProcessOptions(int *argc, char **argv[])
|
|
{
|
|
// add custom configuration options found in povray.conf files
|
|
for (list<Conf_Option>::iterator iter_c = m_custom_conf_options.begin(); iter_c != m_custom_conf_options.end(); iter_c++)
|
|
{
|
|
Option_Info new_opt((*iter_c).Section, (*iter_c).Name, (*iter_c).Value, ((*iter_c).Value != ""), "", "");
|
|
|
|
list<Option_Info>::iterator iter = find(m_user_options.begin(), m_user_options.end(), new_opt);
|
|
if (iter == m_user_options.end())
|
|
m_user_options.push_back(new_opt);
|
|
else
|
|
(*iter).Value = (*iter_c).Value;
|
|
}
|
|
|
|
m_user_options.sort();
|
|
|
|
#ifdef UNIX_DEBUG
|
|
cerr << "OPTIONS (" << m_user_options.size() << ")" << endl;
|
|
#endif
|
|
|
|
// check command line options and environment variables of all registered options
|
|
// this overrides povray.conf settings
|
|
for (list<Option_Info>::iterator iter = m_user_options.begin(); iter != m_user_options.end(); iter++)
|
|
{
|
|
// in ascending order of priority:
|
|
// environment variables:
|
|
if ((*iter).EnvVariable != "")
|
|
{
|
|
char *tmp = getenv((*iter).EnvVariable.c_str());
|
|
if (tmp) // variable defined?
|
|
(*iter).Value = tmp;
|
|
}
|
|
|
|
// command line options:
|
|
// based on 3.6 XWIN_init_povray()
|
|
if ((*iter).CmdOption != "")
|
|
{
|
|
int oargc = *argc;
|
|
char **oargv = *argv;
|
|
|
|
// TODO: free those when no more needed
|
|
char **nargv = (char **)malloc((oargc + 1) * sizeof(char *));
|
|
int nargc = oargc;
|
|
for (int i = 0; i < nargc; i++)
|
|
{
|
|
nargv[i] = (char *)malloc(strlen(oargv[i]) + 1);
|
|
strcpy(nargv[i], oargv[i]);
|
|
}
|
|
|
|
nargv[nargc] = NULL;
|
|
|
|
vector<string> CmdVariations;
|
|
boost::split(CmdVariations, (*iter).CmdOption, boost::is_any_of("|"));
|
|
|
|
for (vector<string>::iterator iter_c = CmdVariations.begin(); iter_c != CmdVariations.end(); iter_c++)
|
|
{
|
|
for (int i = 1; i < nargc;)
|
|
{
|
|
if (string(nargv[i]) == (*iter_c))
|
|
{
|
|
if ((*iter).has_param)
|
|
{
|
|
int j = i + 1;
|
|
if (j < nargc && nargv[j] != NULL)
|
|
{
|
|
(*iter).Value = nargv[j];
|
|
remove_arg(&nargc, nargv, j);
|
|
}
|
|
}
|
|
else
|
|
(*iter).Value = "on"; // FIXME [nc]
|
|
remove_arg(&nargc, nargv, i);
|
|
}
|
|
else
|
|
i++;
|
|
if (nargv[i] == NULL)
|
|
break;
|
|
}
|
|
}
|
|
|
|
*argv = nargv;
|
|
*argc = nargc;
|
|
}
|
|
|
|
#ifdef UNIX_DEBUG
|
|
cerr << " " << (*iter).Name << " = " << (*iter).Value << "(" << (*iter).CmdOption << ", " << (*iter).EnvVariable << ")" << endl;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// based on 3.x RemoveArgs() by Andreas Dilger
|
|
void UnixOptionsProcessor::remove_arg(int *argc, char *argv[], int index)
|
|
{
|
|
if (index >= *argc || index == 0)
|
|
return;
|
|
|
|
if (argv[index] != NULL)
|
|
free(argv[index]);
|
|
|
|
for (; index < *argc; index++)
|
|
argv[index] = argv[index + 1];
|
|
|
|
(*argc)--;
|
|
}
|
|
|
|
// based on 3.6 UNIX_getcwd()
|
|
string UnixOptionsProcessor::unix_getcwd(void)
|
|
{
|
|
#ifdef HAVE_GETCWD
|
|
size_t len;
|
|
|
|
len = 256; // default buffer size
|
|
char *tmp = (char *) calloc(len, 1);
|
|
|
|
while(getcwd(tmp, len) == NULL) // buffer is too small
|
|
{
|
|
free(tmp);
|
|
len *= 2; // double buffer size and try again
|
|
tmp = (char *) calloc(len, 1);
|
|
}
|
|
#else
|
|
string tmp = getenv("PWD"); // must not be NULL; checked by configure
|
|
if(tmp.length() == 0) // run-time checks are safer anyway
|
|
{
|
|
// TODO: correct error handling
|
|
char *errormsg =
|
|
"Cannot determine the current working directory.\n"
|
|
"Check that the PWD environment variable does exist and is valid.\n";
|
|
if(no_error_call)
|
|
{
|
|
fprintf(stderr, "%s: %s\n", PACKAGE, errormsg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
else
|
|
Error("%s", errormsg);
|
|
}
|
|
#endif
|
|
|
|
string s = tmp + string("/"); // add final slash
|
|
|
|
#ifdef HAVE_GETCWD
|
|
free(tmp);
|
|
#endif
|
|
|
|
return s;
|
|
}
|
|
|
|
// based on 3.6 unix_basename()
|
|
string UnixOptionsProcessor::basename(const string &path)
|
|
{
|
|
if(path.length() < 2) // less than two characters
|
|
return path;
|
|
|
|
string s = path;
|
|
if (s[s.length()-1] == '/')
|
|
s.erase(s.length()-1);
|
|
string::size_type pos = s.rfind('/');
|
|
if (pos == 0)
|
|
return s;
|
|
s.erase(0, pos+1);
|
|
return s;
|
|
}
|
|
|
|
// based on 3.6 unix_dirname()
|
|
string UnixOptionsProcessor::dirname(const string &path)
|
|
{
|
|
if(path.length() < 2) // less than two characters
|
|
return string("");
|
|
|
|
string s = path;
|
|
if (s[s.length()-1] == '/')
|
|
s.erase(s.length()-1);
|
|
string::size_type pos = s.rfind('/');
|
|
if (pos == 0)
|
|
return string("");
|
|
s.erase(pos);
|
|
return s;
|
|
}
|
|
|
|
// based on 3.6 unix_readlink()
|
|
string UnixOptionsProcessor::unix_readlink(const string &path)
|
|
{
|
|
#ifdef HAVE_READLINK
|
|
char *tmp;
|
|
size_t len;
|
|
int status;
|
|
|
|
len = 256; // default buffer size
|
|
tmp = (char *) calloc(len, 1); // init with '\0'
|
|
|
|
while(true)
|
|
{
|
|
status = readlink(path.c_str(), tmp, len-1); // without terminating '\0'
|
|
if(status < 0) // an error occured, return empty string
|
|
{
|
|
free(tmp);
|
|
return string("");
|
|
}
|
|
else if(status == len-1) // the buffer is probably too small
|
|
{
|
|
free(tmp);
|
|
len *= 2; // double buffer size and try again
|
|
tmp = (char *) calloc(len, 1);
|
|
}
|
|
else // all right, let's go further
|
|
break;
|
|
}
|
|
|
|
// do we have an absolute path ?
|
|
if(tmp[0] != '/') // no; concatenate symlink to the path dirname
|
|
{
|
|
string s = dirname(path) + "/" + tmp;
|
|
free(tmp);
|
|
return s;
|
|
}
|
|
else // yes; just resize buffer
|
|
{
|
|
string s = tmp;
|
|
free(tmp);
|
|
return s;
|
|
}
|
|
#else
|
|
return string("");
|
|
#endif
|
|
}
|
|
|
|
// based on 3.6 UNIX_canonicalize_path()
|
|
string UnixOptionsProcessor::CanonicalizePath(const string &path)
|
|
{
|
|
int i;
|
|
typedef struct { const char *match, *replace; } subst;
|
|
const subst strings[] = { // beware: order does matter
|
|
{ "%INSTALLDIR%", POVLIBDIR },
|
|
{ "%HOME%", m_home.c_str() },
|
|
{ "//", "/" },
|
|
{ "/./", "/" },
|
|
{ NULL, NULL } // sentinel
|
|
};
|
|
|
|
// nothing to canonicalize; return an empty string
|
|
if(path.length() == 0)
|
|
return string("");
|
|
|
|
#ifdef UNIX_DEBUG
|
|
cerr << " CANONICALIZE '" << path << "'" << endl;
|
|
#endif
|
|
|
|
string s = path;
|
|
|
|
// substitute all occurences of 'match' by 'replace'
|
|
i = 0;
|
|
while(strings[i].match)
|
|
{
|
|
boost::replace_all(s, strings[i].match, strings[i].replace);
|
|
++i;
|
|
#ifdef UNIX_DEBUG
|
|
cerr << " su: " << s << endl;
|
|
#endif
|
|
}
|
|
|
|
// substitute the current working dir to the first "./"
|
|
// or add the cwd to the first directory or "../"
|
|
if (boost::starts_with(s, "./"))
|
|
{
|
|
s.erase(0, 2);
|
|
s.insert(0, unix_getcwd());
|
|
}
|
|
else if(s[0] != '/' || boost::starts_with(s, "../"))
|
|
{
|
|
s.insert(0, unix_getcwd());
|
|
}
|
|
|
|
// iteratively translate all symlinks in the path (dirname and basename)
|
|
#ifdef HAVE_READLINK
|
|
i = 0;
|
|
if(s[i] == '/')
|
|
++i;
|
|
do
|
|
{
|
|
while(i < s.length() && s[i] != '/')
|
|
++i;
|
|
string tmp1 = s.substr(0, i);
|
|
string tmp2 = s.substr(i, s.length()-i);
|
|
string tmp3 = unix_readlink(tmp1);
|
|
if (tmp3.length() > 0)
|
|
{
|
|
#ifdef UNIX_DEBUG
|
|
cerr << " ln: " << tmp1 << " -> " << tmp3 << endl;
|
|
cerr << " " << tmp3 << tmp2 << endl;
|
|
#endif
|
|
s = tmp3 + tmp2;
|
|
i = 0; // start again from beginning
|
|
if(s[i] == '/')
|
|
++i;
|
|
}
|
|
else
|
|
{
|
|
#ifdef UNIX_DEBUG
|
|
cerr << " ln: " << tmp1 << endl;
|
|
#endif
|
|
++i;
|
|
}
|
|
} while(i < s.length());
|
|
#endif // HAVE_READLINK
|
|
|
|
// substitute all occurences of "dir/.." by ""
|
|
string tmp = s;
|
|
string::size_type pos = s.find("/..");
|
|
while (pos != string::npos)
|
|
{
|
|
string::size_type pos2 = s.rfind('/', pos-1);
|
|
s.erase(pos2+1, pos-pos2+3);
|
|
#ifdef UNIX_DEBUG
|
|
cerr << " su: " << s << endl;
|
|
#endif
|
|
pos = s.find("/..", pos+3);
|
|
}
|
|
|
|
// remove the last "/." if any
|
|
if (boost::ends_with(s, "/."))
|
|
{
|
|
s.erase(s.length()-3);
|
|
#ifdef UNIX_DEBUG
|
|
cerr << " su: " << s << endl;
|
|
#endif
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
// based on 3.6 pre_process_conf_line()
|
|
string UnixOptionsProcessor::pre_process_conf_line(const string &input)
|
|
{
|
|
string s = boost::trim_copy(input);
|
|
|
|
// find comment mark
|
|
string::size_type pos = s.find(";");
|
|
if (pos != string::npos)
|
|
{
|
|
s.erase(pos);
|
|
boost::trim(s);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
// based on 3.6 add_permitted_path()
|
|
void UnixOptionsProcessor::add_permitted_path(list<UnixPath> &paths, const string &input, const string &conf_name, unsigned long line_number)
|
|
{
|
|
char quote = 0;
|
|
bool descend = false;
|
|
bool writable = false;
|
|
|
|
// read or read+write entry
|
|
if (boost::starts_with(input, "read"))
|
|
{
|
|
int spos = 4;
|
|
// we have a read+write path
|
|
if (boost::starts_with(input, "read+write"))
|
|
{
|
|
writable = true;
|
|
spos = 10;
|
|
}
|
|
// sub-directory search
|
|
if (input[spos] == '*')
|
|
descend = true;
|
|
|
|
string::size_type pos = input.find('='); // find equal sign
|
|
if (pos != string::npos) // equal sign found
|
|
{
|
|
int i = 0;
|
|
string s = input.substr(pos+1, input.length()-pos-1);
|
|
boost::trim(s);
|
|
|
|
if(s[i] == '"' || s[i] == '\'')
|
|
{
|
|
quote = s[i]; // store and skip quote
|
|
++i;
|
|
}
|
|
int begin = i;
|
|
while(i < s.length()) // find next space caracter or closing quote
|
|
{
|
|
if(s[i] == quote || (!quote && isspace((int) s[i])))
|
|
break;
|
|
++i;
|
|
}
|
|
if(quote && s[i] != quote) // no closing quote
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: ignored entry: missing closing %c quote\n",
|
|
PACKAGE, conf_name.c_str(), line_number, quote
|
|
);
|
|
else if(i-begin) // store given directory
|
|
{
|
|
string directory = s.substr(begin, i-begin) + "/";
|
|
s = CanonicalizePath(directory);
|
|
|
|
#ifdef UNIX_DEBUG
|
|
cerr << "PERMITTED ADD '" << s << "'" << endl;
|
|
#endif
|
|
paths.push_back(UnixPath(s, descend, writable));
|
|
}
|
|
else // nothing found after the equal sign
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: ignored entry: missing directory\n",
|
|
PACKAGE, conf_name.c_str(), line_number
|
|
);
|
|
}
|
|
else // equal sign not found
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: ignored entry: missing equal sign\n",
|
|
PACKAGE, conf_name.c_str(), line_number
|
|
);
|
|
}
|
|
// unknown entry
|
|
else if(input.length() > 0)
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: unknown '%s' setting\n",
|
|
PACKAGE, conf_name.c_str(), line_number, input.c_str()
|
|
);
|
|
}
|
|
|
|
// based on 3.6 unix_parse_conf_file()
|
|
void UnixOptionsProcessor::parse_conf_file(std::istream &Stream, const string &conf_name, bool user_mode)
|
|
{
|
|
list<UnixPath> paths;
|
|
string line;
|
|
string Custom_Section;
|
|
unsigned long line_number;
|
|
bool user_file_io_rejected;
|
|
FileIO file_io;
|
|
bool file_io_is_set;
|
|
ShellOut shellout;
|
|
bool shellout_is_set;
|
|
short i;
|
|
|
|
typedef enum { NONE, FILE_IO, SHELLOUT, PERMITTED_PATHS, UNKNOWN } SectionVal;
|
|
SectionVal section;
|
|
typedef struct Section { const char *label; const SectionVal value; } Section;
|
|
const Section sections[] =
|
|
{
|
|
{ "" , NONE }, // init
|
|
{ "[File I/O Security]", FILE_IO },
|
|
{ "[Shellout Security]", SHELLOUT },
|
|
{ "[Permitted Paths]" , PERMITTED_PATHS },
|
|
{ NULL , UNKNOWN } // sentinel
|
|
};
|
|
|
|
typedef struct IOSettings { const char *label; const FileIO value; } IOSettings;
|
|
const IOSettings io_settings[] =
|
|
{
|
|
{ "" , IO_UNSET },
|
|
{ "none" , IO_NONE },
|
|
{ "read-only" , IO_READONLY },
|
|
{ "restricted", IO_RESTRICTED },
|
|
{ NULL , IO_UNKNOWN }
|
|
};
|
|
|
|
typedef struct SHLSettings { const char *label; const ShellOut value; } SHLSettings;
|
|
const SHLSettings shl_settings[] =
|
|
{
|
|
{ "" , SHL_UNSET },
|
|
{ "allowed" , SHL_ALLOWED },
|
|
{ "forbidden", SHL_FORBIDDEN },
|
|
{ NULL , SHL_UNKNOWN }
|
|
};
|
|
|
|
// inits
|
|
line_number = 0;
|
|
user_file_io_rejected = false;
|
|
file_io_is_set = shellout_is_set = false;
|
|
section = NONE;
|
|
file_io = IO_UNSET;
|
|
shellout = SHL_UNSET;
|
|
|
|
#ifdef UNIX_DEBUG
|
|
cerr << "PARSE CONF '" << conf_name << "'" << endl;
|
|
#endif
|
|
|
|
// Since the file format allows to read permitted paths before
|
|
// setting [File I/O Security], the paths must be stored in a local
|
|
// list which will be used only when the user setting for file I/O
|
|
// is accepted.
|
|
if(!user_mode)
|
|
paths = m_permitted_paths;
|
|
|
|
while(std::getline(Stream, line))
|
|
{
|
|
// check for failbit and badbit and eofbit
|
|
if(!Stream.good())
|
|
{
|
|
// Only in case of the badbit we can assume that some lower
|
|
// level system API function has set errno and perror() can be
|
|
// used safely.
|
|
if(Stream.bad())
|
|
{
|
|
fprintf(stderr, "%s: error while reading/opening configuration file ", PACKAGE);
|
|
perror(conf_name.c_str());
|
|
}
|
|
break;
|
|
}
|
|
// preprocess line
|
|
line = pre_process_conf_line(line);
|
|
++line_number;
|
|
|
|
// skip empty line
|
|
if(line.length() == 0)
|
|
continue;
|
|
|
|
// check section
|
|
if(line[0] == '[') // new section
|
|
{
|
|
Custom_Section = "";
|
|
// search section
|
|
for(i = 1; sections[i].label; ++i)
|
|
if(boost::starts_with(line, sections[i].label))
|
|
break;
|
|
|
|
section = sections[i].value;
|
|
|
|
// unknown section
|
|
if(section == UNKNOWN)
|
|
{
|
|
Custom_Section = boost::to_lower_copy(line);
|
|
Custom_Section.erase(0,1);
|
|
Custom_Section.erase(Custom_Section.find(']'));
|
|
}
|
|
} // check section
|
|
|
|
// process custom sections
|
|
else if (Custom_Section.length() != 0)
|
|
{
|
|
// split at '='
|
|
string::size_type pos = line.find('=');
|
|
string option_name;
|
|
string option_val;
|
|
if (pos == string::npos)
|
|
{
|
|
option_name = boost::to_lower_copy(line);
|
|
option_val = "";
|
|
}
|
|
else
|
|
{
|
|
option_name = boost::to_lower_copy(line.substr(0, pos));
|
|
option_val = line.substr(pos+1, line.length()-pos-1);
|
|
}
|
|
#ifdef UNIX_DEBUG
|
|
cerr << "CUSTOM OPTION:" << endl;
|
|
cerr << " Section: " << Custom_Section << endl;
|
|
cerr << " Name: " << option_name << endl;
|
|
cerr << " Value: " << option_val << endl;
|
|
#endif
|
|
m_custom_conf_options.push_back(Conf_Option(Custom_Section, option_name, option_val));
|
|
}
|
|
|
|
// file I/O security
|
|
else if(section == FILE_IO)
|
|
{
|
|
// search setting
|
|
for(i = 0; io_settings[i].label; ++i)
|
|
if(line != string(io_settings[i].label))
|
|
break;
|
|
file_io = io_settings[i].value;
|
|
|
|
// multiple settings were found
|
|
if(file_io_is_set)
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: multiple settings for %s\n",
|
|
PACKAGE, conf_name.c_str(), line_number, sections[section].label
|
|
);
|
|
if(file_io != IO_UNSET)
|
|
file_io_is_set = true;
|
|
|
|
// unknown setting
|
|
if(file_io == IO_UNKNOWN)
|
|
{
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: unknown '%s' setting for %s: ",
|
|
PACKAGE, conf_name.c_str(), line_number, line.c_str(), sections[section].label
|
|
);
|
|
if(user_mode)
|
|
{
|
|
fprintf(stderr,
|
|
"using system setting '%s'\n",
|
|
io_settings[m_file_io].label
|
|
);
|
|
file_io = m_file_io;
|
|
user_file_io_rejected = true; // won't account for the user paths
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "I/O restrictions are disabled\n");
|
|
file_io = IO_NONE;
|
|
}
|
|
}
|
|
|
|
// user setting not allowed
|
|
if(user_mode && file_io < m_file_io)
|
|
{
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: "
|
|
"the user setting '%s' for %s is less restrictive than "
|
|
"the system setting '%s' in '%s': using system setting\n",
|
|
PACKAGE, conf_name.c_str(), line_number,
|
|
io_settings[ file_io].label, sections[section].label,
|
|
io_settings[m_file_io].label, m_conf.c_str()
|
|
);
|
|
file_io = m_file_io;
|
|
user_file_io_rejected = true; // won't account for the user paths
|
|
}
|
|
|
|
m_file_io = file_io;
|
|
} // file I/O security
|
|
|
|
// shellout security
|
|
else if(section == SHELLOUT)
|
|
{
|
|
// search setting
|
|
for(i = 0; shl_settings[i].label; ++i)
|
|
if(line == string(shl_settings[i].label))
|
|
break;
|
|
shellout = shl_settings[i].value;
|
|
|
|
// multiple settings were found
|
|
if(shellout_is_set)
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: multiple settings for %s\n",
|
|
PACKAGE, conf_name.c_str(), line_number, sections[section].label
|
|
);
|
|
if(shellout != SHL_UNSET)
|
|
shellout_is_set = true;
|
|
|
|
// unknown setting
|
|
if(shellout == SHL_UNKNOWN)
|
|
{
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: unknown '%s' setting for %s: ",
|
|
PACKAGE, conf_name.c_str(), line_number, line.c_str(), sections[section].label
|
|
);
|
|
if(user_mode)
|
|
{
|
|
fprintf(stderr,
|
|
"using system setting '%s'\n",
|
|
shl_settings[m_shellout].label
|
|
);
|
|
shellout = m_shellout;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "shellout security is disabled\n");
|
|
shellout = SHL_ALLOWED;
|
|
}
|
|
}
|
|
|
|
// user setting not allowed
|
|
if(user_mode
|
|
&& m_shellout == SHL_FORBIDDEN
|
|
&& m_shellout != shellout)
|
|
{
|
|
fprintf(stderr,
|
|
"%s: %s: %lu: "
|
|
"the user setting '%s' for %s is less restrictive than "
|
|
"the system '%s' setting in '%s': using system setting\n",
|
|
PACKAGE, conf_name.c_str(), line_number,
|
|
shl_settings[ shellout].label, sections[section].label,
|
|
shl_settings[m_shellout].label, m_conf.c_str()
|
|
);
|
|
shellout = m_shellout;
|
|
}
|
|
|
|
m_shellout = shellout;
|
|
gShelloutsPermittedFixThis = shellout == SHL_ALLOWED;
|
|
} // shellout security
|
|
|
|
// permitted paths
|
|
else if(section == PERMITTED_PATHS)
|
|
add_permitted_path(paths, line, conf_name, line_number);
|
|
|
|
} // Read file loop end
|
|
|
|
// Only in case of the badbit we can assume that some lower
|
|
// level system API function has set errno. Then, perror() can be
|
|
// used safely.
|
|
if(Stream.bad())
|
|
{
|
|
fprintf(stderr, "%s: error while reading/opening config file ", PACKAGE);
|
|
perror(conf_name.c_str());
|
|
}
|
|
|
|
#ifdef UNIX_DEBUG
|
|
fprintf(stderr,
|
|
"I/O RESTRICTIONS\n"
|
|
" file_io = %d\tconfig->file_io = %d\n"
|
|
" shellout = %d\tconfig->shellout = %d\n",
|
|
file_io, m_file_io,
|
|
shellout, m_shellout
|
|
);
|
|
#endif
|
|
|
|
// assign user settings and paths
|
|
if(user_mode)
|
|
{
|
|
if(user_file_io_rejected)
|
|
{
|
|
fprintf(stderr,
|
|
"%s: %s: user permitted paths are ignored: using system paths\n",
|
|
PACKAGE, conf_name.c_str()
|
|
);
|
|
}
|
|
else
|
|
{
|
|
m_permitted_paths = paths; // assign new paths
|
|
}
|
|
}
|
|
}
|
|
|
|
// based on 3.6 unix_process_povray_conf()
|
|
void UnixOptionsProcessor::process_povray_conf(void)
|
|
{
|
|
m_Session->ClearPaths();
|
|
m_Session->AddExcludedPath(string(POVCONFDIR));
|
|
if (m_user_dir.length() != 0)
|
|
m_Session->AddExcludedPath(m_user_dir);
|
|
|
|
// read system configuration file
|
|
if(m_sysconf.length() != 0)
|
|
{
|
|
std::ifstream stream ( m_sysconf.c_str() );
|
|
if ( stream.is_open() )
|
|
{
|
|
parse_conf_file(stream, m_sysconf, false);
|
|
m_conf = m_sysconf;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s: cannot open the system configuration file ", PACKAGE);
|
|
perror(m_sysconf.c_str());
|
|
}
|
|
}
|
|
|
|
// read user configuration file
|
|
if(m_userconf.length() != 0)
|
|
{
|
|
std::ifstream stream ( m_userconf.c_str() );
|
|
if ( stream.is_open() )
|
|
{
|
|
parse_conf_file(stream, m_userconf, true);
|
|
m_conf = m_userconf;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s: cannot open the user configuration file ", PACKAGE);
|
|
perror(m_userconf.c_str());
|
|
}
|
|
}
|
|
|
|
// no file was read, disable I/O restrictions
|
|
if(m_conf.length() == 0)
|
|
fprintf(stderr, "%s: I/O restrictions are disabled\n", PACKAGE);
|
|
|
|
// if no paths specified, at least include POVLIBDIR and POVCONFDIR
|
|
else if(m_permitted_paths.empty())
|
|
{
|
|
m_permitted_paths.push_back(UnixPath(string(POVLIBDIR "/"), true, false)); // read*
|
|
m_permitted_paths.push_back(UnixPath(string(POVCONFDIR "/"), false, false)); // read
|
|
}
|
|
|
|
#ifdef UNIX_DEBUG
|
|
cerr << "PERMITTED PATHS" << endl;
|
|
#endif
|
|
|
|
for(list<UnixPath>::iterator iter = m_permitted_paths.begin(); iter != m_permitted_paths.end(); iter++)
|
|
{
|
|
m_Session->AddReadPath(iter->str, iter->descend);
|
|
#ifdef UNIX_DEBUG
|
|
fprintf(stderr,
|
|
" %s%s = \"%s\"\n",
|
|
iter->writable ? "WRITE" : "READ", iter->descend ? "*" : "", iter->str.c_str()
|
|
);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
bool UnixOptionsProcessor::file_exist(const string &name)
|
|
{
|
|
FILE *file = fopen(name.c_str(), "r");
|
|
|
|
if(file != NULL)
|
|
fclose(file);
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// based on 3.6 unix_process_povray_ini()
|
|
void UnixOptionsProcessor::Process_povray_ini(vfeRenderOptions &opts)
|
|
{
|
|
// try the file pointed to by POVINI
|
|
string povini;
|
|
char * povini_c = getenv("POVINI");
|
|
if (povini_c)
|
|
{
|
|
povini = povini_c;
|
|
if (file_exist(povini))
|
|
{
|
|
opts.AddINI(povini);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s: POVINI environment: cannot open ", PACKAGE);
|
|
perror(povini.c_str());
|
|
}
|
|
}
|
|
|
|
// try any INI file in the current directory
|
|
povini = "./povray.ini";
|
|
if (file_exist(povini))
|
|
{
|
|
opts.AddINI(povini);
|
|
return;
|
|
}
|
|
|
|
// try the user or system INI file
|
|
if ((m_home.length() != 0) && file_exist(m_userini))
|
|
{
|
|
opts.AddINI(m_userini);
|
|
return;
|
|
}
|
|
if (file_exist(m_sysini))
|
|
{
|
|
opts.AddINI(m_sysini);
|
|
return;
|
|
}
|
|
|
|
// try older user or system INI files
|
|
if ((m_home.length() != 0) && file_exist(m_userini_old))
|
|
{
|
|
opts.AddINI(m_userini_old);
|
|
return;
|
|
}
|
|
if (file_exist(m_sysini_old))
|
|
{
|
|
opts.AddINI(m_sysini_old);
|
|
return;
|
|
}
|
|
|
|
// warn that no INI file was found and add minimal library_path setting
|
|
fprintf(stderr, "%s: cannot open an INI file, adding default library path\n", PACKAGE);
|
|
opts.AddLibraryPath(string(POVLIBDIR "/include"));
|
|
}
|
|
|
|
#if 0
|
|
// based on 3.6 unix_subdir()
|
|
static bool UnixOptionsProcessor::file_in_permitted_paths (const string &Filename, bool write)
|
|
{
|
|
// NOTE: Filename must be already canonicalized
|
|
|
|
// no filename nor paths
|
|
if(Filename.length() == 0 || m_permitted_paths.empty())
|
|
return false;
|
|
|
|
// scan the list of paths
|
|
for (list<UnixPath>::iterator iter = m_permitted_paths.begin(); iter != m_permitted_paths.end(); iter++)
|
|
{
|
|
if ((*iter).descend) // allows sub-directories
|
|
{
|
|
if (!write || (*iter).writable)
|
|
{
|
|
#ifdef UNIX_DEBUG
|
|
fprintf(stderr,
|
|
" FILE '%s' <-> %s* '%s'\n",
|
|
Filename.c_str(), path->writable ? "WRITE" : "READ", (*iter).str.c_str()
|
|
);
|
|
#endif
|
|
if (boost::starts_with(Filename, (*iter).str)) // match found
|
|
{
|
|
#ifdef UNIX_DEBUG
|
|
fprintf(stderr, " OK\n");
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else // check for exact match with path->str (without last slash)
|
|
{
|
|
string dirname = dirname(Filename);
|
|
string pathname = (*iter).str;
|
|
pathname.erase(pathname.length()-1);
|
|
if (!write || (*iter).writable)
|
|
{
|
|
#ifdef UNIX_DEBUG
|
|
fprintf(stderr,
|
|
" DIRNAME '%s' <-> %s '%s'\n",
|
|
dirname.c_str(), path->writable ? "WRITE" : "READ", pathname.c_str()
|
|
);
|
|
#endif
|
|
if(dirname == pathname)
|
|
{
|
|
#ifdef UNIX_DEBUG
|
|
fprintf(stderr, " OK\n");
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef UNIX_DEBUG
|
|
fprintf(stderr, " BAD\n");
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool UnixOptionsProcessor::isIORestrictionsEnabled(bool write)
|
|
{
|
|
if (IO_RESTRICTIONS_DISABLED)
|
|
return false;
|
|
if (!write && m_file_io < IO_RESTRICTED)
|
|
return false;
|
|
if(m_file_io < IO_READONLY)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void UnixOptionsProcessor::IORestrictionsError(const string &fnm, bool write, bool is_user_setting)
|
|
{
|
|
if (is_user_setting)
|
|
{
|
|
if (write)
|
|
fprintf(stderr, "%s: writing to '%s' is not permitted; check the configuration in '%s'\n", PACKAGE, fnm.c_str(), m_conf.c_str());
|
|
else
|
|
fprintf(stderr, "%s: reading from '%s' is not permitted; check the configuration in '%s'\n", PACKAGE, fnm.c_str(), m_conf.c_str());
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s: writing to '%s' is not permitted\n", PACKAGE, fnm.c_str());
|
|
}
|
|
|
|
}
|
|
|
|
}
|