Panda3D
configPage.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file configPage.cxx
10  * @author drose
11  * @date 2004-10-15
12  */
13 
14 #include "configPage.h"
15 #include "configDeclaration.h"
16 #include "configVariableCore.h"
17 #include "configVariableManager.h"
18 #include "prcKeyRegistry.h"
19 #include "config_prc.h"
20 #include "encryptStream.h"
21 
22 #include <ctype.h>
23 
24 #ifdef HAVE_OPENSSL
25 #include <openssl/evp.h>
26 #endif
27 
28 using std::istream;
29 using std::ostream;
30 using std::string;
31 
32 ConfigPage *ConfigPage::_default_page = nullptr;
33 ConfigPage *ConfigPage::_local_page = nullptr;
34 
35 /**
36  * The constructor is private because a ConfigPage should be constructed via
37  * the ConfigPageManager make_page() interface.
38  */
39 ConfigPage::
40 ConfigPage(const string &name, bool implicit_load, int page_seq) :
41  _name(name),
42  _implicit_load(implicit_load),
43  _page_seq(page_seq),
44  _sort(implicit_load ? 10 : 0),
45  _next_decl_seq(1),
46  _trust_level(0)
47 {
48 }
49 
50 /**
51  * The destructor is private because a ConfigPage should be deleted via the
52  * ConfigPageManager delete_page() interface.
53  */
54 ConfigPage::
55 ~ConfigPage() {
56  clear();
57 }
58 
59 /**
60  * Returns a pointer to the global "default page". This is the ConfigPage
61  * that lists all variables' original default values.
62  */
65  if (_default_page == nullptr) {
66  _default_page = new ConfigPage("default", false, 0);
67  }
68  return _default_page;
69 }
70 
71 /**
72  * Returns a pointer to the global "local page". This is the ConfigPage that
73  * lists the locally-assigned values for any variables in the world that have
74  * such a local assignment.
75  */
78  if (_local_page == nullptr) {
79  _local_page = new ConfigPage("local", false, 0);
80  }
81  return _local_page;
82 }
83 
84 /**
85  * Changes the explicit sort order of this particular ConfigPage. Lower-
86  * numbered pages supercede higher-numbered pages. Initially, all explicitly-
87  * loaded pages have sort value 0, and implicitly-loaded pages (found on disk)
88  * have sort value 10; you may set an individual page higher or lower to
89  * influence its priority relative to other pages.
90  */
91 void ConfigPage::
92 set_sort(int sort) {
93  if (_sort != sort) {
94  _sort = sort;
95  ConfigPageManager::get_global_ptr()->mark_unsorted();
96  }
97 }
98 
99 /**
100  * Removes all of the declarations from the page.
101  */
102 void ConfigPage::
103 clear() {
104  Declarations::iterator di;
105  for (di = _declarations.begin(); di != _declarations.end(); ++di) {
106  ConfigDeclaration *decl = (*di);
107  delete decl;
108  }
109  _declarations.clear();
110  _trust_level = 0;
111  _signature = string();
112 }
113 
114 /**
115  * Reads the contents of a complete prc file, as returned by the indicated
116  * istream, into the current page file. Returns true on success, or false on
117  * some I/O error.
118  *
119  * This is a low-level interface. Normally you do not need to call it
120  * directly. See the global functions load_prc_file() and unload_prc_file(),
121  * defined in panda/src/putil, for a higher-level interface.
122  */
123 bool ConfigPage::
124 read_prc(istream &in) {
125  // We must empty the page before we start to read it; otherwise trust level
126  // is meaningless.
127  clear();
128 
129  // We avoid getline() here because of its notorious problem with last lines
130  // that lack a trailing newline character.
131  static const size_t buffer_size = 1024;
132  char buffer[buffer_size];
133 
134 #ifdef HAVE_OPENSSL
135  // Set up the evp context for verifying the signature, if we find one.
136  _md_ctx = EVP_MD_CTX_create();
137  EVP_VerifyInit((EVP_MD_CTX *)_md_ctx, EVP_sha1());
138 #endif // HAVE_OPENSSL
139 
140  string prev_line;
141 
142  in.read(buffer, buffer_size);
143  size_t count = in.gcount();
144  while (count != 0) {
145  char *buffer_end = buffer + count;
146 
147  // Look for the first line in the buffer..
148  char *newline = (char *)memchr((void *)buffer, '\n', count);
149  if (newline == nullptr) {
150  // The buffer was one long line. Huh.
151  prev_line += string(buffer, count);
152 
153  } else {
154  // Process the first line in the buffer.
155  size_t length = newline - buffer;
156  read_prc_line(prev_line + string(buffer, length + 1));
157 
158  // Now look for the next line, etc.
159  char *start = newline + 1;
160  newline = (char *)memchr((void *)start, '\n', buffer_end - start);
161  while (newline != nullptr) {
162  length = newline - start;
163  read_prc_line(string(start, length + 1));
164  start = newline + 1;
165  newline = (char *)memchr((void *)start, '\n', buffer_end - start);
166  }
167 
168  // The remaining text in the buffer is the start of the next line.
169  length = buffer_end - start;
170  prev_line = string(start, length);
171  }
172 
173  if (in.fail() || in.eof()) {
174  // If we got a failure reading the buffer last time, don't keep reading
175  // again. Irix seems to require this test; otherwise, it repeatedly
176  // returns the same text at the end of the file.
177  count = 0;
178 
179  } else {
180  in.read(buffer, buffer_size);
181  count = in.gcount();
182  }
183  }
184 
185  if (!prev_line.empty()) {
186  read_prc_line(prev_line);
187  }
188 
189 #ifdef HAVE_OPENSSL
190  // Now validate the signature and free the SSL structures.
191  if (!_signature.empty()) {
192  PrcKeyRegistry *pkr = PrcKeyRegistry::get_global_ptr();
193  int num_keys = pkr->get_num_keys();
194  for (int i = 1; i < num_keys && _trust_level == 0; i++) {
195  EVP_PKEY *pkey = pkr->get_key(i);
196  if (pkey != nullptr) {
197  int verify_result =
198  EVP_VerifyFinal((EVP_MD_CTX *)_md_ctx,
199  (unsigned char *)_signature.data(),
200  _signature.size(), pkey);
201  if (verify_result == 1) {
202  _trust_level = i;
203  }
204  }
205  }
206  if (_trust_level == 0) {
207  prc_cat->info()
208  << "invalid signature found in " << get_name() << "\n";
209  }
210  }
211  EVP_MD_CTX_destroy((EVP_MD_CTX *)_md_ctx);
212 #endif // HAVE_OPENSSL
213 
214  bool failed = (in.fail() && !in.eof());
215 
216  return !failed;
217 }
218 
219 /**
220  * Automatically decrypts and reads the stream, given the indicated password.
221  * Note that if the password is incorrect, the result may be garbage.
222  */
223 bool ConfigPage::
224 read_encrypted_prc(istream &in, const string &password) {
225 #ifdef HAVE_OPENSSL
226  IDecryptStream decrypt(&in, false, password);
227  return read_prc(decrypt);
228 #else
229  return false;
230 #endif // HAVE_OPENSSL
231 }
232 
233 /**
234  * Adds the indicated variable/value pair as a new declaration on the page.
235  */
237 make_declaration(const string &variable, const string &value) {
238  ConfigVariableManager *variable_mgr = ConfigVariableManager::get_global_ptr();
239  return make_declaration(variable_mgr->make_variable(variable), value);
240 }
241 
242 /**
243  * Adds the indicated variable/value pair as a new declaration on the page.
244  */
246 make_declaration(ConfigVariableCore *variable, const string &value) {
248  (this, variable, value, _next_decl_seq);
249  _next_decl_seq++;
250 
251  _declarations.push_back(decl);
252  make_dirty();
253 
254  return decl;
255 }
256 
257 /**
258  * Removes the indicated declaration from the page and deletes it. Returns
259  * true if the declaration is successfully removed, false if it was not on the
260  * page.
261  */
262 bool ConfigPage::
264  Declarations::iterator di;
265  for (di = _declarations.begin(); di != _declarations.end(); ++di) {
266  if ((*di) == decl) {
267  _declarations.erase(di);
268  delete decl;
269  make_dirty();
270  return true;
271  }
272  }
273 
274  return false;
275 }
276 
277 /**
278  * Returns the number of declarations on the page.
279  */
280 size_t ConfigPage::
281 get_num_declarations() const {
282  return _declarations.size();
283 }
284 
285 /**
286  * Returns the nth declaration on the page.
287  */
289 get_declaration(size_t n) const {
290  nassertr(n < _declarations.size(), nullptr);
291  return _declarations[n];
292 }
293 
294 /**
295  * Returns a modifiable pointer to the nth declaration on the page. Any
296  * modifications will appear in the output, if the page is written out with
297  * ConfigPage::write().
298  */
300 modify_declaration(size_t n) {
301  nassertr(n < _declarations.size(), nullptr);
302  return _declarations[n];
303 }
304 
305 /**
306  * Returns the variable named by the nth declaration on the page.
307  */
308 string ConfigPage::
309 get_variable_name(size_t n) const {
310  nassertr(n < _declarations.size(), string());
311  return _declarations[n]->get_variable()->get_name();
312 }
313 
314 /**
315  * Returns the value assigned by the nth declaration on the page.
316  */
317 string ConfigPage::
318 get_string_value(size_t n) const {
319  nassertr(n < _declarations.size(), string());
320  return _declarations[n]->get_string_value();
321 }
322 
323 /**
324  * Returns true if the nth active variable on the page has been used by code,
325  * false otherwise.
326  */
327 bool ConfigPage::
328 is_variable_used(size_t n) const {
329  nassertr(n < _declarations.size(), false);
330  return _declarations[n]->get_variable()->is_used();
331 }
332 
333 /**
334  *
335  */
336 void ConfigPage::
337 output(ostream &out) const {
338  out << "ConfigPage " << get_name() << ", " << get_num_declarations()
339  << " declarations.";
340 }
341 
342 /**
343  * Outputs the first few hex digits of the signature.
344  */
345 void ConfigPage::
346 output_brief_signature(ostream &out) const {
347  size_t num_bytes = std::min(_signature.size(), (size_t)8);
348  for (size_t p = 0; p < num_bytes; ++p) {
349  unsigned int byte = _signature[p];
350 
351  unsigned int hi = (byte >> 4) & 0xf;
352  if (hi >= 10) {
353  out << (char)((hi - 10) + 'a');
354  } else {
355  out << (char)(hi + '0');
356  }
357 
358  unsigned int lo = byte & 0xf;
359  if (lo >= 10) {
360  out << (char)((lo - 10) + 'a');
361  } else {
362  out << (char)(lo + '0');
363  }
364  }
365 }
366 
367 /**
368  *
369  */
370 void ConfigPage::
371 write(ostream &out) const {
372  Declarations::const_iterator di;
373  for (di = _declarations.begin(); di != _declarations.end(); ++di) {
374  (*di)->write(out);
375  }
376 }
377 
378 /**
379  * Handles reading in a single line from a .prc file. This is called
380  * internally by read_prc() for each line.
381  */
382 void ConfigPage::
383 read_prc_line(const string &line) {
384  if (line.substr(0, 7) == "##!sig ") {
385  // This is a signature. Accumulate it into the signature and return, and
386  // don't count it as contributing to the hash.
387  for (size_t p = 7; p < line.length() - 1; p += 2) {
388  unsigned char digit = (hex_digit(line[p]) << 4) | hex_digit(line[p + 1]);
389  _signature += digit;
390  }
391  return;
392  }
393 
394 #ifdef HAVE_OPENSSL
395  // Accumulate any line that's not itself a signature into the hash, so we
396  // can validate the signature at the end.
397  EVP_VerifyUpdate((EVP_MD_CTX *)_md_ctx, line.data(), line.size());
398 #endif // HAVE_OPENSSL
399 
400  // Separate the line into a variable and a value.
401  size_t p = 0;
402  while (p < line.length() && isspace((unsigned char)line[p])) {
403  p++;
404  }
405 
406  if (p == line.length() || line[p] == '#') {
407  // Blank line or comment; do nothing.
408  return;
409  }
410 
411  size_t variable_begin = p;
412  while (p < line.length() && !isspace((unsigned char)line[p])) {
413  p++;
414  }
415  size_t variable_end = p;
416 
417  while (p < line.length() && isspace((unsigned char)line[p])) {
418  p++;
419  }
420  size_t value_begin = p;
421 
422  // Is there an embedded comment on this line?
423  p = line.find(" #", value_begin);
424  if (p == string::npos) {
425  // No, the value extends all the way to the end of the line.
426  p = line.length();
427  }
428 
429  // The value extends from here to the end of the line (or to the start of
430  // the embedded comment), so trim whitespace backwards off from there.
431  while (p > value_begin && isspace((unsigned char)line[p - 1])) {
432  p--;
433  }
434  size_t value_end = p;
435 
436  string variable = line.substr(variable_begin, variable_end - variable_begin);
437  string value = line.substr(value_begin, value_end - value_begin);
438 
439  make_declaration(variable, value);
440 }
441 
442 /**
443  * Decodes a hex digit into its numeric value.
444  */
445 unsigned int ConfigPage::
446 hex_digit(unsigned char digit) {
447  if (isalpha(digit)) {
448  return tolower(digit) - 'a' + 10;
449  } else if (isdigit(digit)) {
450  return digit - '0';
451  } else {
452  return 0;
453  }
454 }
get_name
Returns the name of the page.
Definition: configPage.h:43
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void clear()
Removes all of the declarations from the page.
Definition: configPage.cxx:103
The internal definition of a ConfigVariable.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static ConfigPage * get_local_page()
Returns a pointer to the global "local page".
Definition: configPage.cxx:77
bool read_encrypted_prc(std::istream &in, const std::string &password)
Automatically decrypts and reads the stream, given the indicated password.
Definition: configPage.cxx:224
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
static ConfigPage * get_default_page()
Returns a pointer to the global "default page".
Definition: configPage.cxx:64
ConfigVariableCore * make_variable(const std::string &name)
Creates and returns a new, undefined ConfigVariableCore with the indicated name; or if a variable wit...
bool read_prc(std::istream &in)
Reads the contents of a complete prc file, as returned by the indicated istream, into the current pag...
Definition: configPage.cxx:124
const ConfigDeclaration * get_declaration(size_t n) const
Returns the nth declaration on the page.
Definition: configPage.cxx:289
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A global object that maintains the set of ConfigVariables (actually, ConfigVariableCores) everywhere ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::string get_string_value(size_t n) const
Returns the value assigned by the nth declaration on the page.
Definition: configPage.cxx:318
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void mark_unsorted()
This method is meant to be used internally to this module; there is no need to call it directly.
A page of ConfigDeclarations that may be loaded or unloaded.
Definition: configPage.h:30
std::string get_variable_name(size_t n) const
Returns the variable named by the nth declaration on the page.
Definition: configPage.cxx:309
bool is_variable_used(size_t n) const
Returns true if the nth active variable on the page has been used by code, false otherwise.
Definition: configPage.cxx:328
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A single declaration of a config variable, typically defined as one line in a .prc file,...
modify_declaration
Returns a modifiable pointer to the nth declaration on the page.
Definition: configPage.h:77
ConfigDeclaration * make_declaration(const std::string &variable, const std::string &value)
Adds the indicated variable/value pair as a new declaration on the page.
Definition: configPage.cxx:237
void output_brief_signature(std::ostream &out) const
Outputs the first few hex digits of the signature.
Definition: configPage.cxx:346
set_sort
Changes the explicit sort order of this particular ConfigPage.
Definition: configPage.h:52
bool delete_declaration(ConfigDeclaration *decl)
Removes the indicated declaration from the page and deletes it.
Definition: configPage.cxx:263