diff --git a/xcap/__init__.py b/xcap/__init__.py index 963de3d..06de181 100644 --- a/xcap/__init__.py +++ b/xcap/__init__.py @@ -1,6 +1,4 @@ -"""XCAP package""" - -__version__ = "2.6.1" -__cfgfile__ = "config.ini" +__version__ = '2.6.1' +__cfgfile__ = 'config.ini' diff --git a/xcap/server.py b/xcap/server.py index a6abeed..e8c9920 100644 --- a/xcap/server.py +++ b/xcap/server.py @@ -1,190 +1,192 @@ """HTTP handling for the XCAP server""" from __future__ import absolute_import import resource as _resource import sys from application.configuration.datatypes import IPAddress, NetworkRangeList from application.configuration import ConfigSection, ConfigSetting from application import log from twisted.internet import reactor from twisted.web2 import channel, resource, http, responsecode, server from twisted.cred.portal import Portal from twisted.web2.auth import basic from xcap.tweaks import tweak_DigestCredentialFactory import xcap from xcap import authentication from xcap.datatypes import XCAPRootURI from xcap.appusage import getApplicationForURI, Backend from xcap.resource import XCAPDocument, XCAPElement, XCAPAttribute, XCAPNamespaceBinding from xcap.logutil import web_logger from xcap.tls import Certificate, PrivateKey from xcap.xpath import AttributeSelector, NamespaceSelector server.VERSION = "OpenXCAP/%s" % xcap.__version__ + class AuthenticationConfig(ConfigSection): __cfgfile__ = xcap.__cfgfile__ __section__ = 'Authentication' type = 'digest' cleartext_passwords = True default_realm = ConfigSetting(type=str, value=None) trusted_peers = ConfigSetting(type=NetworkRangeList, value=NetworkRangeList('none')) + class ServerConfig(ConfigSection): __cfgfile__ = xcap.__cfgfile__ __section__ = 'Server' address = ConfigSetting(type=IPAddress, value='0.0.0.0') root = ConfigSetting(type=XCAPRootURI, value=None) backend = ConfigSetting(type=Backend, value=None) + class TLSConfig(ConfigSection): __cfgfile__ = xcap.__cfgfile__ __section__ = 'TLS' certificate = ConfigSetting(type=Certificate, value=None) private_key = ConfigSetting(type=PrivateKey, value=None) + if ServerConfig.root is None: log.critical('The XCAP root URI is not defined') sys.exit(1) if ServerConfig.backend is None: log.critical('OpenXCAP needs a backend to be specified in order to run') sys.exit(1) # Increase the system limit for the maximum number of open file descriptors try: _resource.setrlimit(_resource.RLIMIT_NOFILE, (99999, 99999)) except ValueError: log.warning('Could not raise open file descriptor limit') class XCAPRoot(resource.Resource, resource.LeafResource): addSlash = True def allowedMethods(self): # not used , but methods were already checked by XCAPAuthResource return ('GET', 'PUT', 'DELETE') def resourceForURI(self, xcap_uri): application = getApplicationForURI(xcap_uri) if not xcap_uri.node_selector: return XCAPDocument(xcap_uri, application) else: terminal_selector = xcap_uri.node_selector.terminal_selector if isinstance(terminal_selector, AttributeSelector): return XCAPAttribute(xcap_uri, application) elif isinstance(terminal_selector, NamespaceSelector): return XCAPNamespaceBinding(xcap_uri, application) else: return XCAPElement(xcap_uri, application) def renderHTTP(self, request): application = getApplicationForURI(request.xcap_uri) if not application: return http.Response(responsecode.NOT_FOUND, stream="Application not supported") resource = self.resourceForURI(request.xcap_uri) return resource.renderHTTP(request) class Request(server.Request): def writeResponse(self, response): web_logger.log_access(request=self, response=response) return server.Request.writeResponse(self, response) class HTTPChannelRequest(channel.http.HTTPChannelRequest): _base = channel.http.HTTPChannelRequest def gotInitialLine(self, line): self._initial_line = line return self._base.gotInitialLine(self, line) def createRequest(self): self._base.createRequest(self) self.request._initial_line = self._initial_line class HTTPChannel(channel.http.HTTPChannel): chanRequestFactory = HTTPChannelRequest inputTimeOut = 30 def __init__(self): channel.http.HTTPChannel.__init__(self) # if connection wasn't completed for 30 seconds, terminate it, # this avoids having lingering TCP connections which don't complete # the TLS handshake self.setTimeout(30) def timeoutConnection(self): if self.transport: log.info('Timing out client: {}'.format(self.transport.getPeer())) channel.http.HTTPChannel.timeoutConnection(self) class HTTPFactory(channel.HTTPFactory): noisy = False protocol = HTTPChannel class XCAPSite(server.Site): - def __call__(self, *args, **kwargs): return Request(site=self, *args, **kwargs) class XCAPServer(object): - def __init__(self): portal = Portal(authentication.XCAPAuthRealm()) if AuthenticationConfig.cleartext_passwords: http_checker = ServerConfig.backend.PlainPasswordChecker() else: http_checker = ServerConfig.backend.HashPasswordChecker() portal.registerChecker(http_checker) trusted_peers = AuthenticationConfig.trusted_peers portal.registerChecker(authentication.TrustedPeerChecker(trusted_peers)) portal.registerChecker(authentication.PublicGetApplicationChecker()) auth_type = AuthenticationConfig.type if auth_type == 'basic': credential_factory = basic.BasicCredentialFactory(auth_type) elif auth_type == 'digest': credential_factory = tweak_DigestCredentialFactory('MD5', auth_type) else: - raise ValueError("Invalid authentication type: '%s'. Please check the configuration." % auth_type) + raise ValueError('Invalid authentication type: %r. Please check the configuration.' % auth_type) root = authentication.XCAPAuthResource(XCAPRoot(), (credential_factory,), portal, (authentication.IAuthUser,)) self.site = XCAPSite(root) def _start_https(self, reactor): from gnutls.interfaces.twisted import X509Credentials from gnutls.connection import TLSContext, TLSContextServerOptions cert, pKey = TLSConfig.certificate, TLSConfig.private_key if cert is None or pKey is None: log.critical('The TLS certificate/key could not be loaded') sys.exit(1) credentials = X509Credentials(cert, pKey) tls_context = TLSContext(credentials, server_options=TLSContextServerOptions(certificate_request=None)) reactor.listenTLS(ServerConfig.root.port, HTTPFactory(self.site), tls_context, interface=ServerConfig.address) log.info('TLS started') def start(self): log.info('Listening on: %s:%d' % (ServerConfig.address, ServerConfig.root.port)) log.info('XCAP root: %s' % ServerConfig.root) if ServerConfig.root.startswith('https'): self._start_https(reactor) else: reactor.listenTCP(ServerConfig.root.port, HTTPFactory(self.site), interface=ServerConfig.address) reactor.run(installSignalHandlers=ServerConfig.backend.installSignalHandlers) diff --git a/xcap/tls.py b/xcap/tls.py index 77d42a6..7503129 100644 --- a/xcap/tls.py +++ b/xcap/tls.py @@ -1,54 +1,54 @@ """TLS helper classes""" __all__ = ['Certificate', 'PrivateKey'] -from gnutls.crypto import X509Certificate, X509PrivateKey - from application import log from application.process import process +from gnutls.crypto import X509Certificate, X509PrivateKey -class _FileError(Exception): pass +class _FileError(Exception): + pass -def file_content(file): +def file_content(filename): path = process.configuration.file(filename) if path is None: - raise _FileError("File '%s' does not exist" % file) + raise _FileError('File %r does not exist' % filename) try: f = open(path, 'rt') except Exception: - raise _FileError("File '%s' could not be open" % file) + raise _FileError('File %r could not be open' % filename) try: return f.read() finally: f.close() class Certificate(object): """Configuration data type. Used to create a gnutls.crypto.X509Certificate object from a file given in the configuration file.""" def __new__(cls, value): if isinstance(value, str): try: return X509Certificate(file_content(value)) except Exception, e: log.warning('Certificate file %r could not be loaded: %s' % (value, e)) return None else: raise TypeError('value should be a string') class PrivateKey(object): """Configuration data type. Used to create a gnutls.crypto.X509PrivateKey object from a file given in the configuration file.""" def __new__(cls, value): if isinstance(value, str): try: return X509PrivateKey(file_content(value)) except Exception, e: log.warning('Private key file %r could not be loaded: %s' % (value, e)) return None else: raise TypeError('value should be a string')