1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 2012 - 2020, 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 https://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  * RFC7628 A Set of SASL Mechanisms for OAuth
28  * Draft   LOGIN SASL Mechanism <draft-murchison-sasl-login-00.txt>
29  *
30  ***************************************************************************/
31 
32 #include "curl_setup.h"
33 
34 #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \
35   !defined(CURL_DISABLE_POP3)
36 
37 #include <curl/curl.h>
38 #include "urldata.h"
39 
40 #include "curl_base64.h"
41 #include "curl_md5.h"
42 #include "vauth/vauth.h"
43 #include "vtls/vtls.h"
44 #include "curl_hmac.h"
45 #include "curl_sasl.h"
46 #include "warnless.h"
47 #include "strtok.h"
48 #include "sendf.h"
49 #include "non-ascii.h" /* included for Curl_convert_... prototypes */
50 /* The last 3 #include files should be in this order */
51 #include "curl_printf.h"
52 #include "curl_memory.h"
53 #include "memdebug.h"
54 
55 /* Supported mechanisms */
56 static const struct {
57   const char   *name;  /* Name */
58   size_t        len;   /* Name length */
59   unsigned int  bit;   /* Flag bit */
60 } mechtable[] = {
61   { "LOGIN",        5,  SASL_MECH_LOGIN },
62   { "PLAIN",        5,  SASL_MECH_PLAIN },
63   { "CRAM-MD5",     8,  SASL_MECH_CRAM_MD5 },
64   { "DIGEST-MD5",   10, SASL_MECH_DIGEST_MD5 },
65   { "GSSAPI",       6,  SASL_MECH_GSSAPI },
66   { "EXTERNAL",     8,  SASL_MECH_EXTERNAL },
67   { "NTLM",         4,  SASL_MECH_NTLM },
68   { "XOAUTH2",      7,  SASL_MECH_XOAUTH2 },
69   { "OAUTHBEARER",  11, SASL_MECH_OAUTHBEARER },
70   { ZERO_NULL,      0,  0 }
71 };
72 
73 /*
74  * Curl_sasl_cleanup()
75  *
76  * This is used to cleanup any libraries or curl modules used by the sasl
77  * functions.
78  *
79  * Parameters:
80  *
81  * conn     [in]     - The connection data.
82  * authused [in]     - The authentication mechanism used.
83  */
Curl_sasl_cleanup(struct connectdata * conn,unsigned int authused)84 void Curl_sasl_cleanup(struct connectdata *conn, unsigned int authused)
85 {
86 #if defined(USE_KERBEROS5)
87   /* Cleanup the gssapi structure */
88   if(authused == SASL_MECH_GSSAPI) {
89     Curl_auth_cleanup_gssapi(&conn->krb5);
90   }
91 #endif
92 
93 #if defined(USE_NTLM)
94   /* Cleanup the NTLM structure */
95   if(authused == SASL_MECH_NTLM) {
96     Curl_auth_cleanup_ntlm(&conn->ntlm);
97   }
98 #endif
99 
100 #if !defined(USE_KERBEROS5) && !defined(USE_NTLM)
101   /* Reserved for future use */
102   (void)conn;
103   (void)authused;
104 #endif
105 }
106 
107 /*
108  * Curl_sasl_decode_mech()
109  *
110  * Convert a SASL mechanism name into a token.
111  *
112  * Parameters:
113  *
114  * ptr    [in]     - The mechanism string.
115  * maxlen [in]     - Maximum mechanism string length.
116  * len    [out]    - If not NULL, effective name length.
117  *
118  * Returns the SASL mechanism token or 0 if no match.
119  */
Curl_sasl_decode_mech(const char * ptr,size_t maxlen,size_t * len)120 unsigned int Curl_sasl_decode_mech(const char *ptr, size_t maxlen, size_t *len)
121 {
122   unsigned int i;
123   char c;
124 
125   for(i = 0; mechtable[i].name; i++) {
126     if(maxlen >= mechtable[i].len &&
127        !memcmp(ptr, mechtable[i].name, mechtable[i].len)) {
128       if(len)
129         *len = mechtable[i].len;
130 
131       if(maxlen == mechtable[i].len)
132         return mechtable[i].bit;
133 
134       c = ptr[mechtable[i].len];
135       if(!ISUPPER(c) && !ISDIGIT(c) && c != '-' && c != '_')
136         return mechtable[i].bit;
137     }
138   }
139 
140   return 0;
141 }
142 
143 /*
144  * Curl_sasl_parse_url_auth_option()
145  *
146  * Parse the URL login options.
147  */
Curl_sasl_parse_url_auth_option(struct SASL * sasl,const char * value,size_t len)148 CURLcode Curl_sasl_parse_url_auth_option(struct SASL *sasl,
149                                          const char *value, size_t len)
150 {
151   CURLcode result = CURLE_OK;
152   size_t mechlen;
153 
154   if(!len)
155     return CURLE_URL_MALFORMAT;
156 
157   if(sasl->resetprefs) {
158     sasl->resetprefs = FALSE;
159     sasl->prefmech = SASL_AUTH_NONE;
160   }
161 
162   if(!strncmp(value, "*", len))
163     sasl->prefmech = SASL_AUTH_DEFAULT;
164   else {
165     unsigned int mechbit = Curl_sasl_decode_mech(value, len, &mechlen);
166     if(mechbit && mechlen == len)
167       sasl->prefmech |= mechbit;
168     else
169       result = CURLE_URL_MALFORMAT;
170   }
171 
172   return result;
173 }
174 
175 /*
176  * Curl_sasl_init()
177  *
178  * Initializes the SASL structure.
179  */
Curl_sasl_init(struct SASL * sasl,const struct SASLproto * params)180 void Curl_sasl_init(struct SASL *sasl, const struct SASLproto *params)
181 {
182   sasl->params = params;           /* Set protocol dependent parameters */
183   sasl->state = SASL_STOP;         /* Not yet running */
184   sasl->authmechs = SASL_AUTH_NONE; /* No known authentication mechanism yet */
185   sasl->prefmech = SASL_AUTH_DEFAULT; /* Prefer all mechanisms */
186   sasl->authused = SASL_AUTH_NONE; /* No the authentication mechanism used */
187   sasl->resetprefs = TRUE;         /* Reset prefmech upon AUTH parsing. */
188   sasl->mutual_auth = FALSE;       /* No mutual authentication (GSSAPI only) */
189   sasl->force_ir = FALSE;          /* Respect external option */
190 }
191 
192 /*
193  * state()
194  *
195  * This is the ONLY way to change SASL state!
196  */
state(struct SASL * sasl,struct connectdata * conn,saslstate newstate)197 static void state(struct SASL *sasl, struct connectdata *conn,
198                   saslstate newstate)
199 {
200 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
201   /* for debug purposes */
202   static const char * const names[]={
203     "STOP",
204     "PLAIN",
205     "LOGIN",
206     "LOGIN_PASSWD",
207     "EXTERNAL",
208     "CRAMMD5",
209     "DIGESTMD5",
210     "DIGESTMD5_RESP",
211     "NTLM",
212     "NTLM_TYPE2MSG",
213     "GSSAPI",
214     "GSSAPI_TOKEN",
215     "GSSAPI_NO_DATA",
216     "OAUTH2",
217     "OAUTH2_RESP",
218     "CANCEL",
219     "FINAL",
220     /* LAST */
221   };
222 
223   if(sasl->state != newstate)
224     infof(conn->data, "SASL %p state change from %s to %s\n",
225           (void *)sasl, names[sasl->state], names[newstate]);
226 #else
227   (void) conn;
228 #endif
229 
230   sasl->state = newstate;
231 }
232 
233 /*
234  * Curl_sasl_can_authenticate()
235  *
236  * Check if we have enough auth data and capabilities to authenticate.
237  */
Curl_sasl_can_authenticate(struct SASL * sasl,struct connectdata * conn)238 bool Curl_sasl_can_authenticate(struct SASL *sasl, struct connectdata *conn)
239 {
240   /* Have credentials been provided? */
241   if(conn->bits.user_passwd)
242     return TRUE;
243 
244   /* EXTERNAL can authenticate without a user name and/or password */
245   if(sasl->authmechs & sasl->prefmech & SASL_MECH_EXTERNAL)
246     return TRUE;
247 
248   return FALSE;
249 }
250 
251 /*
252  * Curl_sasl_start()
253  *
254  * Calculate the required login details for SASL authentication.
255  */
Curl_sasl_start(struct SASL * sasl,struct connectdata * conn,bool force_ir,saslprogress * progress)256 CURLcode Curl_sasl_start(struct SASL *sasl, struct connectdata *conn,
257                          bool force_ir, saslprogress *progress)
258 {
259   CURLcode result = CURLE_OK;
260   struct Curl_easy *data = conn->data;
261   unsigned int enabledmechs;
262   const char *mech = NULL;
263   char *resp = NULL;
264   size_t len = 0;
265   saslstate state1 = SASL_STOP;
266   saslstate state2 = SASL_FINAL;
267 #ifndef CURL_DISABLE_PROXY
268   const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
269     conn->host.name;
270   const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port;
271 #else
272   const char * const hostname = conn->host.name;
273   const long int port = conn->remote_port;
274 #endif
275 #if defined(USE_KERBEROS5) || defined(USE_NTLM)
276   const char *service = data->set.str[STRING_SERVICE_NAME] ?
277     data->set.str[STRING_SERVICE_NAME] :
278     sasl->params->service;
279 #endif
280   const char *oauth_bearer = data->set.str[STRING_BEARER];
281 
282   sasl->force_ir = force_ir;    /* Latch for future use */
283   sasl->authused = 0;           /* No mechanism used yet */
284   enabledmechs = sasl->authmechs & sasl->prefmech;
285   *progress = SASL_IDLE;
286 
287   /* Calculate the supported authentication mechanism, by decreasing order of
288      security, as well as the initial response where appropriate */
289   if((enabledmechs & SASL_MECH_EXTERNAL) && !conn->passwd[0]) {
290     mech = SASL_MECH_STRING_EXTERNAL;
291     state1 = SASL_EXTERNAL;
292     sasl->authused = SASL_MECH_EXTERNAL;
293 
294     if(force_ir || data->set.sasl_ir)
295       result = Curl_auth_create_external_message(data, conn->user, &resp,
296                                                  &len);
297   }
298   else if(conn->bits.user_passwd) {
299 #if defined(USE_KERBEROS5)
300     if((enabledmechs & SASL_MECH_GSSAPI) && Curl_auth_is_gssapi_supported() &&
301        Curl_auth_user_contains_domain(conn->user)) {
302       sasl->mutual_auth = FALSE;
303       mech = SASL_MECH_STRING_GSSAPI;
304       state1 = SASL_GSSAPI;
305       state2 = SASL_GSSAPI_TOKEN;
306       sasl->authused = SASL_MECH_GSSAPI;
307 
308       if(force_ir || data->set.sasl_ir)
309         result = Curl_auth_create_gssapi_user_message(data, conn->user,
310                                                       conn->passwd,
311                                                       service,
312                                                       data->conn->host.name,
313                                                       sasl->mutual_auth,
314                                                       NULL, &conn->krb5,
315                                                       &resp, &len);
316     }
317     else
318 #endif
319 #ifndef CURL_DISABLE_CRYPTO_AUTH
320     if((enabledmechs & SASL_MECH_DIGEST_MD5) &&
321        Curl_auth_is_digest_supported()) {
322       mech = SASL_MECH_STRING_DIGEST_MD5;
323       state1 = SASL_DIGESTMD5;
324       sasl->authused = SASL_MECH_DIGEST_MD5;
325     }
326     else if(enabledmechs & SASL_MECH_CRAM_MD5) {
327       mech = SASL_MECH_STRING_CRAM_MD5;
328       state1 = SASL_CRAMMD5;
329       sasl->authused = SASL_MECH_CRAM_MD5;
330     }
331     else
332 #endif
333 #ifdef USE_NTLM
334     if((enabledmechs & SASL_MECH_NTLM) && Curl_auth_is_ntlm_supported()) {
335       mech = SASL_MECH_STRING_NTLM;
336       state1 = SASL_NTLM;
337       state2 = SASL_NTLM_TYPE2MSG;
338       sasl->authused = SASL_MECH_NTLM;
339 
340       if(force_ir || data->set.sasl_ir)
341         result = Curl_auth_create_ntlm_type1_message(data,
342                                                      conn->user, conn->passwd,
343                                                      service,
344                                                      hostname,
345                                                      &conn->ntlm, &resp,
346                                                      &len);
347       }
348     else
349 #endif
350     if((enabledmechs & SASL_MECH_OAUTHBEARER) && oauth_bearer) {
351       mech = SASL_MECH_STRING_OAUTHBEARER;
352       state1 = SASL_OAUTH2;
353       state2 = SASL_OAUTH2_RESP;
354       sasl->authused = SASL_MECH_OAUTHBEARER;
355 
356       if(force_ir || data->set.sasl_ir)
357         result = Curl_auth_create_oauth_bearer_message(data, conn->user,
358                                                        hostname,
359                                                        port,
360                                                        oauth_bearer,
361                                                        &resp, &len);
362     }
363     else if((enabledmechs & SASL_MECH_XOAUTH2) && oauth_bearer) {
364       mech = SASL_MECH_STRING_XOAUTH2;
365       state1 = SASL_OAUTH2;
366       sasl->authused = SASL_MECH_XOAUTH2;
367 
368       if(force_ir || data->set.sasl_ir)
369         result = Curl_auth_create_xoauth_bearer_message(data, conn->user,
370                                                         oauth_bearer,
371                                                         &resp, &len);
372     }
373     else if(enabledmechs & SASL_MECH_PLAIN) {
374       mech = SASL_MECH_STRING_PLAIN;
375       state1 = SASL_PLAIN;
376       sasl->authused = SASL_MECH_PLAIN;
377 
378       if(force_ir || data->set.sasl_ir)
379         result = Curl_auth_create_plain_message(data, conn->sasl_authzid,
380                                                 conn->user, conn->passwd,
381                                                 &resp, &len);
382     }
383     else if(enabledmechs & SASL_MECH_LOGIN) {
384       mech = SASL_MECH_STRING_LOGIN;
385       state1 = SASL_LOGIN;
386       state2 = SASL_LOGIN_PASSWD;
387       sasl->authused = SASL_MECH_LOGIN;
388 
389       if(force_ir || data->set.sasl_ir)
390         result = Curl_auth_create_login_message(data, conn->user, &resp, &len);
391     }
392   }
393 
394   if(!result && mech) {
395     if(resp && sasl->params->maxirlen &&
396        strlen(mech) + len > sasl->params->maxirlen) {
397       free(resp);
398       resp = NULL;
399     }
400 
401     result = sasl->params->sendauth(conn, mech, resp);
402     if(!result) {
403       *progress = SASL_INPROGRESS;
404       state(sasl, conn, resp ? state2 : state1);
405     }
406   }
407 
408   free(resp);
409 
410   return result;
411 }
412 
413 /*
414  * Curl_sasl_continue()
415  *
416  * Continue the authentication.
417  */
Curl_sasl_continue(struct SASL * sasl,struct connectdata * conn,int code,saslprogress * progress)418 CURLcode Curl_sasl_continue(struct SASL *sasl, struct connectdata *conn,
419                             int code, saslprogress *progress)
420 {
421   CURLcode result = CURLE_OK;
422   struct Curl_easy *data = conn->data;
423   saslstate newstate = SASL_FINAL;
424   char *resp = NULL;
425 #ifndef CURL_DISABLE_PROXY
426   const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
427     conn->host.name;
428   const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port;
429 #else
430   const char * const hostname = conn->host.name;
431   const long int port = conn->remote_port;
432 #endif
433 #if !defined(CURL_DISABLE_CRYPTO_AUTH)
434   char *chlg = NULL;
435   size_t chlglen = 0;
436 #endif
437 #if !defined(CURL_DISABLE_CRYPTO_AUTH) || defined(USE_KERBEROS5) ||     \
438   defined(USE_NTLM)
439   const char *service = data->set.str[STRING_SERVICE_NAME] ?
440     data->set.str[STRING_SERVICE_NAME] :
441     sasl->params->service;
442   char *serverdata;
443 #endif
444   size_t len = 0;
445   const char *oauth_bearer = data->set.str[STRING_BEARER];
446 
447   *progress = SASL_INPROGRESS;
448 
449   if(sasl->state == SASL_FINAL) {
450     if(code != sasl->params->finalcode)
451       result = CURLE_LOGIN_DENIED;
452     *progress = SASL_DONE;
453     state(sasl, conn, SASL_STOP);
454     return result;
455   }
456 
457   if(sasl->state != SASL_CANCEL && sasl->state != SASL_OAUTH2_RESP &&
458      code != sasl->params->contcode) {
459     *progress = SASL_DONE;
460     state(sasl, conn, SASL_STOP);
461     return CURLE_LOGIN_DENIED;
462   }
463 
464   switch(sasl->state) {
465   case SASL_STOP:
466     *progress = SASL_DONE;
467     return result;
468   case SASL_PLAIN:
469     result = Curl_auth_create_plain_message(data, conn->sasl_authzid,
470                                             conn->user, conn->passwd,
471                                             &resp, &len);
472     break;
473   case SASL_LOGIN:
474     result = Curl_auth_create_login_message(data, conn->user, &resp, &len);
475     newstate = SASL_LOGIN_PASSWD;
476     break;
477   case SASL_LOGIN_PASSWD:
478     result = Curl_auth_create_login_message(data, conn->passwd, &resp, &len);
479     break;
480   case SASL_EXTERNAL:
481     result = Curl_auth_create_external_message(data, conn->user, &resp, &len);
482     break;
483 
484 #ifndef CURL_DISABLE_CRYPTO_AUTH
485   case SASL_CRAMMD5:
486     sasl->params->getmessage(data->state.buffer, &serverdata);
487     result = Curl_auth_decode_cram_md5_message(serverdata, &chlg, &chlglen);
488     if(!result)
489       result = Curl_auth_create_cram_md5_message(data, chlg, conn->user,
490                                                  conn->passwd, &resp, &len);
491     free(chlg);
492     break;
493   case SASL_DIGESTMD5:
494     sasl->params->getmessage(data->state.buffer, &serverdata);
495     result = Curl_auth_create_digest_md5_message(data, serverdata,
496                                                  conn->user, conn->passwd,
497                                                  service,
498                                                  &resp, &len);
499     newstate = SASL_DIGESTMD5_RESP;
500     break;
501   case SASL_DIGESTMD5_RESP:
502     resp = strdup("");
503     if(!resp)
504       result = CURLE_OUT_OF_MEMORY;
505     break;
506 #endif
507 
508 #ifdef USE_NTLM
509   case SASL_NTLM:
510     /* Create the type-1 message */
511     result = Curl_auth_create_ntlm_type1_message(data,
512                                                  conn->user, conn->passwd,
513                                                  service, hostname,
514                                                  &conn->ntlm, &resp, &len);
515     newstate = SASL_NTLM_TYPE2MSG;
516     break;
517   case SASL_NTLM_TYPE2MSG:
518     /* Decode the type-2 message */
519     sasl->params->getmessage(data->state.buffer, &serverdata);
520     result = Curl_auth_decode_ntlm_type2_message(data, serverdata,
521                                                  &conn->ntlm);
522     if(!result)
523       result = Curl_auth_create_ntlm_type3_message(data, conn->user,
524                                                    conn->passwd, &conn->ntlm,
525                                                    &resp, &len);
526     break;
527 #endif
528 
529 #if defined(USE_KERBEROS5)
530   case SASL_GSSAPI:
531     result = Curl_auth_create_gssapi_user_message(data, conn->user,
532                                                   conn->passwd,
533                                                   service,
534                                                   data->conn->host.name,
535                                                   sasl->mutual_auth, NULL,
536                                                   &conn->krb5,
537                                                   &resp, &len);
538     newstate = SASL_GSSAPI_TOKEN;
539     break;
540   case SASL_GSSAPI_TOKEN:
541     sasl->params->getmessage(data->state.buffer, &serverdata);
542     if(sasl->mutual_auth) {
543       /* Decode the user token challenge and create the optional response
544          message */
545       result = Curl_auth_create_gssapi_user_message(data, NULL, NULL,
546                                                     NULL, NULL,
547                                                     sasl->mutual_auth,
548                                                     serverdata, &conn->krb5,
549                                                     &resp, &len);
550       newstate = SASL_GSSAPI_NO_DATA;
551     }
552     else
553       /* Decode the security challenge and create the response message */
554       result = Curl_auth_create_gssapi_security_message(data, serverdata,
555                                                         &conn->krb5,
556                                                         &resp, &len);
557     break;
558   case SASL_GSSAPI_NO_DATA:
559     sasl->params->getmessage(data->state.buffer, &serverdata);
560     /* Decode the security challenge and create the response message */
561     result = Curl_auth_create_gssapi_security_message(data, serverdata,
562                                                       &conn->krb5,
563                                                       &resp, &len);
564     break;
565 #endif
566 
567   case SASL_OAUTH2:
568     /* Create the authorisation message */
569     if(sasl->authused == SASL_MECH_OAUTHBEARER) {
570       result = Curl_auth_create_oauth_bearer_message(data, conn->user,
571                                                      hostname,
572                                                      port,
573                                                      oauth_bearer,
574                                                      &resp, &len);
575 
576       /* Failures maybe sent by the server as continuations for OAUTHBEARER */
577       newstate = SASL_OAUTH2_RESP;
578     }
579     else
580       result = Curl_auth_create_xoauth_bearer_message(data, conn->user,
581                                                       oauth_bearer,
582                                                       &resp, &len);
583     break;
584 
585   case SASL_OAUTH2_RESP:
586     /* The continuation is optional so check the response code */
587     if(code == sasl->params->finalcode) {
588       /* Final response was received so we are done */
589       *progress = SASL_DONE;
590       state(sasl, conn, SASL_STOP);
591       return result;
592     }
593     else if(code == sasl->params->contcode) {
594       /* Acknowledge the continuation by sending a 0x01 response base64
595          encoded */
596       resp = strdup("AQ==");
597       if(!resp)
598         result = CURLE_OUT_OF_MEMORY;
599       break;
600     }
601     else {
602       *progress = SASL_DONE;
603       state(sasl, conn, SASL_STOP);
604       return CURLE_LOGIN_DENIED;
605     }
606 
607   case SASL_CANCEL:
608     /* Remove the offending mechanism from the supported list */
609     sasl->authmechs ^= sasl->authused;
610 
611     /* Start an alternative SASL authentication */
612     result = Curl_sasl_start(sasl, conn, sasl->force_ir, progress);
613     newstate = sasl->state;   /* Use state from Curl_sasl_start() */
614     break;
615   default:
616     failf(data, "Unsupported SASL authentication mechanism");
617     result = CURLE_UNSUPPORTED_PROTOCOL;  /* Should not happen */
618     break;
619   }
620 
621   switch(result) {
622   case CURLE_BAD_CONTENT_ENCODING:
623     /* Cancel dialog */
624     result = sasl->params->sendcont(conn, "*");
625     newstate = SASL_CANCEL;
626     break;
627   case CURLE_OK:
628     if(resp)
629       result = sasl->params->sendcont(conn, resp);
630     break;
631   default:
632     newstate = SASL_STOP;    /* Stop on error */
633     *progress = SASL_DONE;
634     break;
635   }
636 
637   free(resp);
638 
639   state(sasl, conn, newstate);
640 
641   return result;
642 }
643 #endif /* protocols are enabled that use SASL */
644