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 BaseHttpDaemon(threading.Thread, netsvc.Server):
112 def __init__(self, interface, port, handler):
113 threading.Thread.__init__(self)
114 netsvc.Server.__init__(self)
116 self.__interface = interface
119 self.server = ThreadedHTTPServer((interface, port), handler)
120 self.server.vdirs = []
121 self.server.logRequests = True
122 netsvc.Logger().notifyChannel(
123 "web-services", netsvc.LOG_INFO,
124 "starting HTTPS service at %s port %d" %
125 (interface or '0.0.0.0', port,))
127 netsvc.Logger().notifyChannel(
128 'httpd', netsvc.LOG_CRITICAL,
129 "Error occur when starting the server daemon: %s" % (e,))
132 def attach(self, path, gw):
139 self.server.socket.shutdown(
140 getattr(socket, 'SHUT_RDWR', 2))
141 except socket.error, e:
142 if e.errno != 57: raise
143 # OSX, socket shutdowns both sides if any side closes it
144 # causing an error 57 'Socket is not connected' on shutdown
145 # of the other side (or something), see
146 # http://bugs.python.org/issue4397
147 netsvc.Logger().notifyChannel(
148 'server', netsvc.LOG_DEBUG,
149 '"%s" when shutting down server socket, '
150 'this is normal under OS X'%e)
151 self.server.socket.close()
156 self.server.handle_request()
159 class HttpDaemon(BaseHttpDaemon):
160 def __init__(self, interface, port):
161 super(HttpDaemon, self).__init__(interface, port,
162 handler=MultiHandler2)
164 class HttpSDaemon(BaseHttpDaemon):
165 def __init__(self, interface, port):
167 super(HttpSDaemon, self).__init__(interface, port,
168 handler=SecureMultiHandler2)
170 netsvc.Logger().notifyChannel(
171 'httpd-ssl', netsvc.LOG_CRITICAL,
172 "Can not load the certificate and/or the private key files")
180 if tools.config.get_misc('httpd','enable', True):
181 httpd = HttpDaemon(tools.config.get_misc('httpd','interface', ''), \
182 int(tools.config.get_misc('httpd','port', tools.config.get('port',8069))))
184 if tools.config.get_misc('httpsd','enable', False):
185 httpsd = HttpSDaemon(tools.config.get_misc('httpsd','interface', ''), \
186 int(tools.config.get_misc('httpsd','port', 8071)))
188 def reg_http_service(hts, secure_only = False):
189 """ Register some handler to httpd.
190 hts must be an HTTPDir
193 if not isinstance(hts, HTTPDir):
194 raise Exception("Wrong class for http service")
196 if httpd and not secure_only:
197 httpd.server.vdirs.append(hts)
200 httpsd.server.vdirs.append(hts)
202 if (not httpd) and (not httpsd):
203 netsvc.Logger().notifyChannel('httpd',netsvc.LOG_WARNING,"No httpd available to register service %s" % hts.path)
206 import SimpleXMLRPCServer
207 class XMLRPCRequestHandler(netsvc.OpenERPDispatcher,FixSendError,SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
209 protocol_version = 'HTTP/1.1'
210 def _dispatch(self, method, params):
212 service_name = self.path.split("/")[-1]
213 return self.dispatch(service_name, method, params)
214 except netsvc.OpenERPDispatcherException, e:
215 raise xmlrpclib.Fault(tools.exception_to_unicode(e.exception), e.traceback)
217 def log_message(self, format, *args):
218 netsvc.Logger().notifyChannel('xmlrpc',netsvc.LOG_DEBUG_RPC,format % args)
227 self.connection = dummyconn()
228 if not len(XMLRPCRequestHandler.rpc_paths):
229 XMLRPCRequestHandler.rpc_paths = map(lambda s: '/%s' % s, netsvc.ExportService._services.keys())
234 if not tools.config.get_misc('xmlrpc','enable', True):
236 reg_http_service(HTTPDir('/xmlrpc/',XMLRPCRequestHandler))
237 # Example of http file serving:
238 # reg_http_service(HTTPDir('/test/',HTTPHandler))
239 netsvc.Logger().notifyChannel("web-services", netsvc.LOG_INFO,
240 "Registered XML-RPC over HTTP")
243 class OerpAuthProxy(AuthProxy):
244 """ Require basic authentication..
246 This is a copy of the BasicAuthProxy, which however checks/caches the db
249 def __init__(self,provider):
250 AuthProxy.__init__(self,provider)
253 self.last_auth = None
255 def checkRequest(self,handler,path = '/'):
258 auth_str = handler.headers.get('Authorization',False)
260 db = handler.get_db_from_path(path)
263 if path.startswith('/'):
270 self.provider.log("Wrong path: %s, failing auth" %path)
271 raise AuthRejectedExc("Authorization failed. Wrong sub-path.")
273 if auth_str and auth_str.startswith('Basic '):
274 auth_str=auth_str[len('Basic '):]
275 (user,passwd) = base64.decodestring(auth_str).split(':')
276 self.provider.log("Found user=\"%s\", passwd=\"***\" for db=\"%s\"" %(user,db))
277 acd = self.provider.authenticate(db,user,passwd,handler.client_address)
279 self.auth_creds[db] = acd
282 if self.auth_tries > 5:
283 self.provider.log("Failing authorization after 5 requests w/o password")
284 raise AuthRejectedExc("Authorization failed.")
286 raise AuthRequiredExc(atype = 'Basic', realm=self.provider.realm)
289 class OpenERPAuthProvider(AuthProvider):
290 def __init__(self,realm = 'OpenERP User'):
293 def setupAuth(self, multi, handler):
294 if not multi.sec_realms.has_key(self.realm):
295 multi.sec_realms[self.realm] = OerpAuthProxy(self)
296 handler.auth_proxy = multi.sec_realms[self.realm]
298 def authenticate(self, db, user, passwd, client_address):
300 uid = security.login(db,user,passwd)
303 return (user, passwd, db, uid)
305 netsvc.Logger().notifyChannel("auth",netsvc.LOG_DEBUG,"Fail auth:"+ str(e))
309 netsvc.Logger().notifyChannel("auth",netsvc.LOG_INFO,msg)