1/* 2 * Copyright (c) 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following 12 * disclaimer in the documentation and/or other materials provided 13 * with the distribution. 14 * 3. Neither the name of Apple Inc. ("Apple") nor the names of its 15 * contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 20 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 21 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS 22 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 25 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 28 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32/* Implements RFC 4468 - Submission BURL */ 33 34#if defined(USE_SASL_AUTH) && defined(USE_TLS) 35 36#include <sys_defs.h> 37#include <msg.h> 38#include <mymalloc.h> 39#include <vstring.h> 40#include <vstream.h> 41#include <vstring_vstream.h> 42#include <mail_params.h> 43#include <iostuff.h> 44#include <imap-url.h> 45#include <smtpd.h> 46#include <smtpd_chat.h> 47#include <smtpd_sasl_glue.h> 48#include <smtp_stream.h> 49#include <base64_code.h> 50#include <connect.h> 51#include <tls.h> 52#include <stdlib.h> 53#include <string.h> 54#include <strings.h> 55#include <sys/stat.h> 56 57char *var_imap_submit_cred_file; 58 59struct imap_server { 60 struct imap_server *next; 61 char *hostport; 62 char *username; 63 char *password; 64}; 65static struct imap_server *imap_servers = NULL; 66 67static bool imap_validate(const struct imap_url_parts *parts, 68 const char **error) 69{ 70 // user: mandatory; RFC 3501 "userid" 71 if (parts->user == NULL || 72 !imap_url_astring_validate(parts->user)) { 73 *error = "missing or invalid user ID"; 74 return FALSE; 75 } 76 77 // auth_type: optional; RFC 3501 "auth-type" 78 if (parts->auth_type != NULL && 79 !imap_url_atom_validate(parts->auth_type)) { 80 *error = "invalid auth type"; 81 return FALSE; 82 } 83 84 // hostport: mandatory; RFC 1738 "hostport" 85 if (parts->hostport == NULL || 86 !imap_url_hostport_validate(parts->hostport)) { 87 *error = "missing or invalid server"; 88 return FALSE; 89 } 90 91 // mailbox: mandatory; RFC 3501 "mailbox" 92 if (parts->mailbox == NULL || 93 !imap_url_mailbox_validate(parts->mailbox)) { 94 *error = "missing or invalid mailbox"; 95 return FALSE; 96 } 97 98 // uidvalidity: optional; RFC 3501 "nz-number" 99 if (parts->uidvalidity != NULL && 100 !imap_url_nz_number_validate(parts->uidvalidity)) { 101 *error = "invalid uidvalidity"; 102 return FALSE; 103 } 104 105 // uid: mandatory; RFC 3501 "nz-number" 106 if (parts->uid == NULL || 107 !imap_url_nz_number_validate(parts->uid)) { 108 *error = "missing or invalid uid"; 109 return FALSE; 110 } 111 112 // section: optional; RFC 2192 "section" 113 if (parts->section != NULL && 114 !imap_url_section_validate(parts->section)) { 115 *error = "invalid section"; 116 return FALSE; 117 } 118 119 // expiration: optional; RFC 3339 "date-time" 120 if (parts->expiration != NULL && 121 !imap_url_datetime_validate(parts->expiration)) { 122 *error = "invalid expiration"; 123 return FALSE; 124 } 125 126 // access: mandatory; RFC 4467 "access" 127 if (parts->access == NULL || 128 !imap_url_access_validate(parts->access)) { 129 *error = "missing or invalid access ID"; 130 return FALSE; 131 } 132 133 // mechanism: mandatory; RFC 4467 "mechanism" 134 if (parts->mechanism == NULL || 135 !imap_url_mechanism_validate(parts->mechanism)) { 136 *error = "missing or invalid mechanism"; 137 return FALSE; 138 } 139 140 // urlauth: mandatory; RFC 4467 "urlauth" 141 if (parts->urlauth == NULL || 142 !imap_url_urlauth_validate(parts->urlauth)) { 143 *error = "missing or invalid access token"; 144 return FALSE; 145 } 146 147 return TRUE; 148} 149 150static const struct imap_server *imap_check_policy(SMTPD_STATE *state, 151 const struct imap_url_parts *parts) 152{ 153 const struct imap_server *is; 154 155 if (strncasecmp(parts->access, "user+", 5) == 0) { 156 smtpd_chat_reply(state, "554 5.7.0 Invalid URL: unsupported access method"); 157 return NULL; 158 } 159 160 if (strcmp(parts->user, state->sasl_username) != 0 || 161 (strncasecmp(parts->access, "submit+", 7) == 0 && 162 strcmp(&parts->access[7], state->sasl_username) != 0)) { 163 smtpd_chat_reply(state, "554 5.7.0 Invalid URL: user mismatch"); 164 return NULL; 165 } 166 167 for (is = imap_servers; is != NULL; is = is->next) 168 if (strcasecmp(parts->hostport, is->hostport) == 0) 169 return is; 170 171 smtpd_chat_reply(state, "554 5.7.14 No trust relationship with IMAP server"); 172 return NULL; 173} 174 175void imap_read_config(void) 176{ 177 VSTREAM *fp; 178 struct stat stbuf; 179 VSTRING *line; 180 struct imap_server *list; 181 int lineno; 182 183 if (*var_imap_submit_cred_file == 0) 184 return; 185 186 fp = vstream_fopen(var_imap_submit_cred_file, O_RDONLY, 0600); 187 if (fp == NULL) 188 msg_fatal("open %s: %m", var_imap_submit_cred_file); 189 190 if (fstat(vstream_fileno(fp), &stbuf) < 0) 191 msg_fatal("fstat %s: %m", var_imap_submit_cred_file); 192 193 if (stbuf.st_uid != 0 || stbuf.st_gid != 0 || 194 (stbuf.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) 195 msg_fatal("unsafe ownership or permissions on %s: " 196 "uid/gid/mode are %d/%d/%0o should be 0/0/0600", 197 var_imap_submit_cred_file, stbuf.st_uid, stbuf.st_gid, 198 stbuf.st_mode & ~S_IFMT); 199 200 line = vstring_alloc(100); 201 list = NULL; 202 lineno = 0; 203 while (vstring_get_nonl(line, fp) != VSTREAM_EOF) { 204 const char *str = vstring_str(line); 205 const char *username, *password; 206 struct imap_server *is; 207 const char *invalid; 208 209 ++lineno; 210 211 if (*str == '#') 212 continue; 213 214 /* future-proofing */ 215 if (strcmp(str, "submitcred version 1") == 0) 216 continue; 217 218 /* hostport|username|password, all nonempty */ 219 username = strchr(str, '|'); 220 if (username == NULL || username == str || *++username == 0) { 221 msg_warn("syntax error on line %d of %s", lineno, 222 var_imap_submit_cred_file); 223 continue; 224 } 225 password = strchr(username, '|'); 226 if (password == NULL || password == username || *++password == 0) { 227 msg_warn("syntax error on line %d of %s", lineno, 228 var_imap_submit_cred_file); 229 continue; 230 } 231 232 is = (struct imap_server *) mymalloc(sizeof *is); 233 is->hostport = mystrndup(str, username - str - 1); 234 is->username = mystrndup(username, password - username - 1); 235 is->password = mystrdup(password); 236 237 invalid = NULL; 238 if (!imap_url_hostport_validate(is->hostport)) 239 invalid = "hostport"; 240 else if (!imap_url_astring_validate(is->username)) 241 invalid = "username"; 242 else if (!imap_url_astring_validate(is->password)) 243 invalid = "password"; 244 if (invalid != NULL) { 245 msg_warn("invalid %s on line %d of %s", invalid, lineno, 246 var_imap_submit_cred_file); 247 myfree((char *) is); 248 continue; 249 } 250 251 is->next = list; 252 list = is; 253 } 254 255 vstring_free(line); 256 vstream_fclose(fp); 257 258 if (list == NULL) { 259 msg_warn("no valid hostport|username|password entries in %s%s", 260 var_imap_submit_cred_file, imap_servers != NULL ? 261 "; keeping old list" : ""); 262 return; 263 } 264 265 /* free old list */ 266 while (imap_servers != NULL) { 267 struct imap_server *next = imap_servers->next; 268 myfree(imap_servers->hostport); 269 myfree(imap_servers->username); 270 myfree(imap_servers->password); 271 myfree((char *) imap_servers); 272 imap_servers = next; 273 } 274 275 /* reverse list to preserve order of entries in cred file */ 276 while (list != NULL) { 277 struct imap_server *next = list->next; 278 list->next = imap_servers; 279 imap_servers = list; 280 list = next; 281 } 282} 283 284bool imap_allowed(SMTPD_STATE *state) 285{ 286 return smtpd_sasl_is_active(state) && imap_servers != NULL && 287 (strcasecmp(state->service, "submission") == 0 || 288 atoi(state->service) == 587); 289} 290 291static TLS_APPL_STATE *tls_ctx = NULL; 292static TLS_SESS_STATE *imap_starttls(VSTREAM *stream, 293 const struct imap_server *is) 294{ 295 TLS_CLIENT_START_PROPS start_props; 296 TLS_SESS_STATE *sess_ctx; 297 298 /* XXX all these hard-coded values should be configurable */ 299 300 if (tls_ctx == NULL) { 301 TLS_CLIENT_INIT_PROPS init_props; 302 303 tls_ctx = TLS_CLIENT_INIT(&init_props, 304 log_param = VAR_SMTPD_TLS_LOGLEVEL, 305 log_level = var_smtpd_tls_loglevel, 306 verifydepth = DEF_SMTP_TLS_SCERT_VD, 307 /* XXX TLS_MGR_SCACHE_IMAP? */ 308 cache_type = TLS_MGR_SCACHE_SMTPD, 309 cert_file = "", 310 key_file = "", 311 dcert_file = "", 312 dkey_file = "", 313 eccert_file = "", 314 eckey_file = "", 315 CAfile = "", 316 CApath = "", 317 fpt_dgst = DEF_SMTP_TLS_FPT_DGST); 318 if (tls_ctx == NULL) { 319 msg_fatal("unable to initialize client TLS"); 320 return NULL; 321 } 322 } 323 324 sess_ctx = TLS_CLIENT_START(&start_props, 325 ctx = tls_ctx, 326 stream = stream, 327 timeout = 30, 328 tls_level = TLS_LEV_ENCRYPT, 329 nexthop = "", 330 host = is->hostport, 331 namaddr = is->hostport, 332 serverid = is->hostport, 333 protocols = DEF_SMTP_TLS_MAND_PROTO, 334 cipher_grade = DEF_SMTP_TLS_MAND_CIPH, 335 cipher_exclusions = "SSLv2, aNULL, ADH, eNULL", 336 matchargv = NULL, 337 fpt_dgst = DEF_SMTP_TLS_FPT_DGST); 338 if (sess_ctx == NULL) 339 msg_warn("unable to start client TLS for IMAP server %s", 340 is->hostport); 341 return sess_ctx; 342} 343 344static bool imap_capable_of(VSTREAM *stream, const struct imap_server *is, 345 VSTRING *request, VSTRING *response, 346 const char *action, bool verbose) 347{ 348 const char *cp, *capabilities; 349 350 /* get capabilities if not already present in response */ 351 cp = strcasestr(vstring_str(response), "[CAPABILITY "); 352 if (cp != NULL) { 353 const char *eb; 354 355 cp += 12; 356 eb = strchr(cp, ']'); 357 if (eb) 358 capabilities = mystrndup(cp, eb - cp); 359 else 360 capabilities = mystrdup(cp); 361 } else { 362 VSTRING_RESET(request); 363 vstring_sprintf(request, "C CAPABILITY"); 364 smtp_fputs(vstring_str(request), VSTRING_LEN(request), stream); 365 366 while (smtp_get(response, stream, 0, SMTP_GET_FLAG_NONE) == '\n') { 367 if (VSTRING_LEN(response) >= 13 && 368 strncasecmp(vstring_str(response), "* CAPABILITY ", 13) == 0) 369 capabilities = mystrdup(vstring_str(response) + 13); 370 if (VSTRING_LEN(response) >= 2 && 371 strncasecmp(vstring_str(response), "C ", 2) == 0) 372 break; 373 } 374 if (VSTRING_LEN(response) < 4 || 375 strncasecmp(vstring_str(response), "C OK", 4) != 0) { 376 msg_warn("querying capabilities of IMAP server %s failed. " 377 "request=\"%s\" response=\"%s\"", 378 is->hostport, vstring_str(request), vstring_str(response)); 379 return FALSE; 380 } 381 } 382 if (capabilities == NULL) { 383 msg_warn("cannot determine capabilities of IMAP server %s", 384 is->hostport); 385 return FALSE; 386 } 387 if (strcasestr(capabilities, action) == 0) { 388 if (verbose) 389 msg_warn("IMAP server %s does not support %s. " 390 "detected capabilities \"%s\"", 391 is->hostport, action, capabilities); 392 myfree((char *) capabilities); 393 return FALSE; 394 } 395 myfree((char *) capabilities); 396 397 return TRUE; 398} 399 400VSTREAM *imap_open(SMTPD_STATE *state, const char *url) 401{ 402 int port; 403 VSTREAM *stream; 404 struct imap_url_parts enc_parts, dec_parts; 405 const char *error, *cp; 406 const struct imap_server *is; 407 int jv; 408 TLS_SESS_STATE *sess_ctx; 409 VSTRING *request, *response; 410 unsigned int length; 411 bool plain, x_plain_submit; 412 413 port = 143; 414 stream = NULL; 415 memset(&enc_parts, 0, sizeof enc_parts); 416 memset(&dec_parts, 0, sizeof dec_parts); 417 error = NULL; 418 419 /* first parse the url */ 420 imap_url_parse(url, &enc_parts); 421 if (imap_url_decode(&enc_parts, &dec_parts, &error) && 422 imap_validate(&dec_parts, &error)) { 423 is = imap_check_policy(state, &dec_parts); 424 if (is != NULL) { 425 int fd; 426 char *hostport; 427 428 if ((cp = strchr(is->hostport, ':')) != NULL) { 429 hostport = is->hostport; 430 if (strcasecmp(cp + 1, "imaps") == 0) 431 port = 993; 432 else 433 port = atoi(cp + 1); 434 } else { 435 VSTRING *str = vstring_alloc(strlen(is->hostport) + 6); 436 vstring_sprintf(str, "%s:imap", is->hostport); 437 hostport = mystrdup(vstring_str(str)); 438 vstring_free(str); 439 port = 143; 440 } 441 442 fd = inet_connect(hostport, BLOCKING, 30); 443 if (fd >= 0) { 444 stream = vstream_fdopen(fd, O_RDWR); 445 vstream_control(stream, VSTREAM_CTL_PATH, hostport, 446 VSTREAM_CTL_END); 447 smtp_timeout_setup(stream, 30); 448 } else { 449 msg_warn("imap_open: connect to %s: %m", hostport); 450 smtpd_chat_reply(state, "451 4.4.1 IMAP server unavailable"); 451 } 452 453 if (hostport != is->hostport) 454 myfree(hostport); 455 } 456 } else { 457 if (error) 458 smtpd_chat_reply(state, "554 5.7.0 Invalid URL: %s", error); 459 else 460 smtpd_chat_reply(state, "554 5.7.0 Invalid URL"); 461 } 462 463 imap_url_parts_free(&dec_parts); 464 imap_url_parts_free(&enc_parts); 465 466 if (stream == NULL) 467 return NULL; 468 469 sess_ctx = NULL; 470 request = vstring_alloc(128); 471 response = vstring_alloc(128); 472 473 jv = vstream_setjmp(stream); 474 if (jv != 0) { 475 if (jv == -2) 476 smtpd_chat_reply(state, "554 5.6.6 IMAP URL resolution failed"); 477 else 478 smtpd_chat_reply(state, "451 4.4.1 IMAP server unavailable"); 479 if (sess_ctx != NULL) 480 tls_client_stop(tls_ctx, stream, 5, TRUE, sess_ctx); 481 vstream_fclose(stream); 482 vstring_free(request); 483 vstring_free(response); 484 return NULL; 485 } 486 487 /* negotiate SSL now if applicable (IMAPS) */ 488 if (port == 993) { 489 sess_ctx = imap_starttls(stream, is); 490 if (sess_ctx == NULL) { 491 vstream_fpurge(stream, VSTREAM_PURGE_BOTH); 492 vstream_longjmp(stream, -1); 493 } 494 } 495 496 /* read server greeting */ 497 if (smtp_get(response, stream, 0, SMTP_GET_FLAG_NONE) != '\n' || VSTRING_LEN(response) < 4 || 498 strncasecmp(vstring_str(response), "* OK", 4) != 0) { 499 msg_warn("bad greeting from IMAP server %s: %s", is->hostport, 500 vstring_str(response)); 501 vstream_longjmp(stream, -1); 502 } 503 504 /* send STARTTLS if applicable (IMAP) */ 505 if (sess_ctx == NULL) { 506 /* make sure the server supports STARTTLS */ 507 if (!imap_capable_of(stream, is, request, response, "STARTTLS", TRUE)) 508 vstream_longjmp(stream, -1); 509 510 VSTRING_RESET(request); 511 vstring_sprintf(request, "S STARTTLS"); 512 smtp_fputs(vstring_str(request), VSTRING_LEN(request), stream); 513 514 while (smtp_get(response, stream, 0, SMTP_GET_FLAG_NONE) == '\n') { 515 if (VSTRING_LEN(response) >= 2 && 516 strncasecmp(vstring_str(response), "S ", 2) == 0) 517 break; 518 } 519 if (VSTRING_LEN(response) < 4 || 520 strncasecmp(vstring_str(response), "S OK", 4) != 0) { 521 msg_warn("starttls to IMAP server %s failed. " 522 "request=\"%s\" response=\"%s\"", 523 is->hostport, vstring_str(request), vstring_str(response)); 524 vstream_fpurge(stream, VSTREAM_PURGE_BOTH); 525 vstream_longjmp(stream, -1); 526 } 527 528 sess_ctx = imap_starttls(stream, is); 529 if (sess_ctx == NULL) { 530 vstream_fpurge(stream, VSTREAM_PURGE_BOTH); 531 vstream_longjmp(stream, -1); 532 } 533 534 /* can't use old capabilities, must request */ 535 VSTRING_RESET(response); 536 VSTRING_TERMINATE(response); 537 } 538 539 /* determine which authentication mechanism to use; prefer PLAIN */ 540 plain = FALSE; 541 x_plain_submit = FALSE; 542 if (imap_capable_of(stream, is, request, response, "AUTH=PLAIN", FALSE)) 543 plain = TRUE; 544 if (imap_capable_of(stream, is, request, response, "AUTH=X-PLAIN-SUBMIT", FALSE)) 545 x_plain_submit = TRUE; 546 if (!plain && !x_plain_submit) { 547 msg_warn("IMAP server %s supports neither " 548 "AUTH=PLAIN nor AUTH=X-PLAIN-SUBMIT. can't log in.", 549 is->hostport); 550 vstream_longjmp(stream, -1); 551 } 552 553 /* log in as the submit user */ 554 VSTRING_RESET(request); 555 vstring_sprintf(request, "A AUTHENTICATE %s", 556 plain ? "PLAIN" : "X-PLAIN-SUBMIT"); 557 smtp_fputs(vstring_str(request), VSTRING_LEN(request), stream); 558 559 while (smtp_get(response, stream, 0, SMTP_GET_FLAG_NONE) == '\n') { 560 if (VSTRING_LEN(response) >= 1 && 561 strncmp(vstring_str(response), "+", 1) == 0) 562 break; 563 if (VSTRING_LEN(response) >= 2 && 564 strncasecmp(vstring_str(response), "A ", 2) == 0) 565 break; 566 } 567 if (VSTRING_LEN(response) < 1 || 568 strncmp(vstring_str(response), "+", 1) != 0) { 569 msg_warn("logging in to IMAP server %s failed. " 570 "request=\"%s\" response=\"%s\"", 571 is->hostport, vstring_str(request), vstring_str(response)); 572 vstream_longjmp(stream, -1); 573 } 574 575 VSTRING_RESET(response); 576 if (x_plain_submit) { 577 /* 578 * Servers which advertise X-PLAIN-SUBMIT need both authorization and 579 * authentication IDs with either authentication mechanism: 580 * authorization ID \0 authentication ID \0 password 581 */ 582 vstring_strcat(response, state->sasl_username); 583 } else { 584 /* 585 * Servers which don't advertise X-PLAIN-SUBMIT need only the 586 * authentication ID: 587 * \0 authentication ID \0 password 588 */ 589 } 590 VSTRING_ADDCH(response, 0); 591 vstring_strcat(response, is->username); 592 VSTRING_ADDCH(response, 0); 593 vstring_strcat(response, is->password); 594 595 VSTRING_RESET(request); 596 base64_encode(request, vstring_str(response), VSTRING_LEN(response)); 597 smtp_fputs(vstring_str(request), VSTRING_LEN(request), stream); 598 599 while (smtp_get(response, stream, 0, SMTP_GET_FLAG_NONE) == '\n') { 600 if (VSTRING_LEN(response) >= 2 && 601 strncasecmp(vstring_str(response), "A ", 2) == 0) 602 break; 603 } 604 if (VSTRING_LEN(response) < 4 || 605 strncasecmp(vstring_str(response), "A OK", 4) != 0) { 606 msg_warn("logging in to IMAP server %s failed. " 607 "mechanism=%s authz=%s auth=%s response=\"%s\"", 608 is->hostport, plain ? "PLAIN" : "X-PLAIN-SUBMIT", 609 x_plain_submit ? state->sasl_username : "", is->username, 610 vstring_str(response)); 611 vstream_longjmp(stream, -1); 612 } 613 614 /* make sure the server supports URLAUTH */ 615 if (!imap_capable_of(stream, is, request, response, "URLAUTH", TRUE)) 616 vstream_longjmp(stream, -1); 617 618 /* finally, begin the fetch */ 619 VSTRING_RESET(request); 620 vstring_sprintf(request, "U URLFETCH \"%s\"", url); 621 smtp_fputs(vstring_str(request), VSTRING_LEN(request), stream); 622 623 while (smtp_get(response, stream, 0, SMTP_GET_FLAG_NONE) == '\n') { 624 if (VSTRING_LEN(response) >= 11 && 625 strncasecmp(vstring_str(response), "* URLFETCH ", 11) == 0) 626 break; 627 if (VSTRING_LEN(response) >= 2 && 628 strncasecmp(vstring_str(response), "U ", 2) == 0) 629 break; 630 } 631 if (VSTRING_LEN(response) < 11 || 632 strncasecmp(vstring_str(response), "* URLFETCH ", 11) != 0) { 633 msg_warn("URLFETCH from IMAP server %s returned no data. " 634 "request=\"%s\" response=\"%s\"", 635 is->hostport, vstring_str(request), vstring_str(response)); 636 vstream_longjmp(stream, -1); 637 } 638 639 cp = strchr(vstring_str(response) + 11, ' '); 640 if (cp == NULL || cp[1] != '{' || cp[2] < '0' || cp[2] > '9') { 641 msg_warn("URLFETCH from IMAP server %s returned no data. " 642 "request=\"%s\" response=\"%s\"", 643 is->hostport, vstring_str(request), vstring_str(response)); 644 vstream_longjmp(stream, -2); 645 } 646 length = strtoul(cp + 2, NULL, 10); 647 648 vstream_control(stream, VSTREAM_CTL_CONTEXT, sess_ctx, VSTREAM_CTL_END); 649 650 /* read only the literal, no more */ 651 vstream_limit_init(stream, length); 652 653 vstring_free(request); 654 vstring_free(response); 655 656 return stream; 657} 658 659bool imap_isdone(VSTREAM *stream) 660{ 661 return vstream_limit_reached(stream); 662} 663 664void imap_close(VSTREAM *stream) 665{ 666 vstream_limit_deinit(stream); 667 vstream_fputs("Z LOGOUT", stream); 668 vstream_fflush(stream); 669 tls_client_stop(tls_ctx, stream, 5, FALSE, vstream_context(stream)); 670 vstream_fclose(stream); 671} 672 673#endif 674