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