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