1
# -*- encoding: utf-8 -*-
4
# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
7
# WARNING: This program as such is intended to be used by professional
8
# programmers who take the whole responsability of assessing all potential
9
# consequences resulting from its eventual inadequacies and bugs
10
# End users who are looking for a ready-to-use solution with commercial
11
# garantees and support are strongly adviced to contract a Free Software
14
# This program is Free Software; you can redistribute it and/or
15
# modify it under the terms of the GNU General Public License
16
# as published by the Free Software Foundation; either version 2
17
# of the License, or (at your option) any later version.
19
# This program is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
# GNU General Public License for more details.
24
# You should have received a copy of the GNU General Public License
25
# along with this program; if not, write to the Free Software
26
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27
###############################################################################
29
""" This file contains instance of the http server.
33
from websrv_lib import *
41
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
49
from ssl import SSLError
51
class SSLError(Exception): pass
53
class ThreadedHTTPServer(ConnThreadingMixIn, SimpleXMLRPCDispatcher, HTTPServer):
54
""" A threaded httpd server, with all the necessary functionality for us.
56
It also inherits the xml-rpc dispatcher, so that some xml-rpc functions
57
will be available to the request handler
61
allow_reuse_address = 1
62
_send_traceback_header = False
65
def __init__(self, addr, requestHandler,
66
logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
67
self.logRequests = logRequests
69
SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
70
HTTPServer.__init__(self, addr, requestHandler)
72
# [Bug #1222790] If possible, set close-on-exec flag; if a
73
# method spawns a subprocess, the subprocess shouldn't have
74
# the listening socket open.
75
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
76
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
77
flags |= fcntl.FD_CLOEXEC
78
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
80
def handle_error(self, request, client_address):
81
""" Override the error handler
84
netsvc.Logger().notifyChannel("init", netsvc.LOG_ERROR,"Server error in request from %s:\n%s" %
85
(client_address,traceback.format_exc()))
87
class MultiHandler2(MultiHTTPHandler):
88
def log_message(self, format, *args):
89
netsvc.Logger().notifyChannel('http',netsvc.LOG_DEBUG,format % args)
91
def log_error(self, format, *args):
92
netsvc.Logger().notifyChannel('http',netsvc.LOG_ERROR,format % args)
95
class SecureMultiHandler2(SecureMultiHTTPHandler):
96
def log_message(self, format, *args):
97
netsvc.Logger().notifyChannel('https',netsvc.LOG_DEBUG,format % args)
99
def getcert_fnames(self):
101
fcert = tc.get_misc('httpsd','sslcert', 'ssl/server.cert')
102
fkey = tc.get_misc('httpsd','sslkey', 'ssl/server.key')
105
def log_message(self, format, *args):
106
netsvc.Logger().notifyChannel('http',netsvc.LOG_DEBUG,format % args)
108
def log_error(self, format, *args):
109
netsvc.Logger().notifyChannel('http',netsvc.LOG_ERROR,format % args)
111
class HttpDaemon(threading.Thread, netsvc.Server):
112
def __init__(self, interface, port):
113
threading.Thread.__init__(self)
114
netsvc.Server.__init__(self)
116
self.__interface = interface
119
self.server = ThreadedHTTPServer((interface, port), MultiHandler2)
120
self.server.vdirs = []
121
self.server.logRequests = True
122
netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO,
123
"starting HTTP service at %s port %d" % (interface or '0.0.0.0', port,))
125
netsvc.Logger().notifyChannel('httpd', netsvc.LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,))
129
def attach(self, path, gw):
135
self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 )
136
self.server.socket.close()
139
#self.server.register_introspection_functions()
143
self.server.handle_request()
146
class HttpSDaemon(threading.Thread, netsvc.Server):
147
def __init__(self, interface, port):
148
threading.Thread.__init__(self)
149
netsvc.Server.__init__(self)
151
self.__interface = interface
154
self.server = ThreadedHTTPServer((interface, port), SecureMultiHandler2)
155
self.server.vdirs = []
156
self.server.logRequests = True
157
netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO,
158
"starting HTTPS service at %s port %d" % (interface or '0.0.0.0', port,))
160
netsvc.Logger().notifyChannel('httpd-ssl', netsvc.LOG_CRITICAL, "Can not load the certificate and/or the private key files")
163
netsvc.Logger().notifyChannel('httpd-ssl', netsvc.LOG_CRITICAL, "Error occur when starting the server daemon: %s" % (e,))
166
def attach(self, path, gw):
172
self.server.socket.shutdown( hasattr(socket, 'SHUT_RDWR') and socket.SHUT_RDWR or 2 )
173
self.server.socket.close()
176
#self.server.register_introspection_functions()
180
self.server.handle_request()
188
if tools.config.get_misc('httpd','enable', True):
189
httpd = HttpDaemon(tools.config.get_misc('httpd','interface', ''), \
190
tools.config.get_misc('httpd','port', 8069))
192
if tools.config.get_misc('httpsd','enable', False):
193
httpsd = HttpSDaemon(tools.config.get_misc('httpsd','interface', ''), \
194
tools.config.get_misc('httpsd','port', 8071))
196
def reg_http_service(hts, secure_only = False):
197
""" Register some handler to httpd.
198
hts must be an HTTPDir
201
if not isinstance(hts, HTTPDir):
202
raise Exception("Wrong class for http service")
204
if httpd and not secure_only:
205
httpd.server.vdirs.append(hts)
208
httpsd.server.vdirs.append(hts)
210
if (not httpd) and (not httpsd):
211
netsvc.Logger().notifyChannel('httpd',netsvc.LOG_WARNING,"No httpd available to register service %s" % hts.path)
214
import SimpleXMLRPCServer
215
class XMLRPCRequestHandler(netsvc.OpenERPDispatcher,FixSendError,SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
217
protocol_version = 'HTTP/1.1'
218
def _dispatch(self, method, params):
220
service_name = self.path.split("/")[-1]
221
return self.dispatch(service_name, method, params)
222
except netsvc.OpenERPDispatcherException, e:
223
raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback)
225
def log_message(self, format, *args):
226
netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_DEBUG_RPC,format % args)
235
self.connection = dummyconn()
236
if not len(XMLRPCRequestHandler.rpc_paths):
237
XMLRPCRequestHandler.rpc_paths = map(lambda s: '/%s' % s, netsvc.ExportService._services.keys())
242
if not tools.config.get_misc('xmlrpc','enable', True):
244
reg_http_service(HTTPDir('/xmlrpc/',XMLRPCRequestHandler))
245
# Example of http file serving:
246
# reg_http_service(HTTPDir('/test/',HTTPHandler))
247
netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO,
248
"Registered XML-RPC over HTTP")
251
class OerpAuthProxy(AuthProxy):
252
""" Require basic authentication..
254
This is a copy of the BasicAuthProxy, which however checks/caches the db
257
def __init__(self,provider):
258
AuthProxy.__init__(self,provider)
261
self.last_auth = None
263
def checkRequest(self,handler,path = '/'):
266
auth_str = handler.headers.get('Authorization',False)
268
db = handler.get_db_from_path(path)
271
if path.startswith('/'):
278
self.provider.log("Wrong path: %s, failing auth" %path)
279
raise AuthRejectedExc("Authorization failed. Wrong sub-path.")
281
if auth_str and auth_str.startswith('Basic '):
282
auth_str=auth_str[len('Basic '):]
283
(user,passwd) = base64.decodestring(auth_str).split(':')
284
self.provider.log("Found user=\"%s\", passwd=\"***\" for db=\"%s\"" %(user,db))
285
acd = self.provider.authenticate(db,user,passwd,handler.client_address)
287
self.auth_creds[db] = acd
290
if self.auth_tries > 5:
291
self.provider.log("Failing authorization after 5 requests w/o password")
292
raise AuthRejectedExc("Authorization failed.")
294
raise AuthRequiredExc(atype = 'Basic', realm=self.provider.realm)
297
class OpenERPAuthProvider(AuthProvider):
298
def __init__(self,realm = 'OpenERP User'):
301
def setupAuth(self, multi, handler):
302
if not multi.sec_realms.has_key(self.realm):
303
multi.sec_realms[self.realm] = OerpAuthProxy(self)
304
handler.auth_proxy = multi.sec_realms[self.realm]
306
def authenticate(self, db, user, passwd, client_address):
308
uid = security.login(db,user,passwd)
311
return (user, passwd, db, uid)
313
netsvc.Logger().notifyChannel("auth",netsvc.LOG_DEBUG,"Fail auth:"+ str(e))
317
netsvc.Logger().notifyChannel("auth",netsvc.LOG_INFO,msg)