1 /* $Id$ */
2 
3 /*
4  * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 
21 #include <ctype.h>
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include "fdm.h"
29 #include "deliver.h"
30 #include "fetch.h"
31 
32 /*
33  * This file is a bit of a mishmash, so that we can use some bits from
34  * the IMAP fetching code.
35  *
36  * All needs to be straightened out sometime.
37  */
38 
39 int	 deliver_imap_deliver(struct deliver_ctx *, struct actitem *);
40 void	 deliver_imap_desc(struct actitem *, char *, size_t);
41 
42 int	 deliver_imap_poll(struct account *, struct io *);
43 int	 deliver_imap_pollto(int (*)(struct account *, struct fetch_ctx *),
44 	     struct account *, struct io *, struct fetch_ctx *);
45 int	 deliver_imap_waitokay(struct account *, struct fetch_ctx *,
46 	     struct io *, char **);
47 int	 deliver_imap_waitcontinue(struct account *, struct fetch_ctx *,
48 	     struct io *, char **);
49 int	 deliver_imap_waitappend(struct account *, struct fetch_ctx *,
50 	     struct io *, char **);
51 
52 struct deliver deliver_imap = {
53 	"imap",
54 	DELIVER_ASUSER,
55 	deliver_imap_deliver,
56 	deliver_imap_desc
57 };
58 
59 /* Poll for data from/to server. */
60 int
deliver_imap_poll(struct account * a,struct io * io)61 deliver_imap_poll(struct account *a, struct io *io)
62 {
63 	char	*cause;
64 
65 	switch (io_poll(io, conf.timeout, &cause)) {
66 	case 0:
67 		log_warnx("%s: connection unexpectedly closed", a->name);
68 		return (1);
69 	case -1:
70 		log_warnx("%s: %s", a->name, cause);
71 		xfree(cause);
72 		return (1);
73 	}
74 	return (0);
75 }
76 
77 /* Poll through the IMAP fetch states until a particular one is reached. */
78 int
deliver_imap_pollto(int (* state)(struct account *,struct fetch_ctx *),struct account * a,struct io * io,struct fetch_ctx * fctx)79 deliver_imap_pollto(int (*state)(struct account *, struct fetch_ctx *),
80     struct account *a, struct io *io, struct fetch_ctx *fctx)
81 {
82 	while (state == NULL || fctx->state != state) {
83 		switch (fctx->state(a, fctx)) {
84 		case FETCH_AGAIN:
85 			continue;
86 		case FETCH_ERROR:
87 			return (1);
88 		case FETCH_EXIT:
89 			return (0);
90 		}
91 		if (deliver_imap_poll(a, io) != 0)
92 			return (1);
93 	}
94 	return (0);
95 }
96 
97 /* Wait for okay. */
98 int
deliver_imap_waitokay(struct account * a,struct fetch_ctx * fctx,struct io * io,char ** line)99 deliver_imap_waitokay(struct account *a, struct fetch_ctx *fctx, struct io *io,
100     char **line)
101 {
102 	do {
103 		if (deliver_imap_poll(a, io) != 0)
104 			return (1);
105 		if (imap_getln(a, fctx, IMAP_TAGGED, line) != 0)
106 			return (1);
107 	} while (*line == NULL);
108 
109 	if (!imap_okay(*line)) {
110 		imap_bad(a, *line);
111 		return (1);
112 	}
113 	return (0);
114 }
115 
116 /* Wait for continuation. */
117 int
deliver_imap_waitcontinue(struct account * a,struct fetch_ctx * fctx,struct io * io,char ** line)118 deliver_imap_waitcontinue(struct account *a, struct fetch_ctx *fctx,
119     struct io *io, char **line)
120 {
121 	do {
122 		if (deliver_imap_poll(a, io) != 0)
123 			return (1);
124 		if (imap_getln(a, fctx, IMAP_CONTINUE, line) != 0)
125 			return (1);
126 	} while (*line == NULL);
127 
128 	return (0);
129 }
130 
131 /* Wait for append response. */
132 int
deliver_imap_waitappend(struct account * a,struct fetch_ctx * fctx,struct io * io,char ** line)133 deliver_imap_waitappend(struct account *a, struct fetch_ctx *fctx,
134     struct io *io, char **line)
135 {
136 	struct fetch_imap_data	*data = a->data;
137 	int			 tag;
138 
139 	for (;;) {
140 		if (deliver_imap_poll(a, io) != 0) {
141 			line = NULL;
142 			return (IMAP_TAG_ERROR);
143 		}
144 		if (data->getln(a, fctx, line) != 0) {
145 			line = NULL;
146 			return (IMAP_TAG_ERROR);
147 		}
148 		if (*line == NULL)
149 			continue;
150 
151 		tag = imap_tag(*line);
152 		if (tag != IMAP_TAG_NONE)
153 			break;
154 	}
155 
156 	if (tag == IMAP_TAG_CONTINUE)
157 		return (IMAP_TAG_CONTINUE);
158 	if (tag != data->tag)
159 		return (IMAP_TAG_ERROR);
160 	return (tag);
161 }
162 
163 int
deliver_imap_deliver(struct deliver_ctx * dctx,struct actitem * ti)164 deliver_imap_deliver(struct deliver_ctx *dctx, struct actitem *ti)
165 {
166 	struct account			*a = dctx->account;
167 	struct mail			*m = dctx->mail;
168 	struct deliver_imap_data	*data = ti->data;
169 	struct io			*io;
170 	struct fetch_ctx		 fctx;
171 	struct fetch_imap_data		 fdata;
172 	char				*cause, *folder, *ptr, *line;
173 	size_t				 len, maillen;
174 	u_int				 total, body;
175 
176 	/* Connect to the IMAP server. */
177 	io = connectproxy(&data->server,
178 	    conf.verify_certs, conf.proxy, IO_CRLF, conf.timeout, &cause);
179 	if (io == NULL) {
180 		log_warnx("%s: %s", a->name, cause);
181 		xfree(cause);
182 		return (DELIVER_FAILURE);
183 	}
184 	if (conf.debug > 3 && !conf.syslog)
185 		io->dup_fd = STDOUT_FILENO;
186 
187 	/* Work out the folder name. */
188 	folder = replacestr(&data->folder, m->tags, m, &m->rml);
189 	if (folder == NULL || *folder == '\0') {
190 		log_warnx("%s: empty folder", a->name);
191 		goto error;
192 	}
193 
194 	/* Fake up the fetch context for the fetch code. */
195 	memset(&fdata, 0, sizeof fdata);
196 	fdata.user = data->user;
197 	fdata.pass = data->pass;
198 	fdata.nocrammd5 = data->nocrammd5;
199 	fdata.nologin = data->nologin;
200 	memcpy(&fdata.server, &data->server, sizeof fdata.server);
201 	fdata.io = io;
202 	fdata.only = FETCH_ONLY_ALL;
203 	a->data = &fdata;
204 	fetch_imap_state_init(a, &fctx);
205 	fctx.state = imap_state_connected;
206 	fctx.llen = IO_LINESIZE;
207 	fctx.lbuf = xmalloc(fctx.llen);
208 
209 	/* Use the fetch code until the select1 state is reached. */
210 	if (deliver_imap_pollto(imap_state_select1, a, io, &fctx) != 0)
211 		goto error;
212 
213 retry:
214 	/* Send an append command. */
215 	if (imap_putln(a, "%u APPEND {%zu}", ++fdata.tag, strlen(folder)) != 0)
216 		goto error;
217 	switch (deliver_imap_waitappend(a, &fctx, io, &line)) {
218 	case IMAP_TAG_ERROR:
219 		if (line != NULL)
220 			imap_invalid(a, line);
221 		goto error;
222 	case IMAP_TAG_CONTINUE:
223 		break;
224 	default:
225 		if (imap_no(line) && strstr(line, "[TRYCREATE]") != NULL)
226 			goto try_create;
227 		imap_invalid(a, line);
228 		goto error;
229 	}
230 
231 	/*
232 	 * Send the mail size, not forgetting lines are CRLF terminated. The
233 	 * Google IMAP server is written strangely, so send the size as if
234 	 * every CRLF was a CR if the server has XYZZY.
235 	 */
236 	count_lines(m, &total, &body);
237 	maillen = m->size + total - 1;
238 	if (fdata.capa & IMAP_CAPA_XYZZY) {
239 		log_debug2("%s: adjusting size: actual %zu", a->name, maillen);
240 		maillen = m->size;
241 	}
242 	if (fdata.capa & IMAP_CAPA_NOSPACE) {
243 		if (imap_putln(a, "%s{%zu}", folder, maillen) != 0)
244 			goto error;
245 	} else {
246 		if (imap_putln(a, "%s {%zu}", folder, maillen) != 0)
247 			goto error;
248 	}
249 	switch (deliver_imap_waitappend(a, &fctx, io, &line)) {
250 	case IMAP_TAG_ERROR:
251 		if (line != NULL)
252 			imap_invalid(a, line);
253 		goto error;
254 	case IMAP_TAG_CONTINUE:
255 		break;
256 	default:
257 		if (imap_no(line) && strstr(line, "[TRYCREATE]") != NULL)
258 			goto try_create;
259 		imap_invalid(a, line);
260 		goto error;
261 	}
262 
263 	/* Send the mail data. */
264 	line_init(m, &ptr, &len);
265 	while (ptr != NULL) {
266 		if (len > 1)
267 			io_write(io, ptr, len - 1);
268 		io_writeline(io, NULL);
269 
270 		/* Update if necessary. */
271 		if (io_update(io, conf.timeout, &cause) != 1) {
272 			log_warnx("%s: %s", a->name, cause);
273 			xfree(cause);
274 			goto error;
275 		}
276 
277 		line_next(m, &ptr, &len);
278 	}
279 
280 	/* Wait for an okay from the server. */
281 	switch (deliver_imap_waitappend(a, &fctx, io, &line)) {
282 	case IMAP_TAG_ERROR:
283 	case IMAP_TAG_CONTINUE:
284 		if (line != NULL)
285 			imap_invalid(a, line);
286 		goto error;
287 	default:
288 		if (imap_okay(line))
289 			break;
290 		if (strstr(line, "[TRYCREATE]") != NULL)
291 			goto try_create;
292 		imap_invalid(a, line);
293 		goto error;
294 	}
295 
296 	if (imap_putln(a, "%u LOGOUT", ++fdata.tag) != 0)
297 		goto error;
298 	if (deliver_imap_waitokay(a, &fctx, io, &line) != 0)
299 		goto error;
300 
301 	xfree(fctx.lbuf);
302 	xfree(folder);
303 
304 	fdata.disconnect(a);
305 	return (DELIVER_SUCCESS);
306 
307 try_create:	/* XXX function? */
308 	/* Try to create the folder. */
309 	if (imap_putln(a, "%u CREATE {%zu}", ++fdata.tag, strlen(folder)) != 0)
310 		goto error;
311 	if (deliver_imap_waitcontinue(a, &fctx, io, &line) != 0)
312 		goto error;
313 	if (imap_putln(a, "%s", folder) != 0)
314 		goto error;
315 	if (deliver_imap_waitokay(a, &fctx, io, &line) != 0)
316 		goto error;
317 	goto retry;
318 
319 error:
320 	io_writeline(io, "QUIT");
321 	io_flush(io, conf.timeout, NULL);
322 
323 	xfree(fctx.lbuf);
324 	if (folder != NULL)
325 		xfree(folder);
326 
327 	fdata.disconnect(a);
328 	return (DELIVER_FAILURE);
329 }
330 
331 void
deliver_imap_desc(struct actitem * ti,char * buf,size_t len)332 deliver_imap_desc(struct actitem *ti, char *buf, size_t len)
333 {
334 	struct deliver_imap_data	*data = ti->data;
335 
336 	xsnprintf(buf, len, "imap%s server \"%s\" port %s folder \"%s\"",
337 	    data->server.ssl ? "s" : "", data->server.host, data->server.port,
338 	    data->folder.str);
339 }
340