1 /*
2 HTTP Authentication routines
3 Copyright (C) 1999-2021, Joe Orton <joe@manyfish.co.uk>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free
17 Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
18 MA 02111-1307, USA
19
20 */
21
22 #include "config.h"
23
24 #include <sys/types.h>
25
26 #ifdef HAVE_SYS_TIME_H
27 #include <sys/time.h>
28 #endif
29 #ifdef HAVE_STDLIB_H
30 #include <stdlib.h>
31 #endif
32 #ifdef HAVE_STRING_H
33 #include <string.h>
34 #endif
35 #ifdef HAVE_STRINGS_H
36 #include <strings.h>
37 #endif
38 #ifdef HAVE_UNISTD_H
39 #include <unistd.h> /* for getpid() */
40 #endif
41
42 #ifdef WIN32
43 #include <windows.h> /* for GetCurrentThreadId() etc */
44 #endif
45
46 #ifdef HAVE_OPENSSL
47 #include <openssl/rand.h>
48 #elif defined(HAVE_GNUTLS)
49 #include <gnutls/gnutls.h>
50 #if LIBGNUTLS_VERSION_NUMBER < 0x020b00
51 #include <gcrypt.h>
52 #else
53 #include <gnutls/crypto.h>
54 #endif
55 #endif
56
57 #include <errno.h>
58 #include <time.h>
59
60 #include "ne_md5.h"
61 #include "ne_dates.h"
62 #include "ne_request.h"
63 #include "ne_auth.h"
64 #include "ne_string.h"
65 #include "ne_utils.h"
66 #include "ne_alloc.h"
67 #include "ne_uri.h"
68 #include "ne_internal.h"
69
70 #ifdef HAVE_GSSAPI
71 #ifdef HAVE_GSSAPI_GSSAPI_H
72 #include <gssapi/gssapi.h>
73 #ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
74 #include <gssapi/gssapi_generic.h>
75 #endif
76 #else
77 #include <gssapi.h>
78 #endif
79 #endif
80
81 #ifdef HAVE_SSPI
82 #include "ne_sspi.h"
83 #endif
84
85 #ifdef HAVE_NTLM
86 #include "ne_ntlm.h"
87 #endif
88
89 #define HOOK_SERVER_ID "http://webdav.org/neon/hooks/server-auth"
90 #define HOOK_PROXY_ID "http://webdav.org/neon/hooks/proxy-auth"
91
92 typedef enum {
93 auth_alg_md5 = 0,
94 auth_alg_md5_sess,
95 auth_alg_sha256,
96 auth_alg_sha256_sess,
97 auth_alg_sha512_256,
98 auth_alg_sha512_256_sess,
99 auth_alg_unknown
100 } auth_algorithm;
101
102 static const unsigned int alg_to_hash[] = {
103 NE_HASH_MD5,
104 NE_HASH_MD5,
105 NE_HASH_SHA256,
106 NE_HASH_SHA256,
107 NE_HASH_SHA512_256,
108 NE_HASH_SHA512_256,
109 0
110 };
111 static const char *const alg_to_name[] = {
112 "MD5",
113 "MD5-sess",
114 "SHA-256",
115 "SHA-256-sess",
116 "SHA-512-256",
117 "SHA-512-256-sess",
118 "(unknown)",
119 };
120
121 /* Selected method of qop which the client is using */
122 typedef enum {
123 auth_qop_none,
124 auth_qop_auth
125 } auth_qop;
126
127 /* A callback/userdata pair registered by the application for
128 * a particular set of protocols. */
129 struct auth_handler {
130 unsigned protomask;
131
132 ne_auth_creds old_creds;
133 ne_auth_provide new_creds;
134 void *userdata;
135 int attempt; /* number of invocations of this callback for
136 * current request. */
137
138 struct auth_handler *next;
139 };
140
141 /* A challenge */
142 struct auth_challenge {
143 const struct auth_protocol *protocol;
144 struct auth_handler *handler;
145 const char *realm, *nonce, *opaque, *domain;
146 unsigned int stale; /* if stale=true */
147 unsigned int got_qop; /* we were given a qop directive */
148 unsigned int qop_auth; /* "auth" token in qop attrib */
149 enum { userhash_none=0, userhash_true=1, userhash_false=2} userhash;
150 auth_algorithm alg;
151 struct auth_challenge *next;
152 };
153
154 static const struct auth_class {
155 const char *id, *req_hdr, *resp_hdr, *resp_info_hdr;
156 int status_code; /* Response status-code to trap. */
157 int fail_code; /* NE_* request to fail with. */
158 unsigned protomask; /* protocol mask */
159 const char *error_noauth; /* Error message template use when
160 * giving up authentication attempts. */
161 } ah_server_class = {
162 HOOK_SERVER_ID,
163 "Authorization", "WWW-Authenticate", "Authentication-Info",
164 401, NE_AUTH, 0,
165 N_("Could not authenticate to server: %s")
166 }, ah_proxy_class = {
167 HOOK_PROXY_ID,
168 "Proxy-Authorization", "Proxy-Authenticate", "Proxy-Authentication-Info",
169 407, NE_PROXYAUTH, NE_AUTH_PROXY,
170 N_("Could not authenticate to proxy server: %s")
171 };
172
173 /* Internal buffer size, which must be >= NE_ABUFSIZ. */
174 #define ABUFSIZE (NE_ABUFSIZ * 2)
175
176 /* Authentication session state. */
177 typedef struct {
178 ne_session *sess;
179
180 /* Which context will auth challenges be accepted? */
181 enum {
182 AUTH_ANY, /* ignore nothing. */
183 AUTH_CONNECT, /* only in response to a CONNECT request. */
184 AUTH_NOTCONNECT /* only in non-CONNECT responsees */
185 } context;
186
187 /* Protocol type for server/proxy auth. */
188 const struct auth_class *spec;
189
190 /* The protocol used for this authentication session */
191 const struct auth_protocol *protocol;
192
193 struct auth_handler *handlers;
194
195 /*** Session details ***/
196
197 /* The username and password we are using to authenticate with */
198 char username[ABUFSIZE];
199
200 /* This used for Basic auth */
201 char *basic;
202 #ifdef HAVE_GSSAPI
203 /* for the GSSAPI/Negotiate scheme: */
204 char *gssapi_token;
205 gss_ctx_id_t gssctx;
206 gss_name_t gssname;
207 gss_OID gssmech;
208 #endif
209 #ifdef HAVE_SSPI
210 /* This is used for SSPI (Negotiate/NTLM) auth */
211 char *sspi_token;
212 void *sspi_context;
213 char *sspi_host;
214 #endif
215 #ifdef HAVE_NTLM
216 /* This is used for NTLM auth */
217 ne_ntlm_context *ntlm_context;
218 #endif
219 /* These all used for Digest auth */
220 char *realm;
221 char *nonce;
222 char *cnonce;
223 char *opaque;
224 char **domains; /* list of paths given as domain. */
225 size_t ndomains; /* size of domains array */
226 char *userhash;
227 char *username_star;
228 auth_qop qop;
229 auth_algorithm alg;
230 unsigned int nonce_count;
231 /* The hex representation of the H(A1) value */
232 char *h_a1;
233 /* Part of the RHS of the response digest. */
234 char *response_rhs;
235 } auth_session;
236
237 struct auth_request {
238 /*** Per-request details. ***/
239 ne_request *request; /* the request object. */
240
241 /* The method and URI we are using for the current request */
242 const char *uri;
243 const char *method;
244
245 int attempt; /* number of times this request has been retries due
246 * to auth challenges. */
247 };
248
249 /* Used if this protocol takes an unquoted non-name/value-pair
250 * parameter in the challenge. */
251 #define AUTH_FLAG_OPAQUE_PARAM (0x0001)
252 /* Used if this Authentication-Info may be sent for non-40[17]
253 * response for this protocol. */
254 #define AUTH_FLAG_VERIFY_NON40x (0x0002)
255 /* Used for broken the connection-based auth schemes. */
256 #define AUTH_FLAG_CONN_AUTH (0x0004)
257
258 struct auth_protocol {
259 unsigned id; /* public NE_AUTH_* id. */
260
261 int strength; /* protocol strength for sort order. */
262
263 const char *name; /* protocol name. */
264
265 /* Parse the authentication challenge; returns zero on success, or
266 * non-zero if this challenge be handled. 'attempt' is the number
267 * of times the request has been resent due to auth challenges.
268 * On failure, challenge_error() should be used to append an error
269 * message to the error buffer 'errmsg'. */
270 int (*challenge)(auth_session *sess, int attempt,
271 struct auth_challenge *chall,
272 const char *uri, ne_buffer **errmsg);
273
274 /* Return the string to send in the -Authenticate request header:
275 * (ne_malloc-allocated, NUL-terminated string) */
276 char *(*response)(auth_session *sess, struct auth_request *req);
277
278 /* Parse a Authentication-Info response; returns NE_* error code
279 * on failure; on failure, the session error string must be
280 * set. */
281 int (*verify)(struct auth_request *req, auth_session *sess,
282 const char *value);
283
284 int flags; /* AUTH_FLAG_* flags */
285 };
286
287 /* Helper function to append an error to the buffer during challenge
288 * handling. Pass printf-style string. *errmsg may be NULL and is
289 * allocated if necessary. errmsg must be non-NULL. */
290 static void challenge_error(ne_buffer **errmsg, const char *fmt, ...)
291 ne_attribute((format(printf, 2, 3)));
292
293 static int inside_domain(auth_session *sess, const char *req_uri);
294
295 /* Free the domains array, precondition sess->ndomains > 0. */
free_domains(auth_session * sess)296 static void free_domains(auth_session *sess)
297 {
298 do {
299 ne_free(sess->domains[sess->ndomains - 1]);
300 } while (--sess->ndomains);
301 ne_free(sess->domains);
302 sess->domains = NULL;
303 }
304
clean_session(auth_session * sess)305 static void clean_session(auth_session *sess)
306 {
307 if (sess->basic) ne_free(sess->basic);
308 if (sess->nonce) ne_free(sess->nonce);
309 if (sess->cnonce) ne_free(sess->cnonce);
310 if (sess->opaque) ne_free(sess->opaque);
311 if (sess->realm) ne_free(sess->realm);
312 if (sess->userhash) ne_free(sess->userhash);
313 if (sess->username_star) ne_free(sess->username_star);
314 if (sess->response_rhs) ne_free(sess->response_rhs);
315 if (sess->h_a1) ne_free(sess->h_a1);
316 sess->realm = sess->basic = sess->cnonce = sess->nonce =
317 sess->opaque = sess->userhash = sess->response_rhs =
318 sess->h_a1 = sess->username_star = NULL;
319 if (sess->ndomains) free_domains(sess);
320 #ifdef HAVE_GSSAPI
321 {
322 unsigned int major;
323
324 if (sess->gssctx != GSS_C_NO_CONTEXT)
325 gss_delete_sec_context(&major, &sess->gssctx, GSS_C_NO_BUFFER);
326
327 }
328 if (sess->gssapi_token) ne_free(sess->gssapi_token);
329 sess->gssapi_token = NULL;
330 #endif
331 #ifdef HAVE_SSPI
332 if (sess->sspi_token) ne_free(sess->sspi_token);
333 sess->sspi_token = NULL;
334 ne_sspi_destroy_context(sess->sspi_context);
335 sess->sspi_context = NULL;
336 #endif
337 #ifdef HAVE_NTLM
338 if (sess->ntlm_context) {
339 ne__ntlm_destroy_context(sess->ntlm_context);
340 sess->ntlm_context = NULL;
341 }
342 #endif
343
344 sess->protocol = NULL;
345 }
346
347 /* Returns client nonce string. */
get_cnonce(void)348 static char *get_cnonce(void)
349 {
350 unsigned char data[32];
351
352 #ifdef HAVE_GNUTLS
353 if (1) {
354 #if LIBGNUTLS_VERSION_NUMBER < 0x020b00
355 gcry_create_nonce(data, sizeof data);
356 #else
357 gnutls_rnd(GNUTLS_RND_NONCE, data, sizeof data);
358 #endif
359 return ne_base64(data, sizeof data);
360 }
361 else
362 #elif defined(HAVE_OPENSSL)
363 if (RAND_status() == 1 && RAND_bytes(data, sizeof data) >= 0) {
364 return ne_base64(data, sizeof data);
365 }
366 else
367 #endif /* HAVE_OPENSSL */
368 {
369 /* Fallback sources of random data: all bad, but no good sources
370 * are available. */
371 struct ne_md5_ctx *hash;
372 char ret[33];
373
374 hash = ne_md5_create_ctx();
375
376 /* Uninitialized stack data; yes, happy valgrinders, this is
377 * supposed to be here. */
378 ne_md5_process_bytes(data, sizeof data, hash);
379
380 {
381 #ifdef HAVE_GETTIMEOFDAY
382 struct timeval tv;
383 if (gettimeofday(&tv, NULL) == 0)
384 ne_md5_process_bytes(&tv, sizeof tv, hash);
385 #else /* HAVE_GETTIMEOFDAY */
386 time_t t = time(NULL);
387 ne_md5_process_bytes(&t, sizeof t, hash);
388 #endif
389 }
390 {
391 #ifdef WIN32
392 DWORD pid = GetCurrentThreadId();
393 #else
394 pid_t pid = getpid();
395 #endif
396 ne_md5_process_bytes(&pid, sizeof pid, hash);
397 }
398
399 ne_md5_finish_ascii(hash, ret);
400 ne_md5_destroy_ctx(hash);
401
402 return ne_strdup(ret);
403 }
404 }
405
406 /* Callback to retrieve user credentials for given session on given
407 * attempt (pre request) for given challenge. Password is written to
408 * pwbuf (of size ABUFSIZE). On error, challenge_error() is used
409 * with errmsg. */
get_credentials(auth_session * sess,ne_buffer ** errmsg,int attempt,struct auth_challenge * chall,char * pwbuf)410 static int get_credentials(auth_session *sess, ne_buffer **errmsg, int attempt,
411 struct auth_challenge *chall, char *pwbuf)
412 {
413 unsigned mask = chall->protocol->id | sess->spec->protomask;
414 int rv;
415
416 if (chall->handler->new_creds)
417 rv = chall->handler->new_creds(chall->handler->userdata,
418 attempt, mask, sess->realm,
419 sess->username, pwbuf,
420 ABUFSIZE);
421 else
422 rv = chall->handler->old_creds(chall->handler->userdata, sess->realm,
423 chall->handler->attempt++, sess->username, pwbuf);
424
425 if (rv == 0)
426 return 0;
427
428 challenge_error(errmsg, _("rejected %s challenge"),
429 chall->protocol->name);
430 return -1;
431 }
432
433 /* Return the scope of the Basic authentication domain following rule
434 * in RFC 7617. Malloc-allocated path is returned. */
get_scope_path(const char * uri)435 static char *get_scope_path(const char *uri)
436 {
437 ne_uri base, udot, parent;
438 char *s;
439
440 memset(&udot, 0, sizeof udot);
441 udot.path = ".";
442
443 if (ne_uri_parse(uri, &base) != 0) {
444 /* Assume scope is whole origin. */
445 return ne_strdup("/");
446 }
447
448 ne_uri_resolve(&base, &udot, &parent);
449
450 s = parent.path;
451 parent.path = NULL;
452
453 ne_uri_free(&parent);
454 ne_uri_free(&base);
455
456 return s;
457 }
458
459 /* Examine a Basic auth challenge.
460 * Returns 0 if an valid challenge, else non-zero. */
basic_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,const char * uri,ne_buffer ** errmsg)461 static int basic_challenge(auth_session *sess, int attempt,
462 struct auth_challenge *parms,
463 const char *uri, ne_buffer **errmsg)
464 {
465 char *tmp, password[ABUFSIZE];
466
467 /* Verify challenge... must have a realm */
468 if (parms->realm == NULL) {
469 challenge_error(errmsg, _("missing realm in Basic challenge"));
470 return -1;
471 }
472
473 clean_session(sess);
474
475 sess->realm = ne_strdup(parms->realm);
476
477 if (get_credentials(sess, errmsg, attempt, parms, password)) {
478 /* Failed to get credentials */
479 return -1;
480 }
481
482 if (strchr(sess->username, ':') != NULL) {
483 challenge_error(errmsg, _("cannot handle Basic challenge "
484 "for username containing colon"));
485 return -1;
486 }
487
488 tmp = ne_concat(sess->username, ":", password, NULL);
489 sess->basic = ne_base64((unsigned char *)tmp, strlen(tmp));
490 ne_free(tmp);
491
492 ne__strzero(password, sizeof password);
493
494 if (sess->ndomains) free_domains(sess); /* is this really needed? */
495
496 if (strcmp(uri, "*") == 0) {
497 /* If the request-target is "*" the auth scope is explicitly
498 * the whole server. */
499 return 0;
500 }
501
502 sess->domains = ne_realloc(sess->domains, sizeof(*sess->domains));
503 sess->domains[0] = get_scope_path(uri);
504 sess->ndomains = 1;
505
506 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Basic auth scope is: %s\n",
507 sess->domains[0]);
508
509 return 0;
510 }
511
512 /* Add Basic authentication credentials to a request */
request_basic(auth_session * sess,struct auth_request * req)513 static char *request_basic(auth_session *sess, struct auth_request *req)
514 {
515 if (sess->ndomains && !inside_domain(sess, req->uri)) {
516 return NULL;
517 }
518
519 return ne_concat("Basic ", sess->basic, "\r\n", NULL);
520 }
521
522 #ifdef HAVE_GSSAPI
523 /* Add GSSAPI authentication credentials to a request */
request_negotiate(auth_session * sess,struct auth_request * req)524 static char *request_negotiate(auth_session *sess, struct auth_request *req)
525 {
526 if (sess->gssapi_token)
527 return ne_concat("Negotiate ", sess->gssapi_token, "\r\n", NULL);
528 else
529 return NULL;
530 }
531
532 /* Create an GSSAPI name for server HOSTNAME; returns non-zero on
533 * error. */
get_gss_name(gss_name_t * server,const char * hostname)534 static void get_gss_name(gss_name_t *server, const char *hostname)
535 {
536 unsigned int major, minor;
537 gss_buffer_desc token;
538
539 token.value = ne_concat("HTTP@", hostname, NULL);
540 token.length = strlen(token.value);
541
542 major = gss_import_name(&minor, &token, GSS_C_NT_HOSTBASED_SERVICE,
543 server);
544 ne_free(token.value);
545
546 if (GSS_ERROR(major)) {
547 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: gss_import_name failed.\n");
548 *server = GSS_C_NO_NAME;
549 }
550 }
551
552 /* Append GSSAPI error(s) for STATUS of type TYPE to BUF; prepending
553 * ": " to each error if *FLAG is non-zero, setting *FLAG after an
554 * error has been appended. */
make_gss_error(ne_buffer * buf,int * flag,unsigned int status,int type)555 static void make_gss_error(ne_buffer *buf, int *flag,
556 unsigned int status, int type)
557 {
558 unsigned int major, minor;
559 unsigned int context = 0;
560
561 do {
562 gss_buffer_desc msg;
563 major = gss_display_status(&minor, status, type,
564 GSS_C_NO_OID, &context, &msg);
565 if (major == GSS_S_COMPLETE && msg.length) {
566 if ((*flag)++) ne_buffer_append(buf, ": ", 2);
567 ne_buffer_append(buf, msg.value, msg.length);
568 }
569 if (msg.length) gss_release_buffer(&minor, &msg);
570 } while (context);
571 }
572
573 /* Continue a GSS-API Negotiate exchange, using input TOKEN if
574 * non-NULL. Returns non-zero on error, in which case *errmsg is
575 * guaranteed to be non-NULL (i.e. an error message is set). */
continue_negotiate(auth_session * sess,const char * token,ne_buffer ** errmsg)576 static int continue_negotiate(auth_session *sess, const char *token,
577 ne_buffer **errmsg)
578 {
579 unsigned int major, minor;
580 gss_buffer_desc input = GSS_C_EMPTY_BUFFER;
581 gss_buffer_desc output = GSS_C_EMPTY_BUFFER;
582 unsigned char *bintoken = NULL;
583 int ret;
584
585 if (token) {
586 input.length = ne_unbase64(token, &bintoken);
587 if (input.length == 0) {
588 challenge_error(errmsg, _("invalid Negotiate token"));
589 return -1;
590 }
591 input.value = bintoken;
592 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Continuation token [%s]\n", token);
593 }
594 else if (sess->gssctx != GSS_C_NO_CONTEXT) {
595 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Reset incomplete context.\n");
596 gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER);
597 }
598
599 major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &sess->gssctx,
600 sess->gssname, sess->gssmech,
601 GSS_C_MUTUAL_FLAG, GSS_C_INDEFINITE,
602 GSS_C_NO_CHANNEL_BINDINGS,
603 &input, &sess->gssmech, &output, NULL, NULL);
604
605 /* done with the input token. */
606 if (bintoken) ne_free(bintoken);
607
608 if (GSS_ERROR(major)) {
609 int flag = 0;
610
611 challenge_error(errmsg, _("GSSAPI authentication error: "));
612 make_gss_error(*errmsg, &flag, major, GSS_C_GSS_CODE);
613 make_gss_error(*errmsg, &flag, minor, GSS_C_MECH_CODE);
614
615 return -1;
616 }
617
618 if (major == GSS_S_CONTINUE_NEEDED || major == GSS_S_COMPLETE) {
619 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: init_sec_context OK. (major=%d)\n",
620 major);
621 ret = 0;
622 }
623 else {
624 challenge_error(errmsg, _("GSSAPI failure (code %u)"), major);
625 ret = -1;
626 }
627
628 if (major != GSS_S_CONTINUE_NEEDED) {
629 /* context no longer needed: destroy it */
630 gss_delete_sec_context(&minor, &sess->gssctx, GSS_C_NO_BUFFER);
631 }
632
633 if (output.length) {
634 sess->gssapi_token = ne_base64(output.value, output.length);
635 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Output token: [%s]\n",
636 sess->gssapi_token);
637 gss_release_buffer(&minor, &output);
638 } else {
639 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No output token.\n");
640 }
641
642 return ret;
643 }
644
645 /* Process a Negotiate challenge CHALL in session SESS; returns zero
646 * if challenge is accepted. */
negotiate_challenge(auth_session * sess,int attempt,struct auth_challenge * chall,const char * uri,ne_buffer ** errmsg)647 static int negotiate_challenge(auth_session *sess, int attempt,
648 struct auth_challenge *chall,
649 const char *uri, ne_buffer **errmsg)
650 {
651 const char *token = chall->opaque;
652
653 /* Respect an initial challenge - which must have no input token,
654 * or a continuation - which must have an input token. */
655 if (attempt == 0 || token) {
656 return continue_negotiate(sess, token, errmsg);
657 }
658 else {
659 challenge_error(errmsg, _("ignoring empty Negotiate continuation"));
660 return -1;
661 }
662 }
663
664 /* Verify the header HDR in a Negotiate response. */
verify_negotiate_response(struct auth_request * req,auth_session * sess,const char * hdr)665 static int verify_negotiate_response(struct auth_request *req, auth_session *sess,
666 const char *hdr)
667 {
668 char *duphdr = ne_strdup(hdr);
669 char *sep, *ptr = strchr(duphdr, ' ');
670 int ret;
671 ne_buffer *errmsg = NULL;
672
673 if (!ptr || strncmp(hdr, "Negotiate", ptr - duphdr) != 0) {
674 ne_set_error(sess->sess, _("Negotiate response verification failed: "
675 "invalid response header token"));
676 ne_free(duphdr);
677 return NE_ERROR;
678 }
679
680 ptr++;
681
682 if (strlen(ptr) == 0) {
683 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: No token in Negotiate response!\n");
684 ne_free(duphdr);
685 return NE_OK;
686 }
687
688 if ((sep = strchr(ptr, ',')) != NULL)
689 *sep = '\0';
690 if ((sep = strchr(ptr, ' ')) != NULL)
691 *sep = '\0';
692
693 NE_DEBUG(NE_DBG_HTTPAUTH, "gssapi: Negotiate response token [%s]\n", ptr);
694 ret = continue_negotiate(sess, ptr, &errmsg);
695 if (ret) {
696 ne_set_error(sess->sess, _("Negotiate response verification failure: %s"),
697 errmsg->data);
698 }
699
700 if (errmsg) ne_buffer_destroy(errmsg);
701 ne_free(duphdr);
702
703 return ret ? NE_ERROR : NE_OK;
704 }
705 #endif
706
707 #ifdef HAVE_SSPI
request_sspi(auth_session * sess,struct auth_request * request)708 static char *request_sspi(auth_session *sess, struct auth_request *request)
709 {
710 if (sess->sspi_token)
711 return ne_concat(sess->protocol->name, " ", sess->sspi_token, "\r\n", NULL);
712 else
713 return NULL;
714 }
715
continue_sspi(auth_session * sess,int ntlm,const char * hdr)716 static int continue_sspi(auth_session *sess, int ntlm, const char *hdr)
717 {
718 int status;
719 char *response = NULL;
720
721 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: SSPI challenge.\n");
722
723 if (!sess->sspi_context) {
724 status = ne_sspi_create_context(&sess->sspi_context, sess->sspi_host, ntlm);
725 if (status) {
726 return status;
727 }
728 }
729
730 status = ne_sspi_authenticate(sess->sspi_context, hdr, &response);
731 if (status) {
732 return status;
733 }
734
735 if (response && *response) {
736 sess->sspi_token = response;
737
738 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: SSPI challenge [%s]\n", sess->sspi_token);
739 }
740
741 return 0;
742 }
743
sspi_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,const char * uri,ne_buffer ** errmsg)744 static int sspi_challenge(auth_session *sess, int attempt,
745 struct auth_challenge *parms,
746 const char *uri, ne_buffer **errmsg)
747 {
748 int ntlm = ne_strcasecmp(parms->protocol->name, "NTLM") == 0;
749
750 return continue_sspi(sess, ntlm, parms->opaque);
751 }
752
verify_sspi(struct auth_request * req,auth_session * sess,const char * hdr)753 static int verify_sspi(struct auth_request *req, auth_session *sess,
754 const char *hdr)
755 {
756 int ntlm = ne_strncasecmp(hdr, "NTLM ", 5) == 0;
757 char *ptr = strchr(hdr, ' ');
758
759 if (!ptr) {
760 ne_set_error(sess->sess, _("SSPI response verification failed: "
761 "invalid response header token"));
762 return NE_ERROR;
763 }
764
765 while(*ptr == ' ')
766 ptr++;
767
768 if (*ptr == '\0') {
769 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: No token in SSPI response!\n");
770 return NE_OK;
771 }
772
773 return continue_sspi(sess, ntlm, ptr);
774 }
775
776 #endif
777
778 /* Parse the "domain" challenge parameter and set the domains array up
779 * in the session appropriately. */
parse_domain(auth_session * sess,const char * domain)780 static int parse_domain(auth_session *sess, const char *domain)
781 {
782 char *cp = ne_strdup(domain), *p = cp;
783 ne_uri base;
784 int invalid = 0;
785
786 memset(&base, 0, sizeof base);
787 ne_fill_server_uri(sess->sess, &base);
788
789 do {
790 char *token = ne_token(&p, ' ');
791 ne_uri rel, absolute;
792
793 if (ne_uri_parse(token, &rel) == 0) {
794 /* Resolve relative to the Request-URI. */
795 base.path = "/";
796 ne_uri_resolve(&base, &rel, &absolute);
797
798 /* Compare against the resolved path to check this URI has
799 * the same (scheme, host, port) components; ignore it
800 * otherwise: */
801 base.path = absolute.path;
802 if (absolute.path && ne_uri_cmp(&absolute, &base) == 0) {
803 sess->domains = ne_realloc(sess->domains,
804 ++sess->ndomains *
805 sizeof(*sess->domains));
806 sess->domains[sess->ndomains - 1] = absolute.path;
807 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Using domain %s from %s\n",
808 absolute.path, token);
809 absolute.path = NULL;
810 }
811 else {
812 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignoring domain %s\n",
813 token);
814 }
815
816 ne_uri_free(&absolute);
817 }
818 else {
819 invalid = 1;
820 }
821
822 ne_uri_free(&rel);
823
824 } while (p && !invalid);
825
826 if (invalid && sess->ndomains) {
827 free_domains(sess);
828 }
829
830 ne_free(cp);
831 base.path = NULL;
832 ne_uri_free(&base);
833
834 return invalid;
835 }
836
837 #ifdef HAVE_NTLM
838
request_ntlm(auth_session * sess,struct auth_request * request)839 static char *request_ntlm(auth_session *sess, struct auth_request *request)
840 {
841 char *token = ne__ntlm_getRequestToken(sess->ntlm_context);
842 if (token) {
843 char *req = ne_concat(sess->protocol->name, " ", token, "\r\n", NULL);
844 ne_free(token);
845 return req;
846 } else {
847 return NULL;
848 }
849 }
850
ntlm_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,const char * uri,ne_buffer ** errmsg)851 static int ntlm_challenge(auth_session *sess, int attempt,
852 struct auth_challenge *parms,
853 const char *uri, ne_buffer **errmsg)
854 {
855 int status;
856
857 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: NTLM challenge.\n");
858
859 if (!parms->opaque && (!sess->ntlm_context || (attempt > 1))) {
860 char password[ABUFSIZE];
861
862 if (get_credentials(sess, errmsg, attempt, parms, password)) {
863 /* Failed to get credentials */
864 return -1;
865 }
866
867 if (sess->ntlm_context) {
868 ne__ntlm_destroy_context(sess->ntlm_context);
869 sess->ntlm_context = NULL;
870 }
871
872 sess->ntlm_context = ne__ntlm_create_context(sess->username, password);
873
874 ne__strzero(password, sizeof password);
875 }
876
877 status = ne__ntlm_authenticate(sess->ntlm_context, parms->opaque);
878 if (status) {
879 return status;
880 }
881
882 return 0;
883 }
884 #endif /* HAVE_NTLM */
885
886 /* Returns the H(username:realm:password) used in the Digest H(A1)
887 * calculation. */
get_digest_h_urp(auth_session * sess,ne_buffer ** errmsg,unsigned int hash,int attempt,struct auth_challenge * parms)888 static char *get_digest_h_urp(auth_session *sess, ne_buffer **errmsg,
889 unsigned int hash, int attempt,
890 struct auth_challenge *parms)
891 {
892 char password[ABUFSIZE], *h_urp;
893
894 if (get_credentials(sess, errmsg, attempt, parms, password)) {
895 /* Failed to get credentials */
896 return NULL;
897 }
898
899 /* Calculate userhash for this (realm, username) if required.
900 * https://tools.ietf.org/html/rfc7616#section-3.4.4 */
901 if (parms->userhash == userhash_true) {
902 sess->userhash = ne_strhash(hash, sess->username, ":",
903 sess->realm, NULL);
904 }
905 else {
906 /* Without userhash, for usernames which need some kind of
907 * escaping, either: a) username* must be supported, which
908 * is known if the server sent userhash=false, *and* the
909 * caller has indicated the username really is UTF-8; or
910 * else b) the challenge is an error since the username
911 * cannot be sent safely. */
912 char *esc = ne_strparam("UTF-8", NULL, (unsigned char *)sess->username);
913
914 if (esc) {
915 if (parms->userhash == userhash_none
916 || parms->handler->new_creds == NULL) {
917 ne_free(esc);
918 challenge_error(errmsg, _("could not handle non-ASCII "
919 "username in Digest challenge"));
920 ne__strzero(password, sizeof password);
921 return NULL;
922 }
923 sess->username_star = esc;
924 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Using username* => %s\n", esc);
925 }
926 }
927
928 /* H(A1) calculation identical for 2069 or 2617/7616:
929 * https://tools.ietf.org/html/rfc7616#section-3.4.2 */
930 h_urp = ne_strhash(hash, sess->username, ":", sess->realm, ":",
931 password, NULL);
932 ne__strzero(password, sizeof password);
933
934 return h_urp;
935 }
936
937 /* Examine a digest challenge: return 0 if it is a valid Digest challenge,
938 * else non-zero. */
digest_challenge(auth_session * sess,int attempt,struct auth_challenge * parms,const char * uri,ne_buffer ** errmsg)939 static int digest_challenge(auth_session *sess, int attempt,
940 struct auth_challenge *parms,
941 const char *uri, ne_buffer **errmsg)
942 {
943 unsigned int hash;
944 char *p, *h_urp = NULL;
945
946 if (parms->alg == auth_alg_unknown) {
947 challenge_error(errmsg, _("unknown algorithm in Digest challenge"));
948 return -1;
949 }
950 else if (parms->alg == auth_alg_md5_sess && !parms->qop_auth) {
951 challenge_error(errmsg, _("incompatible algorithm in Digest challenge"));
952 return -1;
953 }
954 else if (parms->realm == NULL || parms->nonce == NULL) {
955 challenge_error(errmsg, _("missing parameter in Digest challenge"));
956 return -1;
957 }
958 else if (parms->stale && sess->realm == NULL) {
959 challenge_error(errmsg, _("initial Digest challenge was stale"));
960 return -1;
961 }
962 else if (parms->stale && (sess->alg != parms->alg
963 || strcmp(sess->realm, parms->realm))) {
964 /* With stale=true the realm and algorithm cannot change since these
965 * require re-hashing H(A1) which defeats the point. */
966 challenge_error(errmsg, _("stale Digest challenge with new algorithm or realm"));
967 return -1;
968 }
969 else if (!parms->got_qop
970 && (parms->handler->protomask & NE_AUTH_LEGACY_DIGEST) == 0) {
971 challenge_error(errmsg, _("legacy Digest challenge not supported"));
972 return -1;
973 }
974
975 hash = alg_to_hash[parms->alg];
976 p = ne_strhash(hash, "", NULL);
977 if (p == NULL) {
978 challenge_error(errmsg,
979 _("%s algorithm in Digest challenge not supported"),
980 alg_to_name[parms->alg]);
981 return -1;
982 }
983 ne_free(p);
984
985 if (!parms->stale) {
986 /* Non-stale challenge: clear session and request credentials. */
987 clean_session(sess);
988
989 /* The domain parameter must be parsed after the session is
990 * cleaned; ignore domain for proxy auth. */
991 if (parms->domain && sess->spec == &ah_server_class
992 && parse_domain(sess, parms->domain)) {
993 challenge_error(errmsg, _("could not parse domain in Digest challenge"));
994 return -1;
995 }
996
997 sess->realm = ne_strdup(parms->realm);
998 sess->alg = parms->alg;
999 sess->cnonce = get_cnonce();
1000
1001 h_urp = get_digest_h_urp(sess, errmsg, hash, attempt, parms);
1002 if (h_urp == NULL) {
1003 return -1;
1004 }
1005 }
1006 else {
1007 /* Stale challenge: accept a new nonce or opaque. */
1008 if (sess->nonce) ne_free(sess->nonce);
1009 if (sess->opaque && parms->opaque) ne_free(sess->opaque);
1010 }
1011
1012 sess->nonce = ne_strdup(parms->nonce);
1013 if (parms->opaque) {
1014 sess->opaque = ne_strdup(parms->opaque);
1015 }
1016
1017 if (parms->got_qop) {
1018 /* What type of qop are we to apply to the message? */
1019 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got qop, using 2617-style.\n");
1020 sess->nonce_count = 0;
1021 sess->qop = auth_qop_auth;
1022 } else {
1023 /* No qop at all/ */
1024 sess->qop = auth_qop_none;
1025 }
1026
1027 if (h_urp) {
1028 if (sess->alg == auth_alg_md5_sess || sess->alg == auth_alg_sha256_sess
1029 || sess->alg == auth_alg_sha512_256_sess) {
1030 sess->h_a1 = ne_strhash(hash, h_urp, ":", sess->nonce, ":",
1031 sess->cnonce, NULL);
1032 ne_free(h_urp);
1033 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Session H(A1) is [%s]\n", sess->h_a1);
1034 }
1035 else {
1036 sess->h_a1 = h_urp;
1037 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: H(A1) is [%s]\n", sess->h_a1);
1038 }
1039 }
1040
1041 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Accepting digest challenge.\n");
1042
1043 return 0;
1044 }
1045
1046 /* Returns non-zero if given Request-URI is inside the authentication
1047 * domain defined for the session. */
inside_domain(auth_session * sess,const char * req_uri)1048 static int inside_domain(auth_session *sess, const char *req_uri)
1049 {
1050 int inside = 0;
1051 size_t n;
1052 ne_uri uri;
1053
1054 /* Parse the Request-URI; it will be an absoluteURI if using a
1055 * proxy, and possibly '*'. */
1056 if (strcmp(req_uri, "*") == 0 || ne_uri_parse(req_uri, &uri) != 0) {
1057 /* Presume outside the authentication domain. */
1058 return 0;
1059 }
1060
1061 for (n = 0; n < sess->ndomains && !inside; n++) {
1062 const char *d = sess->domains[n];
1063
1064 inside = strncmp(uri.path, d, strlen(d)) == 0;
1065 }
1066
1067 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: '%s' is inside auth domain: %d.\n",
1068 uri.path, inside);
1069 ne_uri_free(&uri);
1070
1071 return inside;
1072 }
1073
1074 /* Return Digest authentication credentials header value for the given
1075 * session. */
request_digest(auth_session * sess,struct auth_request * req)1076 static char *request_digest(auth_session *sess, struct auth_request *req)
1077 {
1078 char *h_a2, *response;
1079 char nc_value[9] = {0};
1080 const char *qop_value = "auth"; /* qop-value */
1081 ne_buffer *ret;
1082 unsigned int hash = alg_to_hash[sess->alg];
1083
1084 /* Do not submit credentials if an auth domain is defined and this
1085 * request-uri fails outside it. */
1086 if (sess->ndomains && !inside_domain(sess, req->uri)) {
1087 return NULL;
1088 }
1089
1090 /* H(A2): https://tools.ietf.org/html/rfc7616#section-3.4.3 */
1091 h_a2 = ne_strhash(hash, req->method, ":", req->uri, NULL);
1092 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: H(A2): %s\n", h_a2);
1093
1094 /* Calculate the 'response' to the Digest challenge to send the
1095 * server in the request. */
1096 if (sess->qop == auth_qop_none) {
1097 /* RFC 2069 case,
1098 * https://tools.ietf.org/html/rfc2069#section-2.1.2 */
1099 response = ne_strhash(hash, sess->h_a1, ":", sess->nonce,
1100 ":", h_a2, NULL);
1101 } else {
1102 /* For RFC 2617/7616-style; part of this calculation will be
1103 * needed again when verifying the (Proxy-)Authentication-Info
1104 * response header; that part is cached in sess->response_rhs.
1105 * https://tools.ietf.org/html/rfc7616#section-3.4.1 */
1106 sess->nonce_count++;
1107 ne_snprintf(nc_value, 9, "%08x", sess->nonce_count);
1108
1109 if (sess->response_rhs) ne_free(sess->response_rhs);
1110 sess->response_rhs = ne_concat(sess->nonce, ":",
1111 nc_value, ":", sess->cnonce, ":",
1112 qop_value, NULL);
1113 response = ne_strhash(hash, sess->h_a1, ":",
1114 sess->response_rhs, ":", h_a2, NULL);
1115 }
1116
1117 ret = ne_buffer_create();
1118
1119 ne_buffer_concat(ret,
1120 "Digest realm=\"", sess->realm, "\", "
1121 "nonce=\"", sess->nonce, "\", "
1122 "uri=\"", req->uri, "\", "
1123 "response=\"", response, "\", "
1124 "algorithm=\"", alg_to_name[sess->alg], "\"",
1125 NULL);
1126 if (sess->username_star) {
1127 ne_buffer_concat(ret, ", username*=", sess->username_star, NULL);
1128 }
1129 else {
1130 ne_buffer_concat(ret, ", username=\"",
1131 sess->userhash ? sess->userhash : sess->username,
1132 "\"", NULL);
1133 }
1134
1135 ne_free(response);
1136 ne_free(h_a2);
1137
1138 if (sess->opaque != NULL) {
1139 ne_buffer_concat(ret, ", opaque=\"", sess->opaque, "\"", NULL);
1140 }
1141
1142 if (sess->qop != auth_qop_none) {
1143 /* Add in cnonce and nc-value fields */
1144 ne_buffer_concat(ret, ", cnonce=\"", sess->cnonce, "\", "
1145 "nc=", nc_value, ", "
1146 "qop=\"", qop_value, "\"", NULL);
1147 }
1148 if (sess->userhash) {
1149 ne_buffer_czappend(ret, ", userhash=true");
1150 }
1151
1152 ne_buffer_zappend(ret, "\r\n");
1153
1154 return ne_buffer_finish(ret);
1155 }
1156
1157 /* Parse line of comma-separated key-value pairs. If 'ischall' == 1,
1158 * then also return a leading space-separated token, as *value ==
1159 * NULL. Otherwise, if return value is 0, *key and *value will be
1160 * non-NULL. If return value is non-zero, parsing has ended. If
1161 * 'sep' is non-NULL and ischall is 1, the separator character is
1162 * written to *sep when a challenge is parsed. */
tokenize(char ** hdr,char ** key,char ** value,char * sep,int ischall)1163 static int tokenize(char **hdr, char **key, char **value, char *sep,
1164 int ischall)
1165 {
1166 char *pnt = *hdr;
1167 enum { BEFORE_EQ, AFTER_EQ, AFTER_EQ_QUOTED } state = BEFORE_EQ;
1168
1169 if (**hdr == '\0')
1170 return 1;
1171
1172 *key = NULL;
1173
1174 do {
1175 switch (state) {
1176 case BEFORE_EQ:
1177 if (*pnt == '=') {
1178 if (*key == NULL)
1179 return -1;
1180 *pnt = '\0';
1181 *value = pnt + 1;
1182 state = AFTER_EQ;
1183 } else if ((*pnt == ' ' || *pnt == ',')
1184 && ischall && *key != NULL) {
1185 *value = NULL;
1186 if (sep) *sep = *pnt;
1187 *pnt = '\0';
1188 *hdr = pnt + 1;
1189 return 0;
1190 } else if (*key == NULL && strchr(" \r\n\t", *pnt) == NULL) {
1191 *key = pnt;
1192 }
1193 break;
1194 case AFTER_EQ:
1195 if (*pnt == ',') {
1196 *pnt = '\0';
1197 *hdr = pnt + 1;
1198 return 0;
1199 } else if (*pnt == '\"') {
1200 state = AFTER_EQ_QUOTED;
1201 }
1202 break;
1203 case AFTER_EQ_QUOTED:
1204 if (*pnt == '\"') {
1205 state = AFTER_EQ;
1206 *pnt = '\0';
1207 }
1208 break;
1209 }
1210 } while (*++pnt != '\0');
1211
1212 if (state == BEFORE_EQ && ischall && *key != NULL) {
1213 *value = NULL;
1214 if (sep) *sep = '\0';
1215 }
1216
1217 *hdr = pnt;
1218
1219 /* End of string: */
1220 return 0;
1221 }
1222
1223 /* Pass this the value of the 'Authentication-Info:' header field, if
1224 * one is received.
1225 * Returns:
1226 * 0 if it gives a valid authentication for the server
1227 * non-zero otherwise (don't believe the response in this case!).
1228 */
verify_digest_response(struct auth_request * req,auth_session * sess,const char * value)1229 static int verify_digest_response(struct auth_request *req, auth_session *sess,
1230 const char *value)
1231 {
1232 char *hdr, *pnt, *key, *val;
1233 auth_qop qop = auth_qop_none;
1234 char *nextnonce, *rspauth, *cnonce, *nc, *qop_value;
1235 unsigned int nonce_count;
1236 int ret = NE_OK;
1237
1238 nextnonce = rspauth = cnonce = nc = qop_value = NULL;
1239
1240 pnt = hdr = ne_strdup(value);
1241
1242 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got Auth-Info header: %s\n", value);
1243
1244 while (tokenize(&pnt, &key, &val, NULL, 0) == 0) {
1245 val = ne_shave(val, "\"");
1246
1247 if (ne_strcasecmp(key, "qop") == 0) {
1248 qop_value = val;
1249 if (ne_strcasecmp(val, "auth") == 0) {
1250 qop = auth_qop_auth;
1251 } else {
1252 qop = auth_qop_none;
1253 }
1254 } else if (ne_strcasecmp(key, "nextnonce") == 0) {
1255 nextnonce = val;
1256 } else if (ne_strcasecmp(key, "rspauth") == 0) {
1257 rspauth = val;
1258 } else if (ne_strcasecmp(key, "cnonce") == 0) {
1259 cnonce = val;
1260 } else if (ne_strcasecmp(key, "nc") == 0) {
1261 nc = val;
1262 }
1263 }
1264
1265 if (qop == auth_qop_none) {
1266 /* The 2069-style A-I header only has the entity and nextnonce
1267 * parameters. */
1268 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: 2069-style A-I header.\n");
1269 }
1270 else if (!rspauth || !cnonce || !nc) {
1271 ret = NE_ERROR;
1272 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1273 "missing parameters"));
1274 }
1275 else if (strcmp(cnonce, sess->cnonce) != 0) {
1276 ret = NE_ERROR;
1277 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1278 "client nonce mismatch"));
1279 }
1280 else if (nc) {
1281 char *ptr;
1282
1283 errno = 0;
1284 nonce_count = strtoul(nc, &ptr, 16);
1285 if (*ptr != '\0' || errno) {
1286 ret = NE_ERROR;
1287 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1288 "could not parse nonce count"));
1289 }
1290 else if (nonce_count != sess->nonce_count) {
1291 ret = NE_ERROR;
1292 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1293 "nonce count mismatch (%u not %u)"),
1294 nonce_count, sess->nonce_count);
1295 }
1296 }
1297
1298 /* Finally, for qop=auth cases, if everything else is OK, verify
1299 * the response-digest field. */
1300 if (qop == auth_qop_auth && ret == NE_OK) {
1301 char *h_a2, *response;
1302 unsigned int hash = alg_to_hash[sess->alg];
1303
1304 h_a2 = ne_strhash(hash, ":", req->uri, NULL);
1305 response = ne_strhash(hash, sess->h_a1, ":", sess->response_rhs,
1306 ":", h_a2, NULL);
1307 ne_free(h_a2);
1308 ne_free(sess->response_rhs);
1309 sess->response_rhs = NULL;
1310
1311 /* And... do they match? */
1312 ret = ne_strcasecmp(response, rspauth) == 0 ? NE_OK : NE_ERROR;
1313
1314 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: response-digest match: %s "
1315 "(expected [%s] vs actual [%s])\n",
1316 ret == NE_OK ? "yes" : "no", response, rspauth);
1317
1318 if (ret) {
1319 ne_set_error(sess->sess, _("Digest mutual authentication failure: "
1320 "request-digest mismatch"));
1321 }
1322
1323 ne_free(response);
1324 }
1325
1326 /* Check for a nextnonce */
1327 if (nextnonce != NULL) {
1328 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Found nextnonce of [%s].\n", nextnonce);
1329 ne_free(sess->nonce);
1330 sess->nonce = ne_strdup(nextnonce);
1331 sess->nonce_count = 0;
1332 }
1333
1334 ne_free(hdr);
1335
1336 return ret;
1337 }
1338
1339 static const struct auth_protocol protocols[] = {
1340 { NE_AUTH_BASIC, 10, "Basic",
1341 basic_challenge, request_basic, NULL,
1342 0 },
1343 { NE_AUTH_DIGEST, 20, "Digest",
1344 digest_challenge, request_digest, verify_digest_response,
1345 0 },
1346 #ifdef HAVE_GSSAPI
1347 { NE_AUTH_GSSAPI_ONLY, 30, "Negotiate",
1348 negotiate_challenge, request_negotiate, verify_negotiate_response,
1349 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1350 #endif
1351 #ifdef HAVE_SSPI
1352 { NE_AUTH_NTLM, 30, "NTLM",
1353 sspi_challenge, request_sspi, NULL,
1354 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1355 { NE_AUTH_SSPI, 30, "Negotiate",
1356 sspi_challenge, request_sspi, verify_sspi,
1357 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1358 #endif
1359 #ifdef HAVE_NTLM
1360 { NE_AUTH_NTLM, 30, "NTLM",
1361 ntlm_challenge, request_ntlm, NULL,
1362 AUTH_FLAG_OPAQUE_PARAM|AUTH_FLAG_VERIFY_NON40x|AUTH_FLAG_CONN_AUTH },
1363 #endif
1364 { 0 }
1365 };
1366
1367 /* Insert a new auth challenge 'chall' into list of challenges 'list'.
1368 * The challenge list is kept in sorted order of strength, with
1369 * highest strength first. */
insert_challenge(struct auth_challenge ** list,struct auth_challenge * chall)1370 static void insert_challenge(struct auth_challenge **list, struct auth_challenge *chall)
1371 {
1372 struct auth_challenge *cur, *prev;
1373
1374 for (cur = *list, prev = NULL; cur != NULL;
1375 prev = cur, cur = cur->next) {
1376 if (chall->protocol->strength > cur->protocol->strength
1377 || (cur->protocol->id == NE_AUTH_DIGEST
1378 && chall->protocol->id == NE_AUTH_DIGEST
1379 && chall->alg > cur->alg)) {
1380 break;
1381 }
1382 }
1383
1384 if (prev) {
1385 chall->next = prev->next;
1386 prev->next = chall;
1387 }
1388 else {
1389 chall->next = *list;
1390 *list = chall;
1391 }
1392 }
1393
challenge_error(ne_buffer ** errbuf,const char * fmt,...)1394 static void challenge_error(ne_buffer **errbuf, const char *fmt, ...)
1395 {
1396 char err[128];
1397 va_list ap;
1398 size_t len;
1399
1400 va_start(ap, fmt);
1401 len = ne_vsnprintf(err, sizeof err, fmt, ap);
1402 va_end(ap);
1403
1404 if (*errbuf == NULL) {
1405 *errbuf = ne_buffer_create();
1406 ne_buffer_append(*errbuf, err, len);
1407 }
1408 else {
1409 ne_buffer_concat(*errbuf, ", ", err, NULL);
1410 }
1411 }
1412
1413 /* Passed the value of a "(Proxy,WWW)-Authenticate: " header field.
1414 * Returns 0 if valid challenge was accepted; non-zero if no valid
1415 * challenge was found. */
auth_challenge(auth_session * sess,int attempt,const char * uri,const char * value)1416 static int auth_challenge(auth_session *sess, int attempt, const char *uri,
1417 const char *value)
1418 {
1419 char *pnt, *key, *val, *hdr, sep;
1420 struct auth_challenge *chall = NULL, *challenges = NULL;
1421 ne_buffer *errmsg = NULL;
1422
1423 pnt = hdr = ne_strdup(value);
1424
1425 /* The header value may be made up of one or more challenges. We
1426 * split it down into attribute-value pairs, then search for
1427 * schemes in the pair keys. */
1428
1429 while (!tokenize(&pnt, &key, &val, &sep, 1)) {
1430
1431 if (val == NULL) {
1432 /* Special case, challenge token, not key=value pair: */
1433 const struct auth_protocol *proto = NULL;
1434 struct auth_handler *hdl;
1435 size_t n;
1436
1437 /* Accumulated challenge is now completed and can be
1438 * inserted into the list. */
1439 if (chall) {
1440 insert_challenge(&challenges, chall);
1441 chall = NULL;
1442 }
1443
1444 for (hdl = sess->handlers; hdl; hdl = hdl->next) {
1445 for (n = 0; protocols[n].id; n++) {
1446 if (protocols[n].id & hdl->protomask
1447 && ne_strcasecmp(key, protocols[n].name) == 0) {
1448 proto = &protocols[n];
1449 break;
1450 }
1451 }
1452 if (proto) break;
1453 }
1454
1455 if (proto == NULL) {
1456 /* Ignore this challenge. */
1457 challenge_error(&errmsg, _("ignored %s challenge"), key);
1458 continue;
1459 }
1460
1461 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got '%s' challenge.\n", proto->name);
1462 chall = ne_calloc(sizeof *chall);
1463 chall->protocol = proto;
1464 chall->handler = hdl;
1465
1466 if ((proto->flags & AUTH_FLAG_OPAQUE_PARAM) && sep == ' ') {
1467 /* Cope with the fact that the unquoted base64
1468 * parameter token doesn't match the 2617 auth-param
1469 * grammar: */
1470 chall->opaque = ne_shave(ne_token(&pnt, ','), " \t");
1471 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: %s opaque parameter '%s'\n",
1472 proto->name, chall->opaque);
1473 if (!pnt) break; /* stop parsing at end-of-string. */
1474 }
1475 continue;
1476 } else if (chall == NULL) {
1477 /* Ignore pairs for an unknown challenge. */
1478 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignored parameter: %s = %s\n", key, val);
1479 continue;
1480 }
1481
1482 /* Strip quotes off value. */
1483 val = ne_shave(val, "\"'");
1484
1485 if (ne_strcasecmp(key, "realm") == 0) {
1486 chall->realm = val;
1487 } else if (ne_strcasecmp(key, "nonce") == 0) {
1488 chall->nonce = val;
1489 } else if (ne_strcasecmp(key, "opaque") == 0) {
1490 chall->opaque = val;
1491 } else if (ne_strcasecmp(key, "stale") == 0) {
1492 /* Truth value */
1493 chall->stale = (ne_strcasecmp(val, "true") == 0);
1494 } else if (ne_strcasecmp(key, "algorithm") == 0) {
1495 if (ne_strcasecmp(val, "md5") == 0) {
1496 chall->alg = auth_alg_md5;
1497 }
1498 else if (ne_strcasecmp(val, "md5-sess") == 0) {
1499 chall->alg = auth_alg_md5_sess;
1500 }
1501 else if (ne_strcasecmp(val, "sha-256") == 0) {
1502 chall->alg = auth_alg_sha256;
1503 }
1504 else if (ne_strcasecmp(val, "sha-256-sess") == 0) {
1505 chall->alg = auth_alg_sha256_sess;
1506 }
1507 else if (ne_strcasecmp(val, "sha-512-256") == 0) {
1508 chall->alg = auth_alg_sha512_256;
1509 }
1510 else if (ne_strcasecmp(val, "sha-512-256-sess") == 0) {
1511 chall->alg = auth_alg_sha512_256_sess;
1512 }
1513 else {
1514 chall->alg = auth_alg_unknown;
1515 }
1516 } else if (ne_strcasecmp(key, "qop") == 0) {
1517 /* iterate over each token in the value */
1518 do {
1519 const char *tok = ne_shave(ne_token(&val, ','), " \t");
1520
1521 if (ne_strcasecmp(tok, "auth") == 0) {
1522 chall->qop_auth = 1;
1523 }
1524 } while (val);
1525
1526 chall->got_qop = chall->qop_auth;
1527 }
1528 else if (ne_strcasecmp(key, "domain") == 0) {
1529 chall->domain = val;
1530 }
1531 else if (ne_strcasecmp(key, "userhash") == 0) {
1532 if (strcmp(val, "true") == 0)
1533 chall->userhash = userhash_true;
1534 else if (strcmp(val, "false") == 0)
1535 chall->userhash = userhash_false;
1536 else
1537 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Ignored bogus userhash value '%s'\n", val);
1538 }
1539 }
1540
1541 /* Insert the in-flight challenge (if any). */
1542 if (chall) insert_challenge(&challenges, chall);
1543
1544 sess->protocol = NULL;
1545
1546 /* Iterate through the challenge list (which is sorted from
1547 * strongest to weakest) attempting to accept each one. */
1548 for (chall = challenges; chall != NULL; chall = chall->next) {
1549 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Trying %s challenge...\n",
1550 chall->protocol->name);
1551 if (chall->protocol->challenge(sess, attempt, chall, uri, &errmsg) == 0) {
1552 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Accepted %s challenge.\n",
1553 chall->protocol->name);
1554 sess->protocol = chall->protocol;
1555 break;
1556 }
1557 }
1558
1559 if (!sess->protocol) {
1560 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: No challenges accepted.\n");
1561 ne_set_error(sess->sess, _(sess->spec->error_noauth),
1562 errmsg ? errmsg->data : _("could not parse challenge"));
1563 }
1564
1565 while (challenges != NULL) {
1566 chall = challenges->next;
1567 ne_free(challenges);
1568 challenges = chall;
1569 }
1570
1571 ne_free(hdr);
1572 if (errmsg) ne_buffer_destroy(errmsg);
1573
1574 return !(sess->protocol != NULL);
1575 }
1576
ah_create(ne_request * req,void * session,const char * method,const char * uri)1577 static void ah_create(ne_request *req, void *session, const char *method,
1578 const char *uri)
1579 {
1580 auth_session *sess = session;
1581 int is_connect = strcmp(method, "CONNECT") == 0;
1582
1583 if (sess->context == AUTH_ANY ||
1584 (is_connect && sess->context == AUTH_CONNECT) ||
1585 (!is_connect && sess->context == AUTH_NOTCONNECT)) {
1586 struct auth_request *areq = ne_calloc(sizeof *areq);
1587 struct auth_handler *hdl;
1588
1589 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Create for %s\n", sess->spec->resp_hdr);
1590
1591 areq->method = method;
1592 areq->uri = uri;
1593 areq->request = req;
1594
1595 ne_set_request_private(req, sess->spec->id, areq);
1596
1597 /* For each new request, reset the attempt counter in every
1598 * registered handler. */
1599 for (hdl = sess->handlers; hdl; hdl = hdl->next) {
1600 hdl->attempt = 0;
1601 }
1602 }
1603 }
1604
1605
ah_pre_send(ne_request * r,void * cookie,ne_buffer * request)1606 static void ah_pre_send(ne_request *r, void *cookie, ne_buffer *request)
1607 {
1608 auth_session *sess = cookie;
1609 struct auth_request *req = ne_get_request_private(r, sess->spec->id);
1610
1611 if (sess->protocol && req) {
1612 char *value;
1613
1614 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Sending '%s' response.\n",
1615 sess->protocol->name);
1616
1617 value = sess->protocol->response(sess, req);
1618
1619 if (value != NULL) {
1620 ne_buffer_concat(request, sess->spec->req_hdr, ": ", value, NULL);
1621 ne_free(value);
1622 }
1623 }
1624
1625 }
1626
ah_post_send(ne_request * req,void * cookie,const ne_status * status)1627 static int ah_post_send(ne_request *req, void *cookie, const ne_status *status)
1628 {
1629 auth_session *sess = cookie;
1630 struct auth_request *areq = ne_get_request_private(req, sess->spec->id);
1631 const char *auth_hdr, *auth_info_hdr;
1632 int ret = NE_OK;
1633
1634 if (!areq) return NE_OK;
1635
1636 auth_hdr = ne_get_response_header(req, sess->spec->resp_hdr);
1637 auth_info_hdr = ne_get_response_header(req, sess->spec->resp_info_hdr);
1638
1639 if (sess->context == AUTH_CONNECT && status->code == 401 && !auth_hdr) {
1640 /* Some broken proxies issue a 401 as a proxy auth challenge
1641 * to a CONNECT request; handle this here. */
1642 auth_hdr = ne_get_response_header(req, "WWW-Authenticate");
1643 auth_info_hdr = NULL;
1644 }
1645
1646 #ifdef HAVE_GSSAPI
1647 /* whatever happens: forget the GSSAPI token cached thus far */
1648 if (sess->gssapi_token) {
1649 ne_free(sess->gssapi_token);
1650 sess->gssapi_token = NULL;
1651 }
1652 #endif
1653
1654 #ifdef HAVE_SSPI
1655 /* whatever happens: forget the SSPI token cached thus far */
1656 if (sess->sspi_token) {
1657 ne_free(sess->sspi_token);
1658 sess->sspi_token = NULL;
1659 }
1660 #endif
1661
1662 NE_DEBUG(NE_DBG_HTTPAUTH,
1663 "auth: Post-send (#%d), code is %d (want %d), %s is %s\n",
1664 areq->attempt, status->code, sess->spec->status_code,
1665 sess->spec->resp_hdr, auth_hdr ? auth_hdr : "(none)");
1666 if (auth_info_hdr && sess->protocol && sess->protocol->verify
1667 && (sess->protocol->flags & AUTH_FLAG_VERIFY_NON40x) == 0) {
1668 ret = sess->protocol->verify(areq, sess, auth_info_hdr);
1669 }
1670 else if (sess->protocol && sess->protocol->verify
1671 && (sess->protocol->flags & AUTH_FLAG_VERIFY_NON40x)
1672 && (status->klass == 2 || status->klass == 3)
1673 && auth_hdr) {
1674 ret = sess->protocol->verify(areq, sess, auth_hdr);
1675 }
1676 else if ((status->code == sess->spec->status_code ||
1677 (status->code == 401 && sess->context == AUTH_CONNECT)) &&
1678 auth_hdr) {
1679 /* note above: allow a 401 in response to a CONNECT request
1680 * from a proxy since some buggy proxies send that. */
1681 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Got challenge (code %d).\n", status->code);
1682 if (!auth_challenge(sess, areq->attempt++, areq->uri, auth_hdr)) {
1683 ret = NE_RETRY;
1684 } else {
1685 clean_session(sess);
1686 ret = sess->spec->fail_code;
1687 }
1688
1689 /* Set or clear the conn-auth flag according to whether this
1690 * was an accepted challenge for a borked protocol. */
1691 ne_set_session_flag(sess->sess, NE_SESSFLAG_CONNAUTH,
1692 sess->protocol
1693 && (sess->protocol->flags & AUTH_FLAG_CONN_AUTH));
1694 }
1695
1696 #ifdef HAVE_SSPI
1697 /* Clear the SSPI context after successful authentication. */
1698 if (status->code != sess->spec->status_code && sess->sspi_context) {
1699 ne_sspi_clear_context(sess->sspi_context);
1700 }
1701 #endif
1702
1703 return ret;
1704 }
1705
ah_destroy(ne_request * req,void * session)1706 static void ah_destroy(ne_request *req, void *session)
1707 {
1708 auth_session *sess = session;
1709 struct auth_request *areq = ne_get_request_private(req, sess->spec->id);
1710
1711 if (areq) {
1712 ne_free(areq);
1713 }
1714 }
1715
free_auth(void * cookie)1716 static void free_auth(void *cookie)
1717 {
1718 auth_session *sess = cookie;
1719 struct auth_handler *hdl, *next;
1720
1721 #ifdef HAVE_GSSAPI
1722 if (sess->gssname != GSS_C_NO_NAME) {
1723 unsigned int major;
1724 gss_release_name(&major, &sess->gssname);
1725 }
1726 #endif
1727
1728 for (hdl = sess->handlers; hdl; hdl = next) {
1729 next = hdl->next;
1730 ne_free(hdl);
1731 }
1732
1733 clean_session(sess);
1734 #ifdef HAVE_SSPI
1735 if (sess->sspi_host) ne_free(sess->sspi_host);
1736 sess->sspi_host = NULL;
1737 #endif
1738 ne_free(sess);
1739 }
1740
auth_register(ne_session * sess,int isproxy,unsigned protomask,const struct auth_class * ahc,const char * id,ne_auth_creds old_creds,ne_auth_provide new_creds,void * userdata)1741 static void auth_register(ne_session *sess, int isproxy, unsigned protomask,
1742 const struct auth_class *ahc, const char *id,
1743 ne_auth_creds old_creds, ne_auth_provide new_creds,
1744 void *userdata)
1745 {
1746 auth_session *ahs;
1747 struct auth_handler **hdl;
1748
1749 /* Handle the _ALL and _DEFAULT protocol masks: */
1750 if ((protomask & NE_AUTH_ALL) == NE_AUTH_ALL) {
1751 protomask |= NE_AUTH_BASIC | NE_AUTH_DIGEST | NE_AUTH_NEGOTIATE;
1752 }
1753 else if ((protomask & NE_AUTH_DEFAULT) == NE_AUTH_DEFAULT) {
1754 protomask |= NE_AUTH_BASIC | NE_AUTH_DIGEST;
1755
1756 if (strcmp(ne_get_scheme(sess), "https") == 0 || isproxy) {
1757 protomask |= NE_AUTH_NEGOTIATE;
1758 }
1759 }
1760
1761 /* For backwards-compatibility with older releases where DIGEST
1762 * used to be defined as WEAKEST, if only LEGACY_DIGEST is given,
1763 * that implies DIGEST|LEGACY_DIGEST. */
1764 if ((protomask & (NE_AUTH_LEGACY_DIGEST|NE_AUTH_DIGEST)) == NE_AUTH_LEGACY_DIGEST) {
1765 NE_DEBUG(NE_DBG_HTTPAUTH, "auth: Legacy Digest support compatibility mode.\n");
1766 protomask |= NE_AUTH_DIGEST;
1767 }
1768
1769 if ((protomask & NE_AUTH_NEGOTIATE) == NE_AUTH_NEGOTIATE) {
1770 /* Map NEGOTIATE to NTLM | GSSAPI. */
1771 protomask |= NE_AUTH_GSSAPI | NE_AUTH_NTLM;
1772 }
1773
1774 if ((protomask & NE_AUTH_GSSAPI) == NE_AUTH_GSSAPI) {
1775 /* Map GSSAPI to GSSAPI_ONLY | SSPI. */
1776 protomask |= NE_AUTH_GSSAPI_ONLY | NE_AUTH_SSPI;
1777 }
1778
1779 ahs = ne_get_session_private(sess, id);
1780 if (ahs == NULL) {
1781 ahs = ne_calloc(sizeof *ahs);
1782
1783 ahs->sess = sess;
1784 ahs->spec = ahc;
1785
1786 if (strcmp(ne_get_scheme(sess), "https") == 0) {
1787 ahs->context = isproxy ? AUTH_CONNECT : AUTH_NOTCONNECT;
1788 } else {
1789 ahs->context = AUTH_ANY;
1790 }
1791
1792 /* Register hooks */
1793 ne_hook_create_request(sess, ah_create, ahs);
1794 ne_hook_pre_send(sess, ah_pre_send, ahs);
1795 ne_hook_post_send(sess, ah_post_send, ahs);
1796 ne_hook_destroy_request(sess, ah_destroy, ahs);
1797 ne_hook_destroy_session(sess, free_auth, ahs);
1798
1799 ne_set_session_private(sess, id, ahs);
1800 }
1801
1802 #ifdef HAVE_GSSAPI
1803 if ((protomask & NE_AUTH_GSSAPI_ONLY) && ahs->gssname == GSS_C_NO_NAME) {
1804 ne_uri uri = {0};
1805
1806 if (isproxy)
1807 ne_fill_proxy_uri(sess, &uri);
1808 else
1809 ne_fill_server_uri(sess, &uri);
1810
1811 get_gss_name(&ahs->gssname, uri.host);
1812
1813 ne_uri_free(&uri);
1814 }
1815 #endif
1816 #ifdef HAVE_SSPI
1817 if ((protomask & (NE_AUTH_NTLM|NE_AUTH_SSPI)) && !ahs->sspi_host) {
1818 ne_uri uri = {0};
1819
1820 if (isproxy)
1821 ne_fill_proxy_uri(sess, &uri);
1822 else
1823 ne_fill_server_uri(sess, &uri);
1824
1825 ahs->sspi_host = uri.host;
1826 uri.host = NULL;
1827
1828 ne_uri_free(&uri);
1829 }
1830 #endif
1831
1832 /* Find the end of the handler list, and add a new one. */
1833 hdl = &ahs->handlers;
1834 while (*hdl)
1835 hdl = &(*hdl)->next;
1836
1837 *hdl = ne_malloc(sizeof **hdl);
1838 (*hdl)->protomask = protomask;
1839 (*hdl)->old_creds = old_creds;
1840 (*hdl)->new_creds = new_creds;
1841 (*hdl)->userdata = userdata;
1842 (*hdl)->next = NULL;
1843 (*hdl)->attempt = 0;
1844 }
1845
ne_set_server_auth(ne_session * sess,ne_auth_creds creds,void * userdata)1846 void ne_set_server_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1847 {
1848 auth_register(sess, 0, NE_AUTH_DEFAULT, &ah_server_class, HOOK_SERVER_ID,
1849 creds, NULL, userdata);
1850 }
1851
ne_set_proxy_auth(ne_session * sess,ne_auth_creds creds,void * userdata)1852 void ne_set_proxy_auth(ne_session *sess, ne_auth_creds creds, void *userdata)
1853 {
1854 auth_register(sess, 1, NE_AUTH_DEFAULT, &ah_proxy_class, HOOK_PROXY_ID,
1855 creds, NULL, userdata);
1856 }
1857
ne_add_server_auth(ne_session * sess,unsigned protocol,ne_auth_creds creds,void * userdata)1858 void ne_add_server_auth(ne_session *sess, unsigned protocol,
1859 ne_auth_creds creds, void *userdata)
1860 {
1861 auth_register(sess, 0, protocol, &ah_server_class, HOOK_SERVER_ID,
1862 creds, NULL, userdata);
1863 }
1864
ne_add_proxy_auth(ne_session * sess,unsigned protocol,ne_auth_creds creds,void * userdata)1865 void ne_add_proxy_auth(ne_session *sess, unsigned protocol,
1866 ne_auth_creds creds, void *userdata)
1867 {
1868 auth_register(sess, 1, protocol, &ah_proxy_class, HOOK_PROXY_ID,
1869 creds, NULL, userdata);
1870 }
1871
ne_add_auth(ne_session * sess,unsigned protocol,ne_auth_provide new_creds,void * userdata)1872 void ne_add_auth(ne_session *sess, unsigned protocol,
1873 ne_auth_provide new_creds, void *userdata)
1874 {
1875 auth_register(sess, 0, protocol, &ah_proxy_class, HOOK_PROXY_ID,
1876 NULL, new_creds, userdata);
1877 auth_register(sess, 0, protocol, &ah_server_class, HOOK_SERVER_ID,
1878 NULL, new_creds, userdata);
1879 }
1880
ne_forget_auth(ne_session * sess)1881 void ne_forget_auth(ne_session *sess)
1882 {
1883 auth_session *as;
1884 if ((as = ne_get_session_private(sess, HOOK_SERVER_ID)) != NULL)
1885 clean_session(as);
1886 if ((as = ne_get_session_private(sess, HOOK_PROXY_ID)) != NULL)
1887 clean_session(as);
1888 }
1889
1890