Projects
Wiki     Timeline     Roadmap     Browse Source     View Tickets     New Ticket     Search

Ticket #136: config.py

File config.py, 11.7 KB (added by tylerkeating@…, 3 years ago)
Line 
1##
2# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16# DRI: David Reid, dreid@apple.com
17##
18
19import os
20import copy
21
22from twisted.python import log
23
24from twistedcaldav.py.plistlib import readPlist
25
26defaultConfigFile = "/etc/caldavd/caldavd.plist"
27
28serviceDefaultParams = {
29    "twistedcaldav.directory.xmlfile.XMLDirectoryService": {
30        "xmlFile": "/etc/caldavd/accounts.xml",
31    },
32    "twistedcaldav.directory.appleopendirectory.OpenDirectoryService": {
33        "node": "/Search",
34        "requireComputerRecord": True,
35    },
36}
37
38defaultConfig = {
39    #
40    # Public network address information
41    #
42    #    This is the server's public network address, which is provided to
43    #    clients in URLs and the like.  It may or may not be the network
44    #    address that the server is listening to directly, though it is by
45    #    default.  For example, it may be the address of a load balancer or
46    #    proxy which forwards connections to the server.
47    #
48    "ServerHostName": "localhost", # Network host name.
49    "HTTPPort": -1,                # HTTP port (None to disable HTTP)
50    "SSLPort" : -1,                # SSL port (None to disable HTTPS)
51
52    # Note: we'd use None above, but that confuses the command-line parser.
53
54    #
55    # Network address configuration information
56    #
57    #    This configures the actual network address that the server binds to.
58    #
59    "BindAddresses": [],   # List of IP addresses to bind to [empty = all]
60    "BindHTTPPorts": [],   # List of port numbers to bind to for HTTP [empty = same as "Port"]
61    "BindSSLPorts" : [],   # List of port numbers to bind to for SSL [empty = same as "SSLPort"]
62
63    #
64    # Data store
65    #
66    "DocumentRoot": "/Library/CalendarServer/Documents",
67    "UserQuota"            : 104857600, # User quota (in bytes)
68    "MaximumAttachmentSize":   1048576, # Attachment size limit (in bytes)
69
70    #
71    # Directory service
72    #
73    #    A directory service provides information about principals (eg.
74    #    users, groups, locations and resources) to the server.
75    #
76    "DirectoryService": {
77        "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService",
78        "params": serviceDefaultParams["twistedcaldav.directory.xmlfile.XMLDirectoryService"],
79    },
80
81    #
82    # Special principals
83    #
84    "AdminPrincipals": [],                       # Principals with "DAV:all" access (relative URLs)
85    "SudoersFile": "/etc/caldavd/sudoers.plist", # Principals that can pose as other principals
86    "EnableProxyPrincipals": True,               # Create "proxy access" principals
87
88    #
89    # Authentication
90    #
91    "Authentication": {
92        "Basic"   : { "Enabled": False },   # Clear text; best avoided
93        "Digest"  : {                       # Digest challenge/response
94            "Enabled": True,
95            "Algorithm": "md5",
96            "Qop": "",
97            "Secret": "",
98        },
99        "Kerberos": {                       # Kerberos/SPNEGO
100            "Enabled": False,
101            "ServicePrincipal": ''
102        },
103    },
104
105    #
106    # Logging
107    #
108    "Verbose": False,
109    "AccessLogFile"  : "/var/log/caldavd/access.log",                   # Apache-style access log
110    "ErrorLogFile"   : "/var/log/caldavd/error.log",                    # Server activity log
111    "ServerStatsFile": "/Library/CalendarServer/Documents/stats.plist",
112    "PIDFile"        : "/var/run/caldavd.pid",
113    "RotateAccessLog": False,
114
115    #
116    # SSL/TLS
117    #
118    "SSLCertificate": "/etc/certificates/Default.crt", # Public key
119    "SSLPrivateKey": "/etc/certificates/Default.key",  # Private key
120
121    #
122    # Process management
123    #
124
125    # Username and Groupname to drop privileges to, if empty privileges will
126    # not be dropped.
127
128    "UserName": "",
129    "GroupName": "",
130    "ProcessType": "Combined",
131    "MultiProcess": {
132        "ProcessCount": 0,
133        "LoadBalancer": {
134            "Enabled": True,
135            "Scheduler": "LeastConnections",
136        },
137    },
138
139    #
140    # Service ACLs
141    #
142    "EnableSACLs": False,
143
144    #
145    # Non-standard CalDAV extensions
146    #
147    "EnableDropBox"      : False, # Calendar Drop Box
148    "EnableNotifications": False, # Drop Box Notifications
149
150    #
151    # Implementation details
152    #
153    #    The following are specific to how the server is built, and useful
154    #    for development, but shouldn't be needed by users.
155    #
156
157    # Twisted
158    "Twisted": {
159        "twistd": "/usr/share/caldavd/bin/twistd",
160    },
161
162    # Python Director
163    "PythonDirector": {
164        "pydir": "/usr/share/caldavd/bin/pydir.py",
165        "ConfigFile": "/etc/caldavd/pydir.xml",
166        "ControlSocket": "/var/run/caldavd-pydir.sock",
167    },
168
169    # Umask
170    "umask": 0027,
171
172    # A unix socket used for communication between the child and master
173    # processes.
174    "ControlSocket": "/var/run/caldavd.sock",
175   
176    # A secret key (SHA-1 hash of random string) that is used for internal
177    # crypto operations and shared by multiple server processes
178    "SharedSecret": "",
179}
180
181class Config (object):
182    def __init__(self, defaults):
183        self.setDefaults(defaults)
184        self._data = copy.deepcopy(self._defaults)
185        self._configFile = None
186
187    def __str__(self):
188        return str(self._data)
189
190    def update(self, items):
191        dsType = items.get("DirectoryService", {}).get("type", None)
192        if dsType is None:
193            dsType = self._data["DirectoryService"]["type"]
194        else:
195            if dsType == self._data["DirectoryService"]["type"]:
196                oldParams = self._data["DirectoryService"]["params"]
197                newParams = items["DirectoryService"].get("params", {})
198                _mergeData(oldParams, newParams)
199            else:
200                if dsType in serviceDefaultParams:
201                    self._data["DirectoryService"]["params"] = copy.deepcopy(serviceDefaultParams[dsType])
202                   
203                    for param in items.get("DirectoryService", {}).get("params", {}):
204                        if param not in serviceDefaultParams[dsType]:
205                            raise ConfigurationError("Parameter %s is not supported by service %s" % (param, dsType))
206                   
207                    for param in tuple(self._data["DirectoryService"]["params"]):
208                        if param not in serviceDefaultParams[self._data["DirectoryService"]["type"]]:
209                            del self._data["DirectoryService"]["params"][param]
210                else:
211                    self._data["DirectoryService"]["params"] = {}
212
213        _mergeData(self._data, items)
214        self.updateServerCapabilities()
215
216    def updateServerCapabilities(self):
217        """
218        Change server capabilities based on the current config parameters.
219        Here are the "features" in the config that need special treatment:
220       
221        EnableDropBox
222        EnableNotifications
223        """
224       
225        from twistedcaldav.resource import CalendarPrincipalResource
226        CalendarPrincipalResource.enableDropBox(self.EnableDropBox)
227        CalendarPrincipalResource.enableNotifications(self.EnableNotifications)
228
229    def updateDefaults(self, items):
230        _mergeData(self._defaults, items)
231        self.update(items)
232
233    def setDefaults(self, defaults):
234        self._defaults = copy.deepcopy(defaults)
235
236    def __setattr__(self, attr, value):
237        if '_data' in self.__dict__ and attr in self.__dict__['_data']:
238            self._data[attr] = value
239        else:
240            self.__dict__[attr] = value
241
242    def __getattr__(self, attr):
243        if attr in self._data:
244            return self._data[attr]
245
246        raise AttributeError(attr)
247
248    def reload(self):
249        self._data = copy.deepcopy(self._defaults)
250        self.loadConfig(self._configFile)
251
252    def loadConfig(self, configFile):
253        self._configFile = configFile
254
255        if configFile and os.path.exists(configFile):
256            configDict = readPlist(configFile)
257            configDict = _cleanup(configDict)
258            self.update(configDict)
259
260def _mergeData(oldData, newData):
261    for key, value in newData.iteritems():
262        if isinstance(value, (dict,)):
263            if key in oldData:
264                assert isinstance(oldData[key], (dict,))
265            else:
266                oldData[key] = {}
267            _mergeData(oldData[key], value)
268        else:
269            oldData[key] = value
270
271def _cleanup(configDict):
272    cleanDict = copy.deepcopy(configDict)
273
274    def deprecated(oldKey, newKey):
275        log.err("Configuration option %r is deprecated in favor of %r." % (oldKey, newKey))
276
277    def renamed(oldKey, newKey):
278        deprecated(oldKey, newKey)
279        cleanDict[newKey] = configDict[oldKey]
280        del cleanDict[oldKey]
281
282    renamedOptions = {
283        "BindAddress"                : "BindAddresses",
284        "ServerLogFile"              : "AccessLogFile",
285        "MaximumAttachmentSizeBytes" : "MaximumAttachmentSize",
286        "UserQuotaBytes"             : "UserQuota",
287        "DropBoxEnabled"             : "EnableDropBox",
288        "NotificationsEnabled"       : "EnableNotifications",
289        "CalendarUserProxyEnabled"   : "EnableProxyPrincipals",
290        "SACLEnable"                 : "EnableSACLs",
291        "ServerType"                 : "ProcessType",
292    }
293
294    for key in configDict:
295        if key in defaultConfig:
296            continue
297
298        if key == "SSLOnly":
299            deprecated(key, "HTTPPort")
300            if configDict["SSLOnly"]:
301                cleanDict["HTTPPort"] = None
302            del cleanDict["SSLOnly"]
303
304        elif key == "SSLEnable":
305            deprecated(key, "SSLPort")
306            if not configDict["SSLEnable"]:
307                cleanDict["SSLPort"] = None
308            del cleanDict["SSLEnable"]
309
310        elif key == "Port":
311            deprecated(key, "HTTPPort")
312            if not configDict.get("SSLOnly", False):
313                cleanDict["HTTPPort"] = cleanDict["Port"]
314            del cleanDict["Port"]
315
316        elif key == "twistdLocation":
317            deprecated(key, "Twisted -> twistd")
318            if "Twisted" not in cleanDict:
319                cleanDict["Twisted"] = {}
320            cleanDict["Twisted"]["twistd"] = cleanDict["twistdLocation"]
321            del cleanDict["twistdLocation"]
322
323        elif key == "pydirLocation":
324            deprecated(key, "PythonDirector -> pydir")
325            if "PythonDirector" not in cleanDict:
326                cleanDict["PythonDirector"] = {}
327            cleanDict["PythonDirector"]["pydir"] = cleanDict["pydirLocation"]
328            del cleanDict["pydirLocation"]
329
330        elif key == "pydirConfig":
331            deprecated(key, "PythonDirector -> pydir")
332            if "PythonDirector" not in cleanDict:
333                cleanDict["PythonDirector"] = {}
334            cleanDict["PythonDirector"]["ConfigFile"] = cleanDict["pydirConfig"]
335            del cleanDict["pydirConfig"]
336
337        elif key in renamedOptions:
338            renamed(key, renamedOptions[key])
339
340        elif key == "RunStandalone":
341            log.err("Ignoring obsolete configuration option: %s" % (key,))
342            del cleanDict[key]
343
344        else:
345            log.err("Ignoring unknown configuration option: %s" % (key,))
346            del cleanDict[key]
347
348    return cleanDict
349
350class ConfigurationError (RuntimeError):
351    """
352    Invalid server configuration.
353    """
354
355config = Config(defaultConfig)
356
357def parseConfig(configFile):
358    config.loadConfig(configFile)