]> git.ipfire.org Git - thirdparty/squid.git/blob - src/auth/ntlm/UserRequest.cc
HttpRequest::helperNotes to NotePairs
[thirdparty/squid.git] / src / auth / ntlm / UserRequest.cc
1 #include "squid.h"
2 #include "auth/ntlm/auth_ntlm.h"
3 #include "auth/ntlm/UserRequest.h"
4 #include "auth/State.h"
5 #include "cbdata.h"
6 #include "client_side.h"
7 #include "globals.h"
8 #include "HttpRequest.h"
9 #include "SquidTime.h"
10
11 Auth::Ntlm::UserRequest::UserRequest()
12 {
13 waiting=0;
14 client_blob=0;
15 server_blob=0;
16 authserver=NULL;
17 request=NULL;
18 }
19
20 Auth::Ntlm::UserRequest::~UserRequest()
21 {
22 assert(LockCount()==0);
23 safe_free(server_blob);
24 safe_free(client_blob);
25
26 releaseAuthServer();
27
28 if (request) {
29 HTTPMSGUNLOCK(request);
30 request = NULL;
31 }
32 }
33
34 const char *
35 Auth::Ntlm::UserRequest::connLastHeader()
36 {
37 return NULL;
38 }
39
40 int
41 Auth::Ntlm::UserRequest::authenticated() const
42 {
43 if (user() != NULL && user()->credentials() == Auth::Ok) {
44 debugs(29, 9, HERE << "user authenticated.");
45 return 1;
46 }
47
48 debugs(29, 9, HERE << "user not fully authenticated.");
49 return 0;
50 }
51
52 Auth::Direction
53 Auth::Ntlm::UserRequest::module_direction()
54 {
55 /* null auth_user is checked for by Auth::UserRequest::direction() */
56
57 if (waiting || client_blob)
58 return Auth::CRED_LOOKUP; /* need helper response to continue */
59
60 if (user()->auth_type != Auth::AUTH_NTLM)
61 return Auth::CRED_ERROR;
62
63 switch (user()->credentials()) {
64
65 case Auth::Handshake:
66 assert(server_blob);
67 return Auth::CRED_CHALLENGE;
68
69 case Auth::Ok:
70 return Auth::CRED_VALID;
71
72 case Auth::Failed:
73 return Auth::CRED_ERROR; // XXX: really? not VALID or CHALLENGE?
74
75 default:
76 debugs(29, DBG_IMPORTANT, "WARNING: NTLM Authentication in unexpected state: " << user()->credentials());
77 return Auth::CRED_ERROR;
78 }
79 }
80
81 void
82 Auth::Ntlm::UserRequest::module_start(AUTHCB * handler, void *data)
83 {
84 static char buf[MAX_AUTHTOKEN_LEN];
85
86 assert(data);
87 assert(handler);
88
89 if (static_cast<Auth::Ntlm::Config*>(Auth::Config::Find("ntlm"))->authenticateProgram == NULL) {
90 debugs(29, DBG_CRITICAL, "ERROR: NTLM Start: no NTLM program configured.");
91 handler(data);
92 return;
93 }
94
95 debugs(29, 8, HERE << "credentials state is '" << user()->credentials() << "'");
96
97 if (user()->credentials() == Auth::Pending) {
98 snprintf(buf, sizeof(buf), "YR %s\n", client_blob); //CHECKME: can ever client_blob be 0 here?
99 } else {
100 snprintf(buf, sizeof(buf), "KK %s\n", client_blob);
101 }
102
103 waiting = 1;
104
105 safe_free(client_blob);
106 helperStatefulSubmit(ntlmauthenticators, buf, Auth::Ntlm::UserRequest::HandleReply,
107 new Auth::StateData(this, handler, data), authserver);
108 }
109
110 /**
111 * Atomic action: properly release the NTLM auth helpers which may have been reserved
112 * for this request connections use.
113 */
114 void
115 Auth::Ntlm::UserRequest::releaseAuthServer()
116 {
117 if (authserver) {
118 debugs(29, 6, HERE << "releasing NTLM auth server '" << authserver << "'");
119 helperStatefulReleaseServer(authserver);
120 authserver = NULL;
121 } else
122 debugs(29, 6, HERE << "No NTLM auth server to release.");
123 }
124
125 void
126 Auth::Ntlm::UserRequest::authenticate(HttpRequest * aRequest, ConnStateData * conn, http_hdr_type type)
127 {
128 assert(this);
129
130 /* Check that we are in the client side, where we can generate
131 * auth challenges */
132
133 if (conn == NULL || !cbdataReferenceValid(conn)) {
134 user()->credentials(Auth::Failed);
135 debugs(29, DBG_IMPORTANT, "WARNING: NTLM Authentication attempt to perform authentication without a connection!");
136 return;
137 }
138
139 if (waiting) {
140 debugs(29, DBG_IMPORTANT, "WARNING: NTLM Authentication waiting for helper reply!");
141 return;
142 }
143
144 if (server_blob) {
145 debugs(29, 2, HERE << "need to challenge client '" << server_blob << "'!");
146 return;
147 }
148
149 /* get header */
150 const char *proxy_auth = aRequest->header.getStr(type);
151
152 /* locate second word */
153 const char *blob = proxy_auth;
154
155 /* if proxy_auth is actually NULL, we'd better not manipulate it. */
156 if (blob) {
157 while (xisspace(*blob) && *blob)
158 ++blob;
159
160 while (!xisspace(*blob) && *blob)
161 ++blob;
162
163 while (xisspace(*blob) && *blob)
164 ++blob;
165 }
166
167 switch (user()->credentials()) {
168
169 case Auth::Unchecked:
170 /* we've received a ntlm request. pass to a helper */
171 debugs(29, 9, HERE << "auth state ntlm none. Received blob: '" << proxy_auth << "'");
172 user()->credentials(Auth::Pending);
173 safe_free(client_blob);
174 client_blob=xstrdup(blob);
175 assert(conn->getAuth() == NULL);
176 conn->setAuth(this, "new NTLM handshake request");
177 request = aRequest;
178 HTTPMSGLOCK(request);
179 break;
180
181 case Auth::Pending:
182 debugs(29, DBG_IMPORTANT, HERE << "need to ask helper");
183 break;
184
185 case Auth::Handshake:
186 /* we should have received a blob from the client. Hand it off to
187 * some helper */
188 safe_free(client_blob);
189 client_blob = xstrdup(blob);
190 if (request)
191 HTTPMSGUNLOCK(request);
192 request = aRequest;
193 HTTPMSGLOCK(request);
194 break;
195
196 case Auth::Ok:
197 fatal("Auth::Ntlm::UserRequest::authenticate: unexpect auth state DONE! Report a bug to the squid developers.\n");
198 break;
199
200 case Auth::Failed:
201 /* we've failed somewhere in authentication */
202 debugs(29, 9, HERE << "auth state ntlm failed. " << proxy_auth);
203 break;
204 }
205 }
206
207 void
208 Auth::Ntlm::UserRequest::HandleReply(void *data, const HelperReply &reply)
209 {
210 Auth::StateData *r = static_cast<Auth::StateData *>(data);
211
212 debugs(29, 8, HERE << "helper: '" << reply.whichServer << "' sent us reply=" << reply);
213
214 if (!cbdataReferenceValid(r->data)) {
215 debugs(29, DBG_IMPORTANT, "ERROR: NTLM Authentication invalid callback data. helper '" << reply.whichServer << "'.");
216 delete r;
217 return;
218 }
219
220 Auth::UserRequest::Pointer auth_user_request = r->auth_user_request;
221 assert(auth_user_request != NULL);
222
223 Auth::Ntlm::UserRequest *lm_request = dynamic_cast<Auth::Ntlm::UserRequest *>(auth_user_request.getRaw());
224 assert(lm_request != NULL);
225 assert(lm_request->waiting);
226
227 lm_request->waiting = 0;
228 safe_free(lm_request->client_blob);
229
230 assert(auth_user_request->user() != NULL);
231 assert(auth_user_request->user()->auth_type == Auth::AUTH_NTLM);
232
233 if (lm_request->authserver == NULL)
234 lm_request->authserver = reply.whichServer.get(); // XXX: no locking?
235 else
236 assert(reply.whichServer == lm_request->authserver);
237
238 switch (reply.result) {
239 case HelperReply::TT:
240 /* we have been given a blob to send to the client */
241 safe_free(lm_request->server_blob);
242 lm_request->request->flags.mustKeepalive = true;
243 if (lm_request->request->flags.proxyKeepalive) {
244 const char *serverBlob = reply.notes.findFirst("token");
245 lm_request->server_blob = xstrdup(serverBlob);
246 auth_user_request->user()->credentials(Auth::Handshake);
247 auth_user_request->denyMessage("Authentication in progress");
248 debugs(29, 4, HERE << "Need to challenge the client with a server token: '" << serverBlob << "'");
249 } else {
250 auth_user_request->user()->credentials(Auth::Failed);
251 auth_user_request->denyMessage("NTLM authentication requires a persistent connection");
252 }
253 break;
254
255 case HelperReply::Okay: {
256 /* we're finished, release the helper */
257 const char *userLabel = reply.notes.findFirst("user");
258 auth_user_request->user()->username(userLabel);
259 auth_user_request->denyMessage("Login successful");
260 safe_free(lm_request->server_blob);
261 lm_request->releaseAuthServer();
262
263 debugs(29, 4, HERE << "Successfully validated user via NTLM. Username '" << userLabel << "'");
264 /* connection is authenticated */
265 debugs(29, 4, HERE << "authenticated user " << auth_user_request->user()->username());
266 /* see if this is an existing user with a different proxy_auth
267 * string */
268 AuthUserHashPointer *usernamehash = static_cast<AuthUserHashPointer *>(hash_lookup(proxy_auth_username_cache, auth_user_request->user()->username()));
269 Auth::User::Pointer local_auth_user = lm_request->user();
270 while (usernamehash && (usernamehash->user()->auth_type != Auth::AUTH_NTLM ||
271 strcmp(usernamehash->user()->username(), auth_user_request->user()->username()) != 0))
272 usernamehash = static_cast<AuthUserHashPointer *>(usernamehash->next);
273 if (usernamehash) {
274 /* we can't seamlessly recheck the username due to the
275 * challenge-response nature of the protocol.
276 * Just free the temporary auth_user after merging as
277 * much of it new state into the existing one as possible */
278 usernamehash->user()->absorb(local_auth_user);
279 /* from here on we are working with the original cached credentials. */
280 local_auth_user = usernamehash->user();
281 auth_user_request->user(local_auth_user);
282 } else {
283 /* store user in hash's */
284 local_auth_user->addToNameCache();
285 }
286 /* set these to now because this is either a new login from an
287 * existing user or a new user */
288 local_auth_user->expiretime = current_time.tv_sec;
289 auth_user_request->user()->credentials(Auth::Ok);
290 debugs(29, 4, HERE << "Successfully validated user via NTLM. Username '" << auth_user_request->user()->username() << "'");
291 }
292 break;
293
294 case HelperReply::Error: {
295 /* authentication failure (wrong password, etc.) */
296 const char *errNote = reply.notes.find("message");
297 if (errNote != NULL)
298 auth_user_request->denyMessage(errNote);
299 else
300 auth_user_request->denyMessage("NTLM Authentication denied with no reason given");
301 auth_user_request->user()->credentials(Auth::Failed);
302 safe_free(lm_request->server_blob);
303 lm_request->releaseAuthServer();
304 debugs(29, 4, HERE << "Failed validating user via NTLM. Error returned '" << errNote << "'");
305 }
306 break;
307
308 case HelperReply::Unknown:
309 debugs(29, DBG_IMPORTANT, "ERROR: NTLM Authentication Helper '" << reply.whichServer << "' crashed!.");
310 /* continue to the next case */
311
312 case HelperReply::BrokenHelper: {
313 /* TODO kick off a refresh process. This can occur after a YR or after
314 * a KK. If after a YR release the helper and resubmit the request via
315 * Authenticate NTLM start.
316 * If after a KK deny the user's request w/ 407 and mark the helper as
317 * Needing YR. */
318 const char *errNote = reply.notes.find("message");
319 if (reply.result == HelperReply::Unknown)
320 auth_user_request->denyMessage("Internal Error");
321 else if (errNote != NULL)
322 auth_user_request->denyMessage(errNote);
323 else
324 auth_user_request->denyMessage("NTLM Authentication failed with no reason given");
325 auth_user_request->user()->credentials(Auth::Failed);
326 safe_free(lm_request->server_blob);
327 lm_request->releaseAuthServer();
328 debugs(29, DBG_IMPORTANT, "ERROR: NTLM Authentication validating user. Error returned '" << reply << "'");
329 }
330 break;
331 }
332
333 if (lm_request->request) {
334 HTTPMSGUNLOCK(lm_request->request);
335 lm_request->request = NULL;
336 }
337 r->handler(r->data);
338 delete r;
339 }