1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 2012 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at http://curl.haxx.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * RFC2195 CRAM-MD5 authentication
22 * RFC2617 Basic and Digest Access Authentication
23 * RFC2831 DIGEST-MD5 authentication
24 * RFC4422 Simple Authentication and Security Layer (SASL)
25 * RFC4616 PLAIN authentication
26 * RFC6749 OAuth 2.0 Authorization Framework
27 * Draft LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
28 *
29 ***************************************************************************/
30
31 #include "curl_setup.h"
32
33 #include <curl/curl.h>
34 #include "urldata.h"
35
36 #include "curl_base64.h"
37 #include "curl_md5.h"
38 #include "vtls/vtls.h"
39 #include "curl_hmac.h"
40 #include "curl_sasl.h"
41 #include "warnless.h"
42 #include "strtok.h"
43 #include "strequal.h"
44 #include "rawstr.h"
45 #include "sendf.h"
46 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
47 #include "curl_printf.h"
48
49 /* The last #include files should be: */
50 #include "curl_memory.h"
51 #include "memdebug.h"
52
53 /* Supported mechanisms */
54 const struct {
55 const char *name; /* Name */
56 size_t len; /* Name length */
57 unsigned int bit; /* Flag bit */
58 } mechtable[] = {
59 { "LOGIN", 5, SASL_MECH_LOGIN },
60 { "PLAIN", 5, SASL_MECH_PLAIN },
61 { "CRAM-MD5", 8, SASL_MECH_CRAM_MD5 },
62 { "DIGEST-MD5", 10, SASL_MECH_DIGEST_MD5 },
63 { "GSSAPI", 6, SASL_MECH_GSSAPI },
64 { "EXTERNAL", 8, SASL_MECH_EXTERNAL },
65 { "NTLM", 4, SASL_MECH_NTLM },
66 { "XOAUTH2", 7, SASL_MECH_XOAUTH2 },
67 { ZERO_NULL, 0, 0 }
68 };
69
70 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI)
71 #define DIGEST_QOP_VALUE_AUTH (1 << 0)
72 #define DIGEST_QOP_VALUE_AUTH_INT (1 << 1)
73 #define DIGEST_QOP_VALUE_AUTH_CONF (1 << 2)
74
75 #define DIGEST_QOP_VALUE_STRING_AUTH "auth"
76 #define DIGEST_QOP_VALUE_STRING_AUTH_INT "auth-int"
77 #define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
78
79 /* The CURL_OUTPUT_DIGEST_CONV macro below is for non-ASCII machines.
80 It converts digest text to ASCII so the MD5 will be correct for
81 what ultimately goes over the network.
82 */
83 #define CURL_OUTPUT_DIGEST_CONV(a, b) \
84 result = Curl_convert_to_network(a, (char *)b, strlen((const char*)b)); \
85 if(result) { \
86 free(b); \
87 return result; \
88 }
89
90 #endif
91
92 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
93 /*
94 * Returns 0 on success and then the buffers are filled in fine.
95 *
96 * Non-zero means failure to parse.
97 */
Curl_sasl_digest_get_pair(const char * str,char * value,char * content,const char ** endptr)98 int Curl_sasl_digest_get_pair(const char *str, char *value, char *content,
99 const char **endptr)
100 {
101 int c;
102 bool starts_with_quote = FALSE;
103 bool escape = FALSE;
104
105 for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--); )
106 *value++ = *str++;
107 *value = 0;
108
109 if('=' != *str++)
110 /* eek, no match */
111 return 1;
112
113 if('\"' == *str) {
114 /* this starts with a quote so it must end with one as well! */
115 str++;
116 starts_with_quote = TRUE;
117 }
118
119 for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
120 switch(*str) {
121 case '\\':
122 if(!escape) {
123 /* possibly the start of an escaped quote */
124 escape = TRUE;
125 *content++ = '\\'; /* even though this is an escape character, we still
126 store it as-is in the target buffer */
127 continue;
128 }
129 break;
130 case ',':
131 if(!starts_with_quote) {
132 /* this signals the end of the content if we didn't get a starting
133 quote and then we do "sloppy" parsing */
134 c = 0; /* the end */
135 continue;
136 }
137 break;
138 case '\r':
139 case '\n':
140 /* end of string */
141 c = 0;
142 continue;
143 case '\"':
144 if(!escape && starts_with_quote) {
145 /* end of string */
146 c = 0;
147 continue;
148 }
149 break;
150 }
151 escape = FALSE;
152 *content++ = *str;
153 }
154 *content = 0;
155
156 *endptr = str;
157
158 return 0; /* all is fine! */
159 }
160 #endif
161
162 #if !defined(CURL_DISABLE_CRYPTO_AUTH) && !defined(USE_WINDOWS_SSPI)
163 /* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string*/
sasl_digest_md5_to_ascii(unsigned char * source,unsigned char * dest)164 static void sasl_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
165 unsigned char *dest) /* 33 bytes */
166 {
167 int i;
168 for(i = 0; i < 16; i++)
169 snprintf((char *)&dest[i*2], 3, "%02x", source[i]);
170 }
171
172 /* Perform quoted-string escaping as described in RFC2616 and its errata */
sasl_digest_string_quoted(const char * source)173 static char *sasl_digest_string_quoted(const char *source)
174 {
175 char *dest, *d;
176 const char *s = source;
177 size_t n = 1; /* null terminator */
178
179 /* Calculate size needed */
180 while(*s) {
181 ++n;
182 if(*s == '"' || *s == '\\') {
183 ++n;
184 }
185 ++s;
186 }
187
188 dest = malloc(n);
189 if(dest) {
190 s = source;
191 d = dest;
192 while(*s) {
193 if(*s == '"' || *s == '\\') {
194 *d++ = '\\';
195 }
196 *d++ = *s++;
197 }
198 *d = 0;
199 }
200
201 return dest;
202 }
203
204 /* Retrieves the value for a corresponding key from the challenge string
205 * returns TRUE if the key could be found, FALSE if it does not exists
206 */
sasl_digest_get_key_value(const char * chlg,const char * key,char * value,size_t max_val_len,char end_char)207 static bool sasl_digest_get_key_value(const char *chlg,
208 const char *key,
209 char *value,
210 size_t max_val_len,
211 char end_char)
212 {
213 char *find_pos;
214 size_t i;
215
216 find_pos = strstr(chlg, key);
217 if(!find_pos)
218 return FALSE;
219
220 find_pos += strlen(key);
221
222 for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
223 value[i] = *find_pos++;
224 value[i] = '\0';
225
226 return TRUE;
227 }
228
sasl_digest_get_qop_values(const char * options,int * value)229 static CURLcode sasl_digest_get_qop_values(const char *options, int *value)
230 {
231 char *tmp;
232 char *token;
233 char *tok_buf;
234
235 /* Initialise the output */
236 *value = 0;
237
238 /* Tokenise the list of qop values. Use a temporary clone of the buffer since
239 strtok_r() ruins it. */
240 tmp = strdup(options);
241 if(!tmp)
242 return CURLE_OUT_OF_MEMORY;
243
244 token = strtok_r(tmp, ",", &tok_buf);
245 while(token != NULL) {
246 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH))
247 *value |= DIGEST_QOP_VALUE_AUTH;
248 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
249 *value |= DIGEST_QOP_VALUE_AUTH_INT;
250 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
251 *value |= DIGEST_QOP_VALUE_AUTH_CONF;
252
253 token = strtok_r(NULL, ",", &tok_buf);
254 }
255
256 free(tmp);
257
258 return CURLE_OK;
259 }
260 #endif /* !CURL_DISABLE_CRYPTO_AUTH && !USE_WINDOWS_SSPI */
261
262 #if !defined(USE_WINDOWS_SSPI)
263 /*
264 * Curl_sasl_build_spn()
265 *
266 * This is used to build a SPN string in the format service/host.
267 *
268 * Parameters:
269 *
270 * service [in] - The service type such as www, smtp, pop or imap.
271 * host [in] - The host name or realm.
272 *
273 * Returns a pointer to the newly allocated SPN.
274 */
Curl_sasl_build_spn(const char * service,const char * host)275 char *Curl_sasl_build_spn(const char *service, const char *host)
276 {
277 /* Generate and return our SPN */
278 return aprintf("%s/%s", service, host);
279 }
280 #endif
281
282 /*
283 * sasl_create_plain_message()
284 *
285 * This is used to generate an already encoded PLAIN message ready
286 * for sending to the recipient.
287 *
288 * Parameters:
289 *
290 * data [in] - The session handle.
291 * userp [in] - The user name.
292 * passdwp [in] - The user's password.
293 * outptr [in/out] - The address where a pointer to newly allocated memory
294 * holding the result will be stored upon completion.
295 * outlen [out] - The length of the output message.
296 *
297 * Returns CURLE_OK on success.
298 */
sasl_create_plain_message(struct SessionHandle * data,const char * userp,const char * passwdp,char ** outptr,size_t * outlen)299 static CURLcode sasl_create_plain_message(struct SessionHandle *data,
300 const char *userp,
301 const char *passwdp,
302 char **outptr, size_t *outlen)
303 {
304 CURLcode result;
305 char *plainauth;
306 size_t ulen;
307 size_t plen;
308
309 ulen = strlen(userp);
310 plen = strlen(passwdp);
311
312 plainauth = malloc(2 * ulen + plen + 2);
313 if(!plainauth) {
314 *outlen = 0;
315 *outptr = NULL;
316 return CURLE_OUT_OF_MEMORY;
317 }
318
319 /* Calculate the reply */
320 memcpy(plainauth, userp, ulen);
321 plainauth[ulen] = '\0';
322 memcpy(plainauth + ulen + 1, userp, ulen);
323 plainauth[2 * ulen + 1] = '\0';
324 memcpy(plainauth + 2 * ulen + 2, passwdp, plen);
325
326 /* Base64 encode the reply */
327 result = Curl_base64_encode(data, plainauth, 2 * ulen + plen + 2, outptr,
328 outlen);
329 free(plainauth);
330 return result;
331 }
332
333 /*
334 * sasl_create_login_message()
335 *
336 * This is used to generate an already encoded LOGIN message containing the
337 * user name or password ready for sending to the recipient.
338 *
339 * Parameters:
340 *
341 * data [in] - The session handle.
342 * valuep [in] - The user name or user's password.
343 * outptr [in/out] - The address where a pointer to newly allocated memory
344 * holding the result will be stored upon completion.
345 * outlen [out] - The length of the output message.
346 *
347 * Returns CURLE_OK on success.
348 */
sasl_create_login_message(struct SessionHandle * data,const char * valuep,char ** outptr,size_t * outlen)349 static CURLcode sasl_create_login_message(struct SessionHandle *data,
350 const char *valuep, char **outptr,
351 size_t *outlen)
352 {
353 size_t vlen = strlen(valuep);
354
355 if(!vlen) {
356 /* Calculate an empty reply */
357 *outptr = strdup("=");
358 if(*outptr) {
359 *outlen = (size_t) 1;
360 return CURLE_OK;
361 }
362
363 *outlen = 0;
364 return CURLE_OUT_OF_MEMORY;
365 }
366
367 /* Base64 encode the value */
368 return Curl_base64_encode(data, valuep, vlen, outptr, outlen);
369 }
370
371 /*
372 * sasl_create_external_message()
373 *
374 * This is used to generate an already encoded EXTERNAL message containing
375 * the user name ready for sending to the recipient.
376 *
377 * Parameters:
378 *
379 * data [in] - The session handle.
380 * user [in] - The user name.
381 * outptr [in/out] - The address where a pointer to newly allocated memory
382 * holding the result will be stored upon completion.
383 * outlen [out] - The length of the output message.
384 *
385 * Returns CURLE_OK on success.
386 */
sasl_create_external_message(struct SessionHandle * data,const char * user,char ** outptr,size_t * outlen)387 static CURLcode sasl_create_external_message(struct SessionHandle *data,
388 const char *user, char **outptr,
389 size_t *outlen)
390 {
391 /* This is the same formatting as the login message. */
392 return sasl_create_login_message(data, user, outptr, outlen);
393 }
394
395 #ifndef CURL_DISABLE_CRYPTO_AUTH
396 /*
397 * sasl_decode_cram_md5_message()
398 *
399 * This is used to decode an already encoded CRAM-MD5 challenge message.
400 *
401 * Parameters:
402 *
403 * chlg64 [in] - The base64 encoded challenge message.
404 * outptr [in/out] - The address where a pointer to newly allocated memory
405 * holding the result will be stored upon completion.
406 * outlen [out] - The length of the output message.
407 *
408 * Returns CURLE_OK on success.
409 */
sasl_decode_cram_md5_message(const char * chlg64,char ** outptr,size_t * outlen)410 static CURLcode sasl_decode_cram_md5_message(const char *chlg64, char **outptr,
411 size_t *outlen)
412 {
413 CURLcode result = CURLE_OK;
414 size_t chlg64len = strlen(chlg64);
415
416 *outptr = NULL;
417 *outlen = 0;
418
419 /* Decode the challenge if necessary */
420 if(chlg64len && *chlg64 != '=')
421 result = Curl_base64_decode(chlg64, (unsigned char **) outptr, outlen);
422
423 return result;
424 }
425
426 /*
427 * sasl_create_cram_md5_message()
428 *
429 * This is used to generate an already encoded CRAM-MD5 response message ready
430 * for sending to the recipient.
431 *
432 * Parameters:
433 *
434 * data [in] - The session handle.
435 * chlg [in] - The challenge.
436 * userp [in] - The user name.
437 * passdwp [in] - The user's password.
438 * outptr [in/out] - The address where a pointer to newly allocated memory
439 * holding the result will be stored upon completion.
440 * outlen [out] - The length of the output message.
441 *
442 * Returns CURLE_OK on success.
443 */
sasl_create_cram_md5_message(struct SessionHandle * data,const char * chlg,const char * userp,const char * passwdp,char ** outptr,size_t * outlen)444 static CURLcode sasl_create_cram_md5_message(struct SessionHandle *data,
445 const char *chlg,
446 const char *userp,
447 const char *passwdp,
448 char **outptr, size_t *outlen)
449 {
450 CURLcode result = CURLE_OK;
451 size_t chlglen = 0;
452 HMAC_context *ctxt;
453 unsigned char digest[MD5_DIGEST_LEN];
454 char *response;
455
456 if(chlg)
457 chlglen = strlen(chlg);
458
459 /* Compute the digest using the password as the key */
460 ctxt = Curl_HMAC_init(Curl_HMAC_MD5,
461 (const unsigned char *) passwdp,
462 curlx_uztoui(strlen(passwdp)));
463 if(!ctxt)
464 return CURLE_OUT_OF_MEMORY;
465
466 /* Update the digest with the given challenge */
467 if(chlglen > 0)
468 Curl_HMAC_update(ctxt, (const unsigned char *) chlg,
469 curlx_uztoui(chlglen));
470
471 /* Finalise the digest */
472 Curl_HMAC_final(ctxt, digest);
473
474 /* Generate the response */
475 response = aprintf(
476 "%s %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
477 userp, digest[0], digest[1], digest[2], digest[3], digest[4],
478 digest[5], digest[6], digest[7], digest[8], digest[9], digest[10],
479 digest[11], digest[12], digest[13], digest[14], digest[15]);
480 if(!response)
481 return CURLE_OUT_OF_MEMORY;
482
483 /* Base64 encode the response */
484 result = Curl_base64_encode(data, response, 0, outptr, outlen);
485
486 free(response);
487
488 return result;
489 }
490
491 #ifndef USE_WINDOWS_SSPI
492 /*
493 * sasl_decode_digest_md5_message()
494 *
495 * This is used internally to decode an already encoded DIGEST-MD5 challenge
496 * message into the seperate attributes.
497 *
498 * Parameters:
499 *
500 * chlg64 [in] - The base64 encoded challenge message.
501 * nonce [in/out] - The buffer where the nonce will be stored.
502 * nlen [in] - The length of the nonce buffer.
503 * realm [in/out] - The buffer where the realm will be stored.
504 * rlen [in] - The length of the realm buffer.
505 * alg [in/out] - The buffer where the algorithm will be stored.
506 * alen [in] - The length of the algorithm buffer.
507 * qop [in/out] - The buffer where the qop-options will be stored.
508 * qlen [in] - The length of the qop buffer.
509 *
510 * Returns CURLE_OK on success.
511 */
sasl_decode_digest_md5_message(const char * chlg64,char * nonce,size_t nlen,char * realm,size_t rlen,char * alg,size_t alen,char * qop,size_t qlen)512 static CURLcode sasl_decode_digest_md5_message(const char *chlg64,
513 char *nonce, size_t nlen,
514 char *realm, size_t rlen,
515 char *alg, size_t alen,
516 char *qop, size_t qlen)
517 {
518 CURLcode result = CURLE_OK;
519 unsigned char *chlg = NULL;
520 size_t chlglen = 0;
521 size_t chlg64len = strlen(chlg64);
522
523 /* Decode the base-64 encoded challenge message */
524 if(chlg64len && *chlg64 != '=') {
525 result = Curl_base64_decode(chlg64, &chlg, &chlglen);
526 if(result)
527 return result;
528 }
529
530 /* Ensure we have a valid challenge message */
531 if(!chlg)
532 return CURLE_BAD_CONTENT_ENCODING;
533
534 /* Retrieve nonce string from the challenge */
535 if(!sasl_digest_get_key_value((char *)chlg, "nonce=\"", nonce, nlen, '\"')) {
536 free(chlg);
537 return CURLE_BAD_CONTENT_ENCODING;
538 }
539
540 /* Retrieve realm string from the challenge */
541 if(!sasl_digest_get_key_value((char *)chlg, "realm=\"", realm, rlen, '\"')) {
542 /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
543 strcpy(realm, "");
544 }
545
546 /* Retrieve algorithm string from the challenge */
547 if(!sasl_digest_get_key_value((char *)chlg, "algorithm=", alg, alen, ',')) {
548 free(chlg);
549 return CURLE_BAD_CONTENT_ENCODING;
550 }
551
552 /* Retrieve qop-options string from the challenge */
553 if(!sasl_digest_get_key_value((char *)chlg, "qop=\"", qop, qlen, '\"')) {
554 free(chlg);
555 return CURLE_BAD_CONTENT_ENCODING;
556 }
557
558 free(chlg);
559
560 return CURLE_OK;
561 }
562
563 /*
564 * Curl_sasl_create_digest_md5_message()
565 *
566 * This is used to generate an already encoded DIGEST-MD5 response message
567 * ready for sending to the recipient.
568 *
569 * Parameters:
570 *
571 * data [in] - The session handle.
572 * chlg64 [in] - The base64 encoded challenge message.
573 * userp [in] - The user name.
574 * passdwp [in] - The user's password.
575 * service [in] - The service type such as www, smtp, pop or imap.
576 * outptr [in/out] - The address where a pointer to newly allocated memory
577 * holding the result will be stored upon completion.
578 * outlen [out] - The length of the output message.
579 *
580 * Returns CURLE_OK on success.
581 */
Curl_sasl_create_digest_md5_message(struct SessionHandle * data,const char * chlg64,const char * userp,const char * passwdp,const char * service,char ** outptr,size_t * outlen)582 CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data,
583 const char *chlg64,
584 const char *userp,
585 const char *passwdp,
586 const char *service,
587 char **outptr, size_t *outlen)
588 {
589 CURLcode result = CURLE_OK;
590 size_t i;
591 MD5_context *ctxt;
592 char *response = NULL;
593 unsigned char digest[MD5_DIGEST_LEN];
594 char HA1_hex[2 * MD5_DIGEST_LEN + 1];
595 char HA2_hex[2 * MD5_DIGEST_LEN + 1];
596 char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
597 char nonce[64];
598 char realm[128];
599 char algorithm[64];
600 char qop_options[64];
601 int qop_values;
602 char cnonce[33];
603 unsigned int entropy[4];
604 char nonceCount[] = "00000001";
605 char method[] = "AUTHENTICATE";
606 char qop[] = DIGEST_QOP_VALUE_STRING_AUTH;
607 char *spn = NULL;
608
609 /* Decode the challange message */
610 result = sasl_decode_digest_md5_message(chlg64, nonce, sizeof(nonce),
611 realm, sizeof(realm),
612 algorithm, sizeof(algorithm),
613 qop_options, sizeof(qop_options));
614 if(result)
615 return result;
616
617 /* We only support md5 sessions */
618 if(strcmp(algorithm, "md5-sess") != 0)
619 return CURLE_BAD_CONTENT_ENCODING;
620
621 /* Get the qop-values from the qop-options */
622 result = sasl_digest_get_qop_values(qop_options, &qop_values);
623 if(result)
624 return result;
625
626 /* We only support auth quality-of-protection */
627 if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
628 return CURLE_BAD_CONTENT_ENCODING;
629
630 /* Generate 16 bytes of random data */
631 entropy[0] = Curl_rand(data);
632 entropy[1] = Curl_rand(data);
633 entropy[2] = Curl_rand(data);
634 entropy[3] = Curl_rand(data);
635
636 /* Convert the random data into a 32 byte hex string */
637 snprintf(cnonce, sizeof(cnonce), "%08x%08x%08x%08x",
638 entropy[0], entropy[1], entropy[2], entropy[3]);
639
640 /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
641 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
642 if(!ctxt)
643 return CURLE_OUT_OF_MEMORY;
644
645 Curl_MD5_update(ctxt, (const unsigned char *) userp,
646 curlx_uztoui(strlen(userp)));
647 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
648 Curl_MD5_update(ctxt, (const unsigned char *) realm,
649 curlx_uztoui(strlen(realm)));
650 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
651 Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
652 curlx_uztoui(strlen(passwdp)));
653 Curl_MD5_final(ctxt, digest);
654
655 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
656 if(!ctxt)
657 return CURLE_OUT_OF_MEMORY;
658
659 Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
660 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
661 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
662 curlx_uztoui(strlen(nonce)));
663 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
664 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
665 curlx_uztoui(strlen(cnonce)));
666 Curl_MD5_final(ctxt, digest);
667
668 /* Convert calculated 16 octet hex into 32 bytes string */
669 for(i = 0; i < MD5_DIGEST_LEN; i++)
670 snprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
671
672 /* Generate our SPN */
673 spn = Curl_sasl_build_spn(service, realm);
674 if(!spn)
675 return CURLE_OUT_OF_MEMORY;
676
677 /* Calculate H(A2) */
678 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
679 if(!ctxt) {
680 free(spn);
681
682 return CURLE_OUT_OF_MEMORY;
683 }
684
685 Curl_MD5_update(ctxt, (const unsigned char *) method,
686 curlx_uztoui(strlen(method)));
687 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
688 Curl_MD5_update(ctxt, (const unsigned char *) spn,
689 curlx_uztoui(strlen(spn)));
690 Curl_MD5_final(ctxt, digest);
691
692 for(i = 0; i < MD5_DIGEST_LEN; i++)
693 snprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
694
695 /* Now calculate the response hash */
696 ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
697 if(!ctxt) {
698 free(spn);
699
700 return CURLE_OUT_OF_MEMORY;
701 }
702
703 Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
704 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
705 Curl_MD5_update(ctxt, (const unsigned char *) nonce,
706 curlx_uztoui(strlen(nonce)));
707 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
708
709 Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
710 curlx_uztoui(strlen(nonceCount)));
711 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
712 Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
713 curlx_uztoui(strlen(cnonce)));
714 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
715 Curl_MD5_update(ctxt, (const unsigned char *) qop,
716 curlx_uztoui(strlen(qop)));
717 Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
718
719 Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
720 Curl_MD5_final(ctxt, digest);
721
722 for(i = 0; i < MD5_DIGEST_LEN; i++)
723 snprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
724
725 /* Generate the response */
726 response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
727 "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
728 "qop=%s",
729 userp, realm, nonce,
730 cnonce, nonceCount, spn, resp_hash_hex, qop);
731 free(spn);
732 if(!response)
733 return CURLE_OUT_OF_MEMORY;
734
735 /* Base64 encode the response */
736 result = Curl_base64_encode(data, response, 0, outptr, outlen);
737
738 free(response);
739
740 return result;
741 }
742
743 /*
744 * Curl_sasl_decode_digest_http_message()
745 *
746 * This is used to decode a HTTP DIGEST challenge message into the seperate
747 * attributes.
748 *
749 * Parameters:
750 *
751 * chlg [in] - The challenge message.
752 * digest [in/out] - The digest data struct being used and modified.
753 *
754 * Returns CURLE_OK on success.
755 */
Curl_sasl_decode_digest_http_message(const char * chlg,struct digestdata * digest)756 CURLcode Curl_sasl_decode_digest_http_message(const char *chlg,
757 struct digestdata *digest)
758 {
759 bool before = FALSE; /* got a nonce before */
760 bool foundAuth = FALSE;
761 bool foundAuthInt = FALSE;
762 char *token = NULL;
763 char *tmp = NULL;
764
765 /* If we already have received a nonce, keep that in mind */
766 if(digest->nonce)
767 before = TRUE;
768
769 /* Clean up any former leftovers and initialise to defaults */
770 Curl_sasl_digest_cleanup(digest);
771
772 for(;;) {
773 char value[DIGEST_MAX_VALUE_LENGTH];
774 char content[DIGEST_MAX_CONTENT_LENGTH];
775
776 /* Pass all additional spaces here */
777 while(*chlg && ISSPACE(*chlg))
778 chlg++;
779
780 /* Extract a value=content pair */
781 if(!Curl_sasl_digest_get_pair(chlg, value, content, &chlg)) {
782 if(Curl_raw_equal(value, "nonce")) {
783 digest->nonce = strdup(content);
784 if(!digest->nonce)
785 return CURLE_OUT_OF_MEMORY;
786 }
787 else if(Curl_raw_equal(value, "stale")) {
788 if(Curl_raw_equal(content, "true")) {
789 digest->stale = TRUE;
790 digest->nc = 1; /* we make a new nonce now */
791 }
792 }
793 else if(Curl_raw_equal(value, "realm")) {
794 digest->realm = strdup(content);
795 if(!digest->realm)
796 return CURLE_OUT_OF_MEMORY;
797 }
798 else if(Curl_raw_equal(value, "opaque")) {
799 digest->opaque = strdup(content);
800 if(!digest->opaque)
801 return CURLE_OUT_OF_MEMORY;
802 }
803 else if(Curl_raw_equal(value, "qop")) {
804 char *tok_buf;
805 /* Tokenize the list and choose auth if possible, use a temporary
806 clone of the buffer since strtok_r() ruins it */
807 tmp = strdup(content);
808 if(!tmp)
809 return CURLE_OUT_OF_MEMORY;
810
811 token = strtok_r(tmp, ",", &tok_buf);
812 while(token != NULL) {
813 if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
814 foundAuth = TRUE;
815 }
816 else if(Curl_raw_equal(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
817 foundAuthInt = TRUE;
818 }
819 token = strtok_r(NULL, ",", &tok_buf);
820 }
821
822 free(tmp);
823
824 /* Select only auth or auth-int. Otherwise, ignore */
825 if(foundAuth) {
826 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
827 if(!digest->qop)
828 return CURLE_OUT_OF_MEMORY;
829 }
830 else if(foundAuthInt) {
831 digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
832 if(!digest->qop)
833 return CURLE_OUT_OF_MEMORY;
834 }
835 }
836 else if(Curl_raw_equal(value, "algorithm")) {
837 digest->algorithm = strdup(content);
838 if(!digest->algorithm)
839 return CURLE_OUT_OF_MEMORY;
840
841 if(Curl_raw_equal(content, "MD5-sess"))
842 digest->algo = CURLDIGESTALGO_MD5SESS;
843 else if(Curl_raw_equal(content, "MD5"))
844 digest->algo = CURLDIGESTALGO_MD5;
845 else
846 return CURLE_BAD_CONTENT_ENCODING;
847 }
848 else {
849 /* unknown specifier, ignore it! */
850 }
851 }
852 else
853 break; /* we're done here */
854
855 /* Pass all additional spaces here */
856 while(*chlg && ISSPACE(*chlg))
857 chlg++;
858
859 /* Allow the list to be comma-separated */
860 if(',' == *chlg)
861 chlg++;
862 }
863
864 /* We had a nonce since before, and we got another one now without
865 'stale=true'. This means we provided bad credentials in the previous
866 request */
867 if(before && !digest->stale)
868 return CURLE_BAD_CONTENT_ENCODING;
869
870 /* We got this header without a nonce, that's a bad Digest line! */
871 if(!digest->nonce)
872 return CURLE_BAD_CONTENT_ENCODING;
873
874 return CURLE_OK;
875 }
876
877 /*
878 * Curl_sasl_create_digest_http_message()
879 *
880 * This is used to generate a HTTP DIGEST response message ready for sending
881 * to the recipient.
882 *
883 * Parameters:
884 *
885 * data [in] - The session handle.
886 * userp [in] - The user name.
887 * passdwp [in] - The user's password.
888 * request [in] - The HTTP request.
889 * uripath [in] - The path of the HTTP uri.
890 * digest [in/out] - The digest data struct being used and modified.
891 * outptr [in/out] - The address where a pointer to newly allocated memory
892 * holding the result will be stored upon completion.
893 * outlen [out] - The length of the output message.
894 *
895 * Returns CURLE_OK on success.
896 */
Curl_sasl_create_digest_http_message(struct SessionHandle * data,const char * userp,const char * passwdp,const unsigned char * request,const unsigned char * uripath,struct digestdata * digest,char ** outptr,size_t * outlen)897 CURLcode Curl_sasl_create_digest_http_message(struct SessionHandle *data,
898 const char *userp,
899 const char *passwdp,
900 const unsigned char *request,
901 const unsigned char *uripath,
902 struct digestdata *digest,
903 char **outptr, size_t *outlen)
904 {
905 CURLcode result;
906 unsigned char md5buf[16]; /* 16 bytes/128 bits */
907 unsigned char request_digest[33];
908 unsigned char *md5this;
909 unsigned char ha1[33];/* 32 digits and 1 zero byte */
910 unsigned char ha2[33];/* 32 digits and 1 zero byte */
911 char cnoncebuf[33];
912 char *cnonce = NULL;
913 size_t cnonce_sz = 0;
914 char *userp_quoted;
915 char *response = NULL;
916 char *tmp = NULL;
917
918 if(!digest->nc)
919 digest->nc = 1;
920
921 if(!digest->cnonce) {
922 snprintf(cnoncebuf, sizeof(cnoncebuf), "%08x%08x%08x%08x",
923 Curl_rand(data), Curl_rand(data),
924 Curl_rand(data), Curl_rand(data));
925
926 result = Curl_base64_encode(data, cnoncebuf, strlen(cnoncebuf),
927 &cnonce, &cnonce_sz);
928 if(result)
929 return result;
930
931 digest->cnonce = cnonce;
932 }
933
934 /*
935 if the algorithm is "MD5" or unspecified (which then defaults to MD5):
936
937 A1 = unq(username-value) ":" unq(realm-value) ":" passwd
938
939 if the algorithm is "MD5-sess" then:
940
941 A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd )
942 ":" unq(nonce-value) ":" unq(cnonce-value)
943 */
944
945 md5this = (unsigned char *)
946 aprintf("%s:%s:%s", userp, digest->realm, passwdp);
947 if(!md5this)
948 return CURLE_OUT_OF_MEMORY;
949
950 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
951 Curl_md5it(md5buf, md5this);
952 free(md5this);
953 sasl_digest_md5_to_ascii(md5buf, ha1);
954
955 if(digest->algo == CURLDIGESTALGO_MD5SESS) {
956 /* nonce and cnonce are OUTSIDE the hash */
957 tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
958 if(!tmp)
959 return CURLE_OUT_OF_MEMORY;
960
961 CURL_OUTPUT_DIGEST_CONV(data, tmp); /* convert on non-ASCII machines */
962 Curl_md5it(md5buf, (unsigned char *)tmp);
963 free(tmp);
964 sasl_digest_md5_to_ascii(md5buf, ha1);
965 }
966
967 /*
968 If the "qop" directive's value is "auth" or is unspecified, then A2 is:
969
970 A2 = Method ":" digest-uri-value
971
972 If the "qop" value is "auth-int", then A2 is:
973
974 A2 = Method ":" digest-uri-value ":" H(entity-body)
975
976 (The "Method" value is the HTTP request method as specified in section
977 5.1.1 of RFC 2616)
978 */
979
980 md5this = (unsigned char *)aprintf("%s:%s", request, uripath);
981
982 if(digest->qop && Curl_raw_equal(digest->qop, "auth-int")) {
983 /* We don't support auth-int for PUT or POST at the moment.
984 TODO: replace md5 of empty string with entity-body for PUT/POST */
985 unsigned char *md5this2 = (unsigned char *)
986 aprintf("%s:%s", md5this, "d41d8cd98f00b204e9800998ecf8427e");
987 free(md5this);
988 md5this = md5this2;
989 }
990
991 if(!md5this)
992 return CURLE_OUT_OF_MEMORY;
993
994 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
995 Curl_md5it(md5buf, md5this);
996 free(md5this);
997 sasl_digest_md5_to_ascii(md5buf, ha2);
998
999 if(digest->qop) {
1000 md5this = (unsigned char *)aprintf("%s:%s:%08x:%s:%s:%s",
1001 ha1,
1002 digest->nonce,
1003 digest->nc,
1004 digest->cnonce,
1005 digest->qop,
1006 ha2);
1007 }
1008 else {
1009 md5this = (unsigned char *)aprintf("%s:%s:%s",
1010 ha1,
1011 digest->nonce,
1012 ha2);
1013 }
1014
1015 if(!md5this)
1016 return CURLE_OUT_OF_MEMORY;
1017
1018 CURL_OUTPUT_DIGEST_CONV(data, md5this); /* convert on non-ASCII machines */
1019 Curl_md5it(md5buf, md5this);
1020 free(md5this);
1021 sasl_digest_md5_to_ascii(md5buf, request_digest);
1022
1023 /* for test case 64 (snooped from a Mozilla 1.3a request)
1024
1025 Authorization: Digest username="testuser", realm="testrealm", \
1026 nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
1027
1028 Digest parameters are all quoted strings. Username which is provided by
1029 the user will need double quotes and backslashes within it escaped. For
1030 the other fields, this shouldn't be an issue. realm, nonce, and opaque
1031 are copied as is from the server, escapes and all. cnonce is generated
1032 with web-safe characters. uri is already percent encoded. nc is 8 hex
1033 characters. algorithm and qop with standard values only contain web-safe
1034 chracters.
1035 */
1036 userp_quoted = sasl_digest_string_quoted(userp);
1037 if(!userp_quoted)
1038 return CURLE_OUT_OF_MEMORY;
1039
1040 if(digest->qop) {
1041 response = aprintf("username=\"%s\", "
1042 "realm=\"%s\", "
1043 "nonce=\"%s\", "
1044 "uri=\"%s\", "
1045 "cnonce=\"%s\", "
1046 "nc=%08x, "
1047 "qop=%s, "
1048 "response=\"%s\"",
1049 userp_quoted,
1050 digest->realm,
1051 digest->nonce,
1052 uripath,
1053 digest->cnonce,
1054 digest->nc,
1055 digest->qop,
1056 request_digest);
1057
1058 if(Curl_raw_equal(digest->qop, "auth"))
1059 digest->nc++; /* The nc (from RFC) has to be a 8 hex digit number 0
1060 padded which tells to the server how many times you are
1061 using the same nonce in the qop=auth mode */
1062 }
1063 else {
1064 response = aprintf("username=\"%s\", "
1065 "realm=\"%s\", "
1066 "nonce=\"%s\", "
1067 "uri=\"%s\", "
1068 "response=\"%s\"",
1069 userp_quoted,
1070 digest->realm,
1071 digest->nonce,
1072 uripath,
1073 request_digest);
1074 }
1075 free(userp_quoted);
1076 if(!response)
1077 return CURLE_OUT_OF_MEMORY;
1078
1079 /* Add the optional fields */
1080 if(digest->opaque) {
1081 /* Append the opaque */
1082 tmp = aprintf("%s, opaque=\"%s\"", response, digest->opaque);
1083 free(response);
1084 if(!tmp)
1085 return CURLE_OUT_OF_MEMORY;
1086
1087 response = tmp;
1088 }
1089
1090 if(digest->algorithm) {
1091 /* Append the algorithm */
1092 tmp = aprintf("%s, algorithm=\"%s\"", response, digest->algorithm);
1093 free(response);
1094 if(!tmp)
1095 return CURLE_OUT_OF_MEMORY;
1096
1097 response = tmp;
1098 }
1099
1100 /* Return the output */
1101 *outptr = response;
1102 *outlen = strlen(response);
1103
1104 return CURLE_OK;
1105 }
1106
1107 /*
1108 * Curl_sasl_digest_cleanup()
1109 *
1110 * This is used to clean up the digest specific data.
1111 *
1112 * Parameters:
1113 *
1114 * digest [in/out] - The digest data struct being cleaned up.
1115 *
1116 */
Curl_sasl_digest_cleanup(struct digestdata * digest)1117 void Curl_sasl_digest_cleanup(struct digestdata *digest)
1118 {
1119 Curl_safefree(digest->nonce);
1120 Curl_safefree(digest->cnonce);
1121 Curl_safefree(digest->realm);
1122 Curl_safefree(digest->opaque);
1123 Curl_safefree(digest->qop);
1124 Curl_safefree(digest->algorithm);
1125
1126 digest->nc = 0;
1127 digest->algo = CURLDIGESTALGO_MD5; /* default algorithm */
1128 digest->stale = FALSE; /* default means normal, not stale */
1129 }
1130 #endif /* !USE_WINDOWS_SSPI */
1131
1132 #endif /* CURL_DISABLE_CRYPTO_AUTH */
1133
1134 #if defined(USE_NTLM) && !defined(USE_WINDOWS_SSPI)
1135 /*
1136 * Curl_sasl_ntlm_cleanup()
1137 *
1138 * This is used to clean up the ntlm specific data.
1139 *
1140 * Parameters:
1141 *
1142 * ntlm [in/out] - The ntlm data struct being cleaned up.
1143 *
1144 */
Curl_sasl_ntlm_cleanup(struct ntlmdata * ntlm)1145 void Curl_sasl_ntlm_cleanup(struct ntlmdata *ntlm)
1146 {
1147 /* Free the target info */
1148 Curl_safefree(ntlm->target_info);
1149
1150 /* Reset any variables */
1151 ntlm->target_info_len = 0;
1152 }
1153 #endif /* USE_NTLM && !USE_WINDOWS_SSPI*/
1154
1155 /*
1156 * sasl_create_xoauth2_message()
1157 *
1158 * This is used to generate an already encoded OAuth 2.0 message ready for
1159 * sending to the recipient.
1160 *
1161 * Parameters:
1162 *
1163 * data [in] - The session handle.
1164 * user [in] - The user name.
1165 * bearer [in] - The bearer token.
1166 * outptr [in/out] - The address where a pointer to newly allocated memory
1167 * holding the result will be stored upon completion.
1168 * outlen [out] - The length of the output message.
1169 *
1170 * Returns CURLE_OK on success.
1171 */
sasl_create_xoauth2_message(struct SessionHandle * data,const char * user,const char * bearer,char ** outptr,size_t * outlen)1172 static CURLcode sasl_create_xoauth2_message(struct SessionHandle *data,
1173 const char *user,
1174 const char *bearer,
1175 char **outptr, size_t *outlen)
1176 {
1177 CURLcode result = CURLE_OK;
1178 char *xoauth = NULL;
1179
1180 /* Generate the message */
1181 xoauth = aprintf("user=%s\1auth=Bearer %s\1\1", user, bearer);
1182 if(!xoauth)
1183 return CURLE_OUT_OF_MEMORY;
1184
1185 /* Base64 encode the reply */
1186 result = Curl_base64_encode(data, xoauth, strlen(xoauth), outptr, outlen);
1187
1188 free(xoauth);
1189
1190 return result;
1191 }
1192
1193 /*
1194 * Curl_sasl_cleanup()
1195 *
1196 * This is used to cleanup any libraries or curl modules used by the sasl
1197 * functions.
1198 *
1199 * Parameters:
1200 *
1201 * conn [in] - The connection data.
1202 * authused [in] - The authentication mechanism used.
1203 */
Curl_sasl_cleanup(struct connectdata * conn,unsigned int authused)1204 void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused)
1205 {
1206 #if defined(USE_KERBEROS5)
1207 /* Cleanup the gssapi structure */
1208 if(authused == SASL_MECH_GSSAPI) {
1209 Curl_sasl_gssapi_cleanup(&conn->krb5);
1210 }
1211 #endif
1212
1213 #if defined(USE_NTLM)
1214 /* Cleanup the ntlm structure */
1215 if(authused == SASL_MECH_NTLM) {
1216 Curl_sasl_ntlm_cleanup(&conn->ntlm);
1217 }
1218 #endif
1219
1220 #if !defined(USE_KERBEROS5) && !defined(USE_NTLM)
1221 /* Reserved for future use */
1222 (void)conn;
1223 (void)authused;
1224 #endif
1225 }
1226
1227 /*
1228 * Curl_sasl_decode_mech()
1229 *
1230 * Convert a SASL mechanism name into a token.
1231 *
1232 * Parameters:
1233 *
1234 * ptr [in] - The mechanism string.
1235 * maxlen [in] - Maximum mechanism string length.
1236 * len [out] - If not NULL, effective name length.
1237 *
1238 * Returns the SASL mechanism token or 0 if no match.
1239 */
Curl_sasl_decode_mech(const char * ptr,size_t maxlen,size_t * len)1240 unsigned int Curl_sasl_decode_mech(const char *ptr, size_t maxlen, size_t *len)
1241 {
1242 unsigned int i;
1243 char c;
1244
1245 for(i = 0; mechtable[i].name; i++) {
1246 if(maxlen >= mechtable[i].len &&
1247 !memcmp(ptr, mechtable[i].name, mechtable[i].len)) {
1248 if(len)
1249 *len = mechtable[i].len;
1250
1251 if(maxlen == mechtable[i].len)
1252 return mechtable[i].bit;
1253
1254 c = ptr[mechtable[i].len];
1255 if(!ISUPPER(c) && !ISDIGIT(c) && c != '-' && c != '_')
1256 return mechtable[i].bit;
1257 }
1258 }
1259
1260 return 0;
1261 }
1262
1263 /*
1264 * Curl_sasl_parse_url_auth_option()
1265 *
1266 * Parse the URL login options.
1267 */
Curl_sasl_parse_url_auth_option(struct SASL * sasl,const char * value,size_t len)1268 CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl,
1269 const char *value, size_t len)
1270 {
1271 CURLcode result = CURLE_OK;
1272 unsigned int mechbit;
1273 size_t mechlen;
1274
1275 if(!len)
1276 return CURLE_URL_MALFORMAT;
1277
1278 if(sasl->resetprefs) {
1279 sasl->resetprefs = FALSE;
1280 sasl->prefmech = SASL_AUTH_NONE;
1281 }
1282
1283 if(strnequal(value, "*", len))
1284 sasl->prefmech = SASL_AUTH_DEFAULT;
1285 else if((mechbit = Curl_sasl_decode_mech(value, len, &mechlen)) &&
1286 mechlen == len)
1287 sasl->prefmech |= mechbit;
1288 else
1289 result = CURLE_URL_MALFORMAT;
1290
1291 return result;
1292 }
1293
1294 /*
1295 * Curl_sasl_init()
1296 *
1297 * Initializes the SASL structure.
1298 */
Curl_sasl_init(struct SASL * sasl,const struct SASLproto * params)1299 void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params)
1300 {
1301 sasl->params = params; /* Set protocol dependent parameters */
1302 sasl->state = SASL_STOP; /* Not yet running */
1303 sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */
1304 sasl->prefmech = SASL_AUTH_DEFAULT; /* Prefer all mechanisms */
1305 sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */
1306 sasl->resetprefs = TRUE; /* Reset prefmech upon AUTH parsing. */
1307 sasl->mutual_auth = FALSE; /* No mutual authentication (GSSAPI only) */
1308 sasl->force_ir = FALSE; /* Respect external option */
1309 }
1310
1311 /*
1312 * state()
1313 *
1314 * This is the ONLY way to change SASL state!
1315 */
state(struct SASL * sasl,struct connectdata * conn,saslstate newstate)1316 static void state(struct SASL *sasl, struct connectdata *conn,
1317 saslstate newstate)
1318 {
1319 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
1320 /* for debug purposes */
1321 static const char * const names[]={
1322 "STOP",
1323 "PLAIN",
1324 "LOGIN",
1325 "LOGIN_PASSWD",
1326 "EXTERNAL",
1327 "CRAMMD5",
1328 "DIGESTMD5",
1329 "DIGESTMD5_RESP",
1330 "NTLM",
1331 "NTLM_TYPE2MSG",
1332 "GSSAPI",
1333 "GSSAPI_TOKEN",
1334 "GSSAPI_NO_DATA",
1335 "XOAUTH2",
1336 "CANCEL",
1337 "FINAL",
1338 /* LAST */
1339 };
1340
1341 if(sasl->state != newstate)
1342 infof(conn->data, "SASL %p state change from %s to %s\n",
1343 (void *)sasl, names[sasl->state], names[newstate]);
1344 #else
1345 (void) conn;
1346 #endif
1347
1348 sasl->state = newstate;
1349 }
1350
1351 /*
1352 * Curl_sasl_can_authenticate()
1353 *
1354 * Check if we have enough auth data and capabilities to authenticate.
1355 */
Curl_sasl_can_authenticate(struct SASL * sasl,struct connectdata * conn)1356 bool Curl_sasl_can_authenticate(struct SASL *sasl, struct connectdata *conn)
1357 {
1358 /* Have credentials been provided? */
1359 if(conn->bits.user_passwd)
1360 return TRUE;
1361
1362 /* EXTERNAL can authenticate without a user name and/or password */
1363 if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL)
1364 return TRUE;
1365
1366 return FALSE;
1367 }
1368
1369 /*
1370 * Curl_sasl_start()
1371 *
1372 * Calculate the required login details for SASL authentication.
1373 */
Curl_sasl_start(struct SASL * sasl,struct connectdata * conn,bool force_ir,saslprogress * progress)1374 CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn,
1375 bool force_ir, saslprogress *progress)
1376 {
1377 CURLcode result = CURLE_OK;
1378 struct SessionHandle *data = conn->data;
1379 unsigned int enabledmechs;
1380 const char *mech = NULL;
1381 char *resp = NULL;
1382 size_t len = 0;
1383 saslstate state1 = SASL_STOP;
1384 saslstate state2 = SASL_FINAL;
1385
1386 sasl->force_ir = force_ir; /* Latch for future use */
1387 sasl->authused = 0; /* No mechanism used yet */
1388 enabledmechs = sasl->authmechs & sasl->prefmech;
1389 *progress = SASL_IDLE;
1390
1391 /* Calculate the supported authentication mechanism, by decreasing order of
1392 security, as well as the initial response where appropriate */
1393 if((enabledmechs & SASL_MECH_EXTERNAL) && !conn->passwd[0]) {
1394 mech = SASL_MECH_STRING_EXTERNAL;
1395 state1 = SASL_EXTERNAL;
1396 sasl->authused = SASL_MECH_EXTERNAL;
1397
1398 if(force_ir || data->set.sasl_ir)
1399 result = sasl_create_external_message(data, conn->user, &resp, &len);
1400 }
1401 else if(conn->bits.user_passwd) {
1402 #if defined(USE_KERBEROS5)
1403 if(enabledmechs & SASL_MECH_GSSAPI) {
1404 sasl->mutual_auth = FALSE; /* TODO: Calculate mutual authentication */
1405 mech = SASL_MECH_STRING_GSSAPI;
1406 state1 = SASL_GSSAPI;
1407 state2 = SASL_GSSAPI_TOKEN;
1408 sasl->authused = SASL_MECH_GSSAPI;
1409
1410 if(force_ir || data->set.sasl_ir)
1411 result = Curl_sasl_create_gssapi_user_message(data, conn->user,
1412 conn->passwd,
1413 sasl->params->service,
1414 sasl->mutual_auth,
1415 NULL, &conn->krb5,
1416 &resp, &len);
1417 }
1418 else
1419 #endif
1420 #ifndef CURL_DISABLE_CRYPTO_AUTH
1421 if(enabledmechs & SASL_MECH_DIGEST_MD5) {
1422 mech = SASL_MECH_STRING_DIGEST_MD5;
1423 state1 = SASL_DIGESTMD5;
1424 sasl->authused = SASL_MECH_DIGEST_MD5;
1425 }
1426 else if(enabledmechs & SASL_MECH_CRAM_MD5) {
1427 mech = SASL_MECH_STRING_CRAM_MD5;
1428 state1 = SASL_CRAMMD5;
1429 sasl->authused = SASL_MECH_CRAM_MD5;
1430 }
1431 else
1432 #endif
1433 #ifdef USE_NTLM
1434 if(enabledmechs & SASL_MECH_NTLM) {
1435 mech = SASL_MECH_STRING_NTLM;
1436 state1 = SASL_NTLM;
1437 state2 = SASL_NTLM_TYPE2MSG;
1438 sasl->authused = SASL_MECH_NTLM;
1439
1440 if(force_ir || data->set.sasl_ir)
1441 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
1442 &conn->ntlm, &resp, &len);
1443 }
1444 else
1445 #endif
1446 if((enabledmechs & SASL_MECH_XOAUTH2) || conn->xoauth2_bearer) {
1447 mech = SASL_MECH_STRING_XOAUTH2;
1448 state1 = SASL_XOAUTH2;
1449 sasl->authused = SASL_MECH_XOAUTH2;
1450
1451 if(force_ir || data->set.sasl_ir)
1452 result = sasl_create_xoauth2_message(data, conn->user,
1453 conn->xoauth2_bearer,
1454 &resp, &len);
1455 }
1456 else if(enabledmechs & SASL_MECH_LOGIN) {
1457 mech = SASL_MECH_STRING_LOGIN;
1458 state1 = SASL_LOGIN;
1459 state2 = SASL_LOGIN_PASSWD;
1460 sasl->authused = SASL_MECH_LOGIN;
1461
1462 if(force_ir || data->set.sasl_ir)
1463 result = sasl_create_login_message(data, conn->user, &resp, &len);
1464 }
1465 else if(enabledmechs & SASL_MECH_PLAIN) {
1466 mech = SASL_MECH_STRING_PLAIN;
1467 state1 = SASL_PLAIN;
1468 sasl->authused = SASL_MECH_PLAIN;
1469
1470 if(force_ir || data->set.sasl_ir)
1471 result = sasl_create_plain_message(data, conn->user, conn->passwd,
1472 &resp, &len);
1473 }
1474 }
1475
1476 if(!result) {
1477 if(resp && sasl->params->maxirlen &&
1478 strlen(mech) + len > sasl->params->maxirlen) {
1479 free(resp);
1480 resp = NULL;
1481 }
1482
1483 if(mech) {
1484 result = sasl->params->sendauth(conn, mech, resp);
1485 if(!result) {
1486 *progress = SASL_INPROGRESS;
1487 state(sasl, conn, resp? state2: state1);
1488 }
1489 }
1490 }
1491
1492 free(resp);
1493
1494 return result;
1495 }
1496
1497 /*
1498 * Curl_sasl_continue()
1499 *
1500 * Continue the authentication.
1501 */
Curl_sasl_continue(struct SASL * sasl,struct connectdata * conn,int code,saslprogress * progress)1502 CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn,
1503 int code, saslprogress *progress)
1504 {
1505 CURLcode result = CURLE_OK;
1506 struct SessionHandle *data = conn->data;
1507 saslstate newstate = SASL_FINAL;
1508 char *resp = NULL;
1509 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
1510 char *serverdata;
1511 char *chlg = NULL;
1512 size_t chlglen = 0;
1513 #endif
1514 size_t len = 0;
1515
1516 *progress = SASL_INPROGRESS;
1517
1518 if(sasl->state == SASL_FINAL) {
1519 if(code != sasl->params->finalcode)
1520 result = CURLE_LOGIN_DENIED;
1521 *progress = SASL_DONE;
1522 state(sasl, conn, SASL_STOP);
1523 return result;
1524 }
1525
1526 if(sasl->state != SASL_CANCEL && code != sasl->params->contcode) {
1527 *progress = SASL_DONE;
1528 state(sasl, conn, SASL_STOP);
1529 return CURLE_LOGIN_DENIED;
1530 }
1531
1532 switch(sasl->state) {
1533 case SASL_STOP:
1534 *progress = SASL_DONE;
1535 return result;
1536 case SASL_PLAIN:
1537 result = sasl_create_plain_message(data, conn->user, conn->passwd, &resp,
1538 &len);
1539 break;
1540 case SASL_LOGIN:
1541 result = sasl_create_login_message(data, conn->user, &resp, &len);
1542 newstate = SASL_LOGIN_PASSWD;
1543 break;
1544 case SASL_LOGIN_PASSWD:
1545 result = sasl_create_login_message(data, conn->passwd, &resp, &len);
1546 break;
1547 case SASL_EXTERNAL:
1548 result = sasl_create_external_message(data, conn->user, &resp, &len);
1549 break;
1550
1551 #ifndef CURL_DISABLE_CRYPTO_AUTH
1552 case SASL_CRAMMD5:
1553 sasl->params->getmessage(data->state.buffer, &serverdata);
1554 result = sasl_decode_cram_md5_message(serverdata, &chlg, &chlglen);
1555 if(!result)
1556 result = sasl_create_cram_md5_message(data, chlg, conn->user,
1557 conn->passwd, &resp, &len);
1558 free(chlg);
1559 break;
1560 case SASL_DIGESTMD5:
1561 sasl->params->getmessage(data->state.buffer, &serverdata);
1562 result = Curl_sasl_create_digest_md5_message(data, serverdata,
1563 conn->user, conn->passwd,
1564 sasl->params->service,
1565 &resp, &len);
1566 newstate = SASL_DIGESTMD5_RESP;
1567 break;
1568 case SASL_DIGESTMD5_RESP:
1569 if(!(resp = strdup("")))
1570 result = CURLE_OUT_OF_MEMORY;
1571 break;
1572 #endif
1573
1574 #ifdef USE_NTLM
1575 case SASL_NTLM:
1576 /* Create the type-1 message */
1577 result = Curl_sasl_create_ntlm_type1_message(conn->user, conn->passwd,
1578 &conn->ntlm, &resp, &len);
1579 newstate = SASL_NTLM_TYPE2MSG;
1580 break;
1581 case SASL_NTLM_TYPE2MSG:
1582 /* Decode the type-2 message */
1583 sasl->params->getmessage(data->state.buffer, &serverdata);
1584 result = Curl_sasl_decode_ntlm_type2_message(data, serverdata,
1585 &conn->ntlm);
1586 if(!result)
1587 result = Curl_sasl_create_ntlm_type3_message(data, conn->user,
1588 conn->passwd, &conn->ntlm,
1589 &resp, &len);
1590 break;
1591 #endif
1592
1593 #if defined(USE_KERBEROS5)
1594 case SASL_GSSAPI:
1595 result = Curl_sasl_create_gssapi_user_message(data, conn->user,
1596 conn->passwd,
1597 sasl->params->service,
1598 sasl->mutual_auth, NULL,
1599 &conn->krb5,
1600 &resp, &len);
1601 newstate = SASL_GSSAPI_TOKEN;
1602 break;
1603 case SASL_GSSAPI_TOKEN:
1604 sasl->params->getmessage(data->state.buffer, &serverdata);
1605 if(sasl->mutual_auth) {
1606 /* Decode the user token challenge and create the optional response
1607 message */
1608 result = Curl_sasl_create_gssapi_user_message(data, NULL, NULL, NULL,
1609 sasl->mutual_auth,
1610 serverdata, &conn->krb5,
1611 &resp, &len);
1612 newstate = SASL_GSSAPI_NO_DATA;
1613 }
1614 else
1615 /* Decode the security challenge and create the response message */
1616 result = Curl_sasl_create_gssapi_security_message(data, serverdata,
1617 &conn->krb5,
1618 &resp, &len);
1619 break;
1620 case SASL_GSSAPI_NO_DATA:
1621 sasl->params->getmessage(data->state.buffer, &serverdata);
1622 /* Decode the security challenge and create the response message */
1623 result = Curl_sasl_create_gssapi_security_message(data, serverdata,
1624 &conn->krb5,
1625 &resp, &len);
1626 break;
1627 #endif
1628
1629 case SASL_XOAUTH2:
1630 /* Create the authorisation message */
1631 result = sasl_create_xoauth2_message(data, conn->user,
1632 conn->xoauth2_bearer, &resp, &len);
1633 break;
1634 case SASL_CANCEL:
1635 /* Remove the offending mechanism from the supported list */
1636 sasl->authmechs ^= sasl->authused;
1637
1638 /* Start an alternative SASL authentication */
1639 result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress);
1640 newstate = sasl->state; /* Use state from Curl_sasl_start() */
1641 break;
1642 default:
1643 failf(data, "Unsupported SASL authentication mechanism");
1644 result = CURLE_UNSUPPORTED_PROTOCOL; /* Should not happen */
1645 break;
1646 }
1647
1648 switch(result) {
1649 case CURLE_BAD_CONTENT_ENCODING:
1650 /* Cancel dialog */
1651 result = sasl->params->sendcont(conn, "*");
1652 newstate = SASL_CANCEL;
1653 break;
1654 case CURLE_OK:
1655 if(resp)
1656 result = sasl->params->sendcont(conn, resp);
1657 break;
1658 default:
1659 newstate = SASL_STOP; /* Stop on error */
1660 *progress = SASL_DONE;
1661 break;
1662 }
1663
1664 free(resp);
1665
1666 state(sasl, conn, newstate);
1667
1668 return result;
1669 }
1670