Index: setup.py
===================================================================
--- setup.py	(revision 2326)
+++ setup.py	(working copy)
@@ -35,6 +35,7 @@
          htdocs_dir = os.path.join(self.prefix, 'share', 'trac', 'htdocs')
          wiki_dir = os.path.join(self.prefix, 'share', 'trac', 'wiki-default')
          macros_dir = os.path.join(self.prefix, 'share', 'trac', 'wiki-macros')
+         config_dir = os.path.join(self.prefix, 'share', 'trac', 'conf')
          f = open(_p('trac/siteconfig.py'),'w')
          f.write("""
 # PLEASE DO NOT EDIT THIS FILE!
@@ -44,9 +45,11 @@
 __default_htdocs_dir__ = %(htdocs)r
 __default_wiki_dir__ = %(wiki)r
 __default_macros_dir__ = %(macros)r
+__default_config_dir__ = %(config)r
 
 """ % {'trac':PACKAGE, 'ver':VERSION, 'templates':_p(templates_dir),
-       'htdocs':_p(htdocs_dir), 'wiki':_p(wiki_dir), 'macros':_p(macros_dir)})
+       'htdocs':_p(htdocs_dir), 'wiki':_p(wiki_dir), 'macros':_p(macros_dir),
+       'config':_p(config_dir)})
          f.close()
 
          # Run actual install
Index: trac/config.py
===================================================================
--- trac/config.py	(revision 2326)
+++ trac/config.py	(working copy)
@@ -17,9 +17,30 @@
 from __future__ import generators
 
 from ConfigParser import ConfigParser
-import os.path
+import os.path, sys
 
+def default_dir(name):
+    try:
+        from trac import siteconfig
+        return getattr(siteconfig, '__default_%s_dir__' % name)
+    except ImportError:
+        # This is not a regular install with a generated siteconfig.py file,
+        # so try to figure out the directory based on common setups
+        import os.path, sys
+        special_dirs = {'wiki': 'wiki-default', 'macros': 'wiki-macros'}
+        dirname = special_dirs.get(name, name)
 
+        # First assume we're being executing directly form the source directory
+        import trac
+        path = os.path.join(os.path.split(os.path.dirname(trac.__file__))[0],
+                            dirname)
+        if not os.path.isdir(path):
+            # Not being executed from the source directory, so assume the
+            # default installation prefix
+            path = os.path.join(sys.prefix, 'share', 'trac', dirname)
+
+        return path
+
 class Configuration:
     """
     Thin layer over ConfigParser from the Python standard library.
@@ -28,11 +49,14 @@
     when the file has changed.
     """
 
-    def __init__(self, filename):
+    def __init__(self, filename, site_filename = os.path.join(default_dir('config'), 'trac.ini')):
         self.filename = filename
+        self.site_filename = site_filename
         self.parser = ConfigParser()
+        self.site_parser = ConfigParser()
         self.__defaults = {}
         self.__lastmtime = 0
+        self.__lastsitemtime = 0
         self.parse_if_needed()
 
     def get(self, section, name, default=None):
@@ -43,7 +67,8 @@
         return self.parser.get(section, name)
 
     def setdefault(self, section, name, value):
