View Issue Details

IDProjectCategoryView StatusLast Update
0001895SOGo Native Outlook Compatibility (obsolete)public2012-08-03 16:26
Reporterludovic Assigned Tojraby 
PriorityhighSeverityminorReproducibilityhave not tried
Status closedResolutionfixed 
Target Version2.0.0Fixed in Version2.0.0 
Summary0001895: /etc/httpd/conf.d/ocsmanager.conf not provided on RHEL 5/6
Description

Title says all.

TagsNo tags attached.

Activities

ludovic

ludovic

2012-07-19 17:13

administrator   ~0004181

NTLMAUTHHANDLER_WORKDIR = /var/lib/ntlmauthhandler

is also missing in the file and /var/lib/ntlmauthhandler isn't created (and permissions need to be set correctly on that directory once created manually)

ludovic

ludovic

2012-08-02 20:31

administrator   ~0004247


Exception happened during processing of request from ('127.0.0.1', 45807)
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/paste/httpserver.py", line 1068, in process_request_in_thread
self.finish_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 323, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python2.7/SocketServer.py", line 638, in init
self.handle()
File "/usr/lib/python2.7/dist-packages/paste/httpserver.py", line 442, in handle
BaseHTTPRequestHandler.handle(self)
File "/usr/lib/python2.7/BaseHTTPServer.py", line 340, in handle
self.handle_one_request()
File "/usr/lib/python2.7/dist-packages/paste/httpserver.py", line 437, in handle_one_request
self.wsgi_execute()
File "/usr/lib/python2.7/dist-packages/paste/httpserver.py", line 287, in wsgi_execute
self.wsgi_start_response)
File "/usr/lib/python2.7/dist-packages/paste/cascade.py", line 130, in call
return self.apps[-1](environ, start_response)
File "/usr/lib/python2.7/dist-packages/paste/registry.py", line 379, in call
app_iter = self.application(environ, start_response)
File "/usr/lib/python2.7/dist-packages/openchange/web/auth/NTLMAuthHandler.py", line 529, in call
= self._read_environment(env)
File "/usr/lib/python2.7/dist-packages/openchange/web/auth/NTLMAuthHandler.py", line 597, in _read_environment
raise ValueError("'NTLMAUTHHANDLER_WORKDIR' is not set in"
ValueError: 'NTLMAUTHHANDLER_WORKDIR' is not set in the environment

ludovic

ludovic

2012-08-02 23:03

administrator   ~0004248

This might be due to this commit:

Revision: 4023
http://websvn.openchange.org/listing.php?repname=openchange&path=%2F&rev=4023&sc=1
Author: wsourdeau
Date: 2012-06-15 21:50:09 +0200 (Fri, 15 Jun 2012)
Log Message:

Made authentication handler configurable via server env

Modified Paths:

branches/sogo/python/openchange/web/auth/NTLMAuthHandler.py

Modified: branches/sogo/python/openchange/web/auth/NTLMAuthHandler.py

--- branches/sogo/python/openchange/web/auth/NTLMAuthHandler.py 2012-06-15 14:26:07 UTC (rev 4022)
+++ branches/sogo/python/openchange/web/auth/NTLMAuthHandler.py 2012-06-15 19:50:09 UTC (rev 4023)
@@ -43,21 +43,26 @@
from openchange.utils.packets import RPCAuth3OutPacket, RPCBindACKPacket, \
RPCBindOutPacket, RPCFaultPacket, RPCPacket, RPCPingOutPacket

-COOKIE_NAME = "ocs-ntlm-auth"
+
+# TODO: we should make use of port 135 to discover which port our service uses
SAMBA_PORT = 1024

+# those are left unconfigurable, at least for now
CLIENT_TIMEOUT = 60 * 5 # 5 minutes since last use
ACTIVITY_TIMEOUT = CLIENT_TIMEOUT # 5 minutes since any socket has been used

+

client-daemon protocol:

* server knows client?

client -> server "k" + sizeof(cookie) + cookie

server->client = 0 or 1 (binary)

+#

* ntlm transaction

client -> server "t" + sizeof(cookie) + cookie + sizeof(ntlm-payload) +

ntlm-payload

server -> client = 0 or 1 (binary) + sizeof(ntlm-payload) + ntlm-payload

+
def _safe_close(socket_obj):
try:
socket_obj.shutdown(SHUT_RDWR)
@@ -65,13 +70,14 @@
except:
pass

+
class _NTLMDaemon(object):
def init(self, samba_host, socket_filename):
self.socket_filename = socket_filename
self.samba_host = samba_host
self.client_data = {}

  • def run(self, lockf):
  • def run(self):
    if exists(self.socket_filename):
    unlink(self.socket_filename)
    child_pid = fork()
    @@ -203,7 +209,7 @@

     # close server socket and remove fs entry
     _safe_close(server_socket)
  • unlink(self.socket_filename)
  • unlink(self.socket_filename)

     # close client sockets
     [_safe_close(client_socket)
      for client_socket in client_sockets.itervalues()]

    @@ -215,7 +221,7 @@
    def _cleanup_record(client_id):
    record = self.client_data[client_id]
    if "server" in record:

  • print >> sys.stderr, "closing server socket"
  • print >> sys.stderr, "closing server socket"

             server = record["server"]
             _safe_close(server)
         del self.client_data[client_id]

    @@ -384,129 +390,13 @@
    return (response, ntlm_payload)

-class NTLMAuthHandler(object):

  • """

  • HTTP/1.0 NTLM authentication middleware

  • Parameters: application -- the application object that is called only upon

  • successful authentication.

  • """

  • def init(self, application, directory="/var/run/ntlmauthhandler", samba_host="localhost"):

  • self.application = application

  • self.directory = directory
    +class _NTLMAuthClient(object):

  • def init(self, work_dir, samba_host):

  • self.work_dir = work_dir
    self.samba_host = samba_host

  • self.ntlm_server = None

  • self.connection = None

  • def del(self):

  • if self.ntlm_server is not None:

  • _safe_close(self.ntlm_server)

  • @staticmethod

  • def _in_progress_response(start_response, ntlm_data=None, client_id=None):

  • status = "401 %s" % httplib.responses[401]

  • content = "More data needed..."

  • headers = [("Content-Type", "text/plain"),

  • ("Content-Length", "%d" % len(content))]

  • if ntlm_data is None:

  • www_auth_value = "NTLM"

  • else:

  • enc_ntlm_data = ntlm_data.encode("base64")

  • www_auth_value = ("NTLM %s"

  • % enc_ntlm_data.strip().replace("\n", ""))

  • if client_id is not None:

  • MUST occur when ntlm_data is None, can still occur otherwise

  • headers.append(("Set-Cookie", "%s=%s" % (COOKIE_NAME, client_id)))

  • headers.append(("WWW-Authenticate", www_auth_value))

  • start_response(status, headers)

  • return [content]

  • @staticmethod

  • def _get_cookies(env):

  • cookies = {}

  • if "HTTP_COOKIE" in env:

  • cookie_str = env["HTTP_COOKIE"]

  • for pair in cookie_str.split(";"):

  • (key, value) = pair.strip().split("=")

  • cookies[key] = value

  • return cookies

  • def _handle_negotiate(self, env, start_response):

  • client_id = str(uuid4())

  • auth = env["HTTP_AUTHORIZATION"]

  • ntlm_payload = auth[5:].decode("base64")

  • (success, ntlm_payload) = self._server_ntlm_transaction(client_id,

  • ntlm_payload)

  • if success:

  • response = self._in_progress_response(start_response,

  • ntlm_payload,

  • client_id)

  • else:

  • response = self._in_progress_response(start_response)

  • return response

  • def _handle_auth(self, client_id, env, start_response):

  • auth = env["HTTP_AUTHORIZATION"]

  • ntlm_payload = auth[5:].decode("base64")

  • (success, ntlm_payload) = self._server_ntlm_transaction(client_id,

  • ntlm_payload)

  • if success:

  • response = self.application(env, start_response)

  • else:

  • response = self._in_progress_response(start_response)

  • return response

  • def call(self, env, start_response):

  • TODO: validate authorization payload

  • old model that only works with mod_wsgi:

  • if "REMOTE_ADDR" in env and "REMOTE_PORT" in env:

  • client_id = "%(REMOTE_ADDR)s:%(REMOTE_PORT)s".format(env)

  • has_auth = "HTTP_AUTHORIZATION" in env

  • cookies = self._get_cookies(env)

  • if COOKIE_NAME in cookies:

  • client_id = cookies[COOKIE_NAME]

  • server_knows_client = self._server_knows_client(client_id)

  • print >> sys.stderr, \

  • "server knows client (pid: %d): %s" % (getpid(),

  • str(server_knows_client))

  • else:

  • server_knows_client = False

  • print >> sys.stderr, "client did not pass auth cookie"

  • if has_auth:

  • if server_knows_client:

  • stage 1, where the client has already received the challenge

  • from the server and is now sending an AUTH message

  • response = self._handle_auth(client_id, env, start_response)

  • else:

  • stage 0, where the cookie has not been set yet and where we

  • know the NTLM payload is a NEGOTIATE message

  • response = self._handle_negotiate(env, start_response)

  • else:

  • if server_knows_client:

  • authenticated, where no NTLM payload is provided anymore

  • response = self.application(env, start_response)

  • else:

  • this client has never been seen

  • response = self._in_progress_response(start_response, None)

  • return response

  • def _server_knows_client(self, client_id):

  • def server_knows_client(self, client_id):

    print >> sys.stderr, "server knows client? (%d)" % getpid()

     payload = "k%s%s" % (pack("<l", len(client_id)), client_id)
     self._send_to_server(payload)

    @@ -521,10 +411,7 @@

     return code != 0
  • def _server_ntlm_transaction(self, client_id="", ntlm_payload=""):

  • if self.ntlm_server is None:

  • self._setup_server_connection()

  • def ntlm_transaction(self, client_id="", ntlm_payload=""):

    print >> sys.stderr, "ntlm_transaction (%d)" % getpid()

     payload = ("t%s%s%s%s"

    @@ -550,28 +437,47 @@
    return (code != 0, ntlm_payload)

    def _send_to_server(self, payload):

  • print >>sys.stderr, "sending data (%d)" % getpid()

  • if self.connection is None:

  • self._make_connection()

  •  # sends a series of bytes to the server and make sure it receives them
     # by restarting it if needed
  • if self.ntlm_server is None:

  • self._setup_server_connection()

  • print >>sys.stderr, "sending data (%d)" % getpid()

     sent = False
     while not sent:
         try:
  • self.ntlm_server.sendall(payload)

  • self.connection.sendall(payload)
    sent = True
    except IOError:

    print >> sys.stderr, ("(send) reconnecting to server (%d)..."

             #                      % getpid())
             sleep(1)
  • self._setup_server_connection()

  • self._make_connection()

    print >>sys.stderr, "sent data (%d)" % getpid()

  • def _setup_server_connection(self):

  • def _recv_from_server(self, nbr_bytes):

  • print >>sys.stderr, "receiving data (%d)" % getpid()

  • receives a payload from the server and returns an empty string if

  • something went wrong, just as if the actual request failed

  • try:

  • payload = self.connection.recv(nbr_bytes, MSG_WAITALL)

  • if payload is None:

  • payload = ""

  • except IOError:

  • if the server has died, we must return an error anyway

  • payload = ""

  • print >>sys.stderr, "received data (%d)" % getpid()

  • return payload

  • def _make_connection(self):

    we create a lock in order to make sure that we would be the only

     # process or thread starting a daemon if needed
  • lock_filename = join(self.directory, "ntlm.lock")

  • lock_filename = join(self.work_dir, "ntlm-%s.lock" % self.samba_host)
    if not exists(lock_filename):
    lockf = open(lock_filename, "w+")
    lockf.close()
    @@ -580,41 +486,164 @@
    lock_fd = lockf.fileno()
    flock(lock_fd, LOCK_EX)

    print >> sys.stderr, "acquired lock (%d)" % getpid()

  • self._connect_to_daemon(lockf)

  • self.connection = self._connect_to_daemon(self.work_dir,

  • self.samba_host)
    flock(lock_fd, LOCK_UN)
    lockf.close()

  • print >> sys.stderr, "released lock (%d)" % getpid()

  • def _connect_to_daemon(self, lockf):

  • socket_filename = join(self.directory, "ntlm.sock")

  • ntlm_server = socket(AF_UNIX, SOCK_STREAM)

  • retry = True

  • is_parent = True

  • @staticmethod

  • def _connect_to_daemon(work_dir, samba_host):

  • socket_filename = join(work_dir, "ntlm-%s" % samba_host)

  • connection = socket(AF_UNIX, SOCK_STREAM)
    try:

  • ntlm_server.connect(socket_filename)

  • retry = False

  • connection.connect(socket_filename)
    except socket_error:

  • print >> sys.stderr, "spawning daemon (%d)" % getpid()

  • daemon = _NTLMDaemon(self.samba_host, socket_filename)

  • daemon.run(lockf)

  • the socket does not exist or is invalid, therefore we need to

  • respawn the daemon

  • daemon = _NTLMDaemon(samba_host, socket_filename)

  • daemon.run()

  • connection.connect(socket_filename)

  • if retry:

  • ntlm_server.connect(socket_filename)

  • self.ntlm_server = ntlm_server

  • return connection

  • def _recv_from_server(self, nbr_bytes):

  • print >>sys.stderr, "receiving data (%d)" % getpid()

  • def close(self):

  • if self.connection is not None:

  • _safe_close(self.connection)

  • self.connection = None

  • receives a payload from the server and returns an empty string if

  • something went wrong, just as if the actual request failed

  • try:

  • payload = self.ntlm_server.recv(nbr_bytes, MSG_WAITALL)

  • if payload is None:

  • payload = ""

  • except IOError:

  • if the server has died, we must return an error anyway

  • payload = ""

  • print >>sys.stderr, "received data (%d)" % getpid()

    +class NTLMAuthHandler(object):

  • """

  • HTTP/1.0 NTLM authentication middleware

  • return payload

  • Parameters: application -- the application object that is called only upon

  • successful authentication.

  • """

  • def init(self, application):

  • self.application = application

  • def call(self, env, start_response):

  • (work_dir, samba_host, cookie_name, has_auth) \

  • = self._read_environment(env)

  • connection = _NTLMAuthClient(work_dir, samba_host)

  • TODO: validate authorization payload

  • cookies = self._get_cookies(env)

  • if cookie_name in cookies:

  • client_id = cookies[cookie_name]

  • server_knows_client = connection.server_knows_client(client_id)

  • print >> sys.stderr, \

  • "server knows client (pid: %d): %s" % (getpid(),

  • str(server_knows_client))

  • else:

  • server_knows_client = False

  • print >> sys.stderr, "client did not pass auth cookie"

  • if has_auth:

  • auth = env["HTTP_AUTHORIZATION"]

  • ntlm_payload = auth[5:].decode("base64")

  • if server_knows_client:

  • stage 1, where the client has already received the challenge

  • from the server and is now sending an AUTH message

  • server_knows_client implies that client_id is valid

  • (success, payload) \

  • = connection.ntlm_transaction(client_id, ntlm_payload)

  • if success:

  • connection.close()

  • response = self.application(env, start_response)

  • else:

  • response = self._in_progress_response(start_response)

  • else:

  • stage 0, where the cookie has not been set yet and where we

  • know the NTLM payload is a NEGOTIATE message

  • client_id = str(uuid4())

  • (success, ntlm_payload) \

  • = connection.ntlm_transaction(client_id, ntlm_payload)

  • if success:

  • response = self._in_progress_response(start_response,

  • ntlm_payload,

  • client_id,

  • cookie_name)

  • else:

  • response = self._in_progress_response(start_response)

  • else:

  • if server_knows_client:

  • authenticated, where no NTLM payload is provided anymore

  • connection.close()

  • response = self.application(env, start_response)

  • else:

  • this client has never been seen

  • note: this is the only case where the "connection" object is

  • uselessly instantiated

  • response = self._in_progress_response(start_response, None)

  • connection.close()

  • return response

  • @staticmethod

  • def _read_environment(env):

  • if "NTLMAUTHHANDLER_WORKDIR" in env:

  • work_dir = env["NTLMAUTHHANDLER_WORKDIR"]

  • if not exists(work_dir):

  • raise ValueError("the directory specified for"

  • " 'NTLMAUTHHANDLER_WORKDIR' does not exist:"

  • " '%s'" % work_dir)

  • else:

  • raise ValueError("'NTLMAUTHHANDLER_WORKDIR' is not set in"

  • " the environment")

  • if "SAMBA_HOST" in env:

  • samba_host = env["SAMBA_HOST"]

  • else:

  • print >> sys.stderr, \

  • "'SAMBA_HOST' not configured, 'localhost' will be used"

  • samba_host = "localhost"

  • if "NTLMAUTHHANDLER_COOKIENAME" in env:

  • cookie_name = env["NTLMAUTHHANDLER_COOKIENAME"]

  • else:

  • cookie_name = "oc-ntlm-auth"

  • has_auth = "HTTP_AUTHORIZATION" in env

  • return (work_dir, samba_host, cookie_name, has_auth)

  • @staticmethod

  • def _get_cookies(env):

  • cookies = {}

  • if "HTTP_COOKIE" in env:

  • cookie_str = env["HTTP_COOKIE"]

  • for pair in cookie_str.split(";"):

  • (key, value) = pair.strip().split("=")

  • cookies[key] = value

  • return cookies

  • @staticmethod

  • def _in_progress_response(start_response, ntlm_data=None,

  • client_id=None, cookie_name=None):

  • status = "401 %s" % httplib.responses[401]

  • content = "More data needed..."

  • headers = [("Content-Type", "text/plain"),

  • ("Content-Length", "%d" % len(content))]

  • if ntlm_data is None:

  • www_auth_value = "NTLM"

  • else:

  • enc_ntlm_data = ntlm_data.encode("base64")

  • www_auth_value = ("NTLM %s"

  • % enc_ntlm_data.strip().replace("\n", ""))

  • if client_id is not None:

  • MUST occur when ntlm_data is None, can still occur otherwise

  • headers.append(("Set-Cookie", "%s=%s" % (cookie_name, client_id)))

  • headers.append(("WWW-Authenticate", www_auth_value))

  • start_response(status, headers)

  • return [content]

ludovic

ludovic

2012-08-03 16:26

administrator   ~0004251

Fixed with:

Revision: 4079
http://websvn.openchange.org/listing.php?repname=OpenChange&path=%2F&rev=4079&sc=1
Author: wsourdeau
Date: 2012-08-03 16:27:27 +0200 (Fri, 03 Aug 2012)
Log Message:

Appended a closure as wsgi callable which injects NTLMAUTHHANDLER_WORKDIR before passing the request to the auth handler itself

Modified Paths:

branches/sogo/mapiproxy/services/ocsmanager/ocsmanager/config/middleware.py

Modified: branches/sogo/mapiproxy/services/ocsmanager/ocsmanager/config/middleware.py

--- branches/sogo/mapiproxy/services/ocsmanager/ocsmanager/config/middleware.py 2012-08-02 20:08:09 UTC (rev 4078)
+++ branches/sogo/mapiproxy/services/ocsmanager/ocsmanager/config/middleware.py 2012-08-03 14:27:27 UTC (rev 4079)
@@ -65,10 +65,14 @@

authenticator = OCSAuthenticator(config)

 # app = AuthBasicHandler(app, "OCSManager", authenticator)
 fqdn = "%(hostname)s.%(dnsdomain)s" % config["samba"]
  • app = NTLMAuthHandler(app)

  • auth_handler = NTLMAuthHandler(app)

  • def ntlm_env_setter(environ, start_response):

  • environ["NTLMAUTHHANDLER_WORKDIR"] = app_conf["NTLMAUTHHANDLER_WORKDIR"]

  • return auth_handler(environ, start_response)

  • Establish the Registry for this application

  • app = RegistryManager(app)

  • app = RegistryManager(ntlm_env_setter)

    if asbool(static_files):

    Serve static files

Issue History

Date Modified Username Field Change
2012-07-19 16:59 ludovic New Issue
2012-07-19 17:13 ludovic Note Added: 0004181
2012-07-25 20:08 ludovic Target Version => 2.0
2012-07-27 17:52 ludovic Status new => assigned
2012-07-27 17:52 ludovic Assigned To => ludovic
2012-07-30 19:58 ludovic Assigned To ludovic => jraby
2012-08-02 20:31 ludovic Note Added: 0004247
2012-08-02 23:03 ludovic Note Added: 0004248
2012-08-03 16:26 ludovic Note Added: 0004251
2012-08-03 16:26 ludovic Status assigned => resolved
2012-08-03 16:26 ludovic Fixed in Version => 2.0
2012-08-03 16:26 ludovic Resolution open => fixed
2012-08-03 16:26 ludovic Status resolved => closed