Merge in rpcproxy (wsgi based)
[jelmer/openchange.git] / mapiproxy / services / ocsmanager / ocsmanager / config / NTLMAuthHandler.py
1 import httplib
2 import socket
3 import uuid
4
5 from samba.gensec import Security
6 from samba.auth import AuthContext
7
8
9 __all__ = ['NTLMAuthHandler']
10
11 COOKIE_NAME="ocs-ntlm-auth"
12
13
14 class NTLMAuthHandler(object):
15     """
16     HTTP/1.0 ``NTLM`` authentication middleware
17
18     Parameters: application -- the application object that is called only upon
19     successful authentication.
20
21     """
22
23     def __init__(self, application):
24         # TODO: client expiration and/or cleanup
25         self.client_status = {}
26         self.application = application
27
28     def _in_progress_response(self, start_response, ntlm_data=None, client_id=None):
29         status = "401 %s" % httplib.responses[401]
30         content = "More data needed..."
31         headers = [("Content-Type", "text/plain"),
32                    ("Content-Length", "%d" % len(content))]
33         if ntlm_data is None:
34             www_auth_value = "NTLM"
35         else:
36             enc_ntlm_data = ntlm_data.encode("base64")
37             www_auth_value = ("NTLM %s"
38                               % enc_ntlm_data.strip().replace("\n", ""))
39         if client_id is not None:
40             # MUST occur when ntlm_data is None, can still occur otherwise
41             headers.append(("Set-Cookie", "%s=%s" % (COOKIE_NAME, client_id)))
42
43         headers.append(("WWW-Authenticate", www_auth_value))
44         start_response(status, headers)
45
46         return [content]
47
48     def _failure_response(self, start_response, explanation=None):
49         status = "403 %s" % httplib.responses[403]
50         content = "Authentication failure"
51         if explanation is not None:
52             content = content + " (%s)" % explanation
53         headers = [("Content-Type", "text/plain"),
54                    ("Content-Length", "%d" % len(content))]
55         start_response(status, headers)
56
57         return [content]
58
59     def _get_cookies(self, env):
60         cookies = {}
61         if "HTTP_COOKIE" in env:
62             cookie_str = env["HTTP_COOKIE"]
63             for pair in cookie_str.split(";"):
64                 (key, value) = pair.strip().split("=")
65                 cookies[key] = value
66
67         return cookies
68
69     def __call__(self, env, start_response):
70         cookies = self._get_cookies(env)
71         if COOKIE_NAME in cookies:
72             client_id = cookies[COOKIE_NAME]
73         else:
74             client_id = None
75
76         # old model that only works with mod_wsgi:
77         # if "REMOTE_ADDR" in env and "REMOTE_PORT" in env:
78         #     client_id = "%(REMOTE_ADDR)s:%(REMOTE_PORT)s".format(env)
79
80         if client_id is None or client_id not in self.client_status:
81             # first stage
82             server = Security.start_server(auth_context=AuthContext())
83             server.start_mech_by_name("ntlmssp")
84             client_id = str(uuid.uuid4())
85
86             if "HTTP_AUTHORIZATION" in env:
87                 # Outlook may directly have sent a NTLM payload
88                 auth = env["HTTP_AUTHORIZATION"]
89                 auth_msg = server.update(auth[5:].decode('base64'))
90                 response = self._in_progress_response(start_response,
91                                                       auth_msg[1],
92                                                       client_id)
93                 self.client_status[client_id] = {"stage": "stage1",
94                                                  "server": server}
95             else:
96                 self.client_status[client_id] = {"stage": "stage0",
97                                                  "server": server}
98                 response = self._in_progress_response(start_response, None, client_id)
99         else:
100             status_stage = self.client_status[client_id]["stage"]
101
102             if status_stage == "ok":
103                 # client authenticated previously
104                 response = self.application(env, start_response)
105             elif status_stage == "stage0":
106                 # test whether client supports "NTLM"
107                 if "HTTP_AUTHORIZATION" in env:
108                     auth = env["HTTP_AUTHORIZATION"]
109                     server = self.client_status[client_id]["server"]
110                     auth_msg = server.update(auth[5:].decode('base64'))
111                     response = self._in_progress_response(start_response,
112                                                           auth_msg[1])
113                     self.client_status[client_id]["stage"] = "stage1"
114                 else:
115                     del(self.client_status[client_id])
116                     response = self._failure_response(start_response,
117                                                       "failure at '%s'"
118                                                       % status_stage)
119             elif status_stage == "stage1":
120                 if "HTTP_AUTHORIZATION" in env:
121                     auth = env["HTTP_AUTHORIZATION"]
122                     server = self.client_status[client_id]["server"]
123                     try:
124                         auth_msg = server.update(auth[5:].decode('base64'))
125                     except RuntimeError: # a bit violent...
126                         auth_msg = (0,)
127
128                     if auth_msg[0] == 1:
129                         # authentication completed
130                         self.client_status[client_id]["stage"] = "ok"
131                         del(self.client_status[client_id]["server"])
132                         response = self.application(env, start_response)
133                     else:
134                         # we start over with the whole process
135
136                         server = Security.start_server(auth_context=AuthContext())
137                         server.start_mech_by_name("ntlmssp")
138                         self.client_status[client_id] = {"stage": "stage0",
139                                                          "server": server}
140                         response = self._in_progress_response(start_response)
141                 else:
142                     del(self.client_status[client_id])
143                     response = self._failure_response(start_response,
144                                                       "failure at '%s'"
145                                                       % status_stage)
146             else:
147                 raise RuntimeError("none shall pass!")
148
149         return response
150
151
152 middleware = NTLMAuthHandler