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