-        self.__defaults[(section, name)] = value
+        if (section, name) not in self.__defaults:
+            self.__defaults[(section, name)] = value
 
     def set(self, section, name, value):
         """
@@ -55,15 +80,15 @@
         return self.parser.set(section, name, value)
 
     def options(self, section):
-        if not self.parser.has_section(section):
-            return []
-        try:
-            return self.parser.items(section)
-        except AttributeError:
-            options = []
+        options = []
+        if self.parser.has_section(section):
             for option in self.parser.options(section):
                 options.append((option, self.parser.get(section, option)))
-            return options
+        for option, value in self.__defaults.iteritems():
+            if option[0] == section:
+                if not [exists for exists in options if exists[0] == option[1]]:
+                    options.append((option[1], value))
+        return options
 
     def __contains__(self, name):
         return self.parser.has_section(name)
@@ -81,32 +106,21 @@
         self.parser.write(open(self.filename, 'w'))
 
     def parse_if_needed(self):
+        if self.site_filename:
+            try:
+                modtime = os.path.getmtime(self.site_filename)
+                if modtime > self.__lastsitemtime:
+                    self.site_parser.readfp(open(self.site_filename))
+                    # Import global configuration into __defaults
+                    for section in self.site_parser.sections():
+                        for option in self.site_parser.options(section):
+                            self.__defaults[(section, option)] = self.site_parser.get(section, option)
+                    self.__lastsitemtime = modtime
+            except (IOError, OSError):
+                pass
         if not self.filename:
             return
         modtime = os.path.getmtime(self.filename)
         if modtime > self.__lastmtime:
             self.parser.readfp(open(self.filename))
             self.__lastmtime = modtime
-
-
-def default_dir(name):
-    try:
-        from trac import siteconfig
-        return getattr(siteconfig, '__default_%s_dir__' % name)
-    except ImportError:
-        # This is not a regular install with a generated siteconfig.py file,
-        # so try to figure out the directory based on common setups
-        import os.path, sys
-        special_dirs = {'wiki': 'wiki-default', 'macros': 'wiki-macros'}
-        dirname = special_dirs.get(name, name)
-
-        # First assume we're being executing directly form the source directory
-        import trac
-        path = os.path.join(os.path.split(os.path.dirname(trac.__file__))[0],
-                            dirname)
-        if not os.path.isdir(path):
-            # Not being executed from the source directory, so assume the
-            # default installation prefix
-            path = os.path.join(sys.prefix, 'share', 'trac', dirname)
-
-        return path
Index: trac/tests/config.py
===================================================================
--- trac/tests/config.py	(revision 2326)
+++ trac/tests/config.py	(working copy)
@@ -33,7 +33,7 @@
         os.remove(self.filename)
 
     def test_default(self):
-        config = Configuration(self.filename)
+        config = Configuration(self.filename, None)
         self.assertEquals('', config.get('a', 'option'))
         self.assertEquals('value', config.get('a', 'option', 'value'))
 
@@ -45,7 +45,7 @@
         configfile.writelines(['[a]\n', 'option = x\n', '\n'])
         configfile.close()
 
-        config = Configuration(self.filename)
+        config = Configuration(self.filename, None)
         self.assertEquals('x', config.get('a', 'option'))
         self.assertEquals('x', config.get('a', 'option', 'y'))
 
@@ -53,7 +53,7 @@
         configfile = open(self.filename, 'w')
         configfile.close()
 
-        config = Configuration(self.filename)
+        config = Configuration(self.filename, None)
         config.set('a', 'option', 'x')
         self.assertEquals('x', config.get('a', 'option'))
         config.save()
@@ -69,7 +69,7 @@
                                '[b]\n', 'option = y\n'])
         configfile.close()
 
-        config = Configuration(self.filename)
+        config = Configuration(self.filename, None)
         self.assertEquals(['a', 'b'], config.sections())
 
     def test_options(self):
@@ -78,7 +78,7 @@
                                '[b]\n', 'option = y\n'])
         configfile.close()
 
-        config = Configuration(self.filename)
+        config = Configuration(self.filename, None)
         self.assertEquals(('option', 'x'), iter(config.options('a')).next())
         self.assertEquals(('option', 'y'), iter(config.options('b')).next())
         self.assertRaises(StopIteration, iter(config.options('c')).next)
@@ -88,7 +88,7 @@
         configfile.writelines(['[a]\n', 'option = x\n', '\n'])
         configfile.close()
 
-        config = Configuration(self.filename)
+        config = Configuration(self.filename, None)
         self.assertEquals('x', config.get('a', 'option'))
         time.sleep(1) # needed because of low mtime granularity
 
Index: trac/test.py
===================================================================
--- trac/test.py	(revision 2326)
+++ trac/test.py	(working copy)
@@ -125,7 +125,7 @@
         self.db = InMemoryDatabase()
 
         from trac.config import Configuration
-        self.config = Configuration(None)
+        self.config = Configuration(None, None)
 
         from trac.log import logger_factory
         self.log = logger_factory('test')

