1 /* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "lib.h"
4 #include "ioloop.h"
5 #include "istream.h"
6 #include "bsearch-insert-pos.h"
7 #include "str.h"
8 #include "sort.h"
9 #include "strnum.h"
10 #include "index-mail.h"
11 #include "pop3c-client.h"
12 #include "pop3c-storage.h"
13 #include "pop3c-sync.h"
14 #include "mailbox-recent-flags.h"
15 
16 struct pop3c_sync_msg {
17 	uint32_t seq;
18 	const char *uidl;
19 };
20 ARRAY_DEFINE_TYPE(pop3c_sync_msg, struct pop3c_sync_msg);
21 
pop3c_sync_get_uidls(struct pop3c_mailbox * mbox)22 int pop3c_sync_get_uidls(struct pop3c_mailbox *mbox)
23 {
24 	ARRAY_TYPE(const_string) uidls;
25 	struct istream *input;
26 	const char *error, *cline;
27 	char *line, *p;
28 	unsigned int seq, line_seq;
29 
30 	if (mbox->msg_uidls != NULL)
31 		return 0;
32 	if ((pop3c_client_get_capabilities(mbox->client) &
33 	     POP3C_CAPABILITY_UIDL) == 0) {
34 		mail_storage_set_error(mbox->box.storage,
35 				       MAIL_ERROR_NOTPOSSIBLE,
36 				       "UIDLs not supported by server");
37 		return -1;
38 	}
39 
40 	if (pop3c_client_cmd_stream(mbox->client, "UIDL\r\n",
41 				    &input, &error) < 0) {
42 		mailbox_set_critical(&mbox->box, "UIDL failed: %s", error);
43 		return -1;
44 	}
45 
46 	mbox->uidl_pool = pool_alloconly_create("POP3 UIDLs", 1024*32);
47 	p_array_init(&uidls, mbox->uidl_pool, 64); seq = 0;
48 	while ((line = i_stream_read_next_line(input)) != NULL) {
49 		seq++;
50 		p = strchr(line, ' ');
51 		if (p == NULL) {
52 			mailbox_set_critical(&mbox->box,
53 				"Invalid UIDL line: %s", line);
54 			break;
55 		}
56 		*p++ = '\0';
57 		if (str_to_uint(line, &line_seq) < 0 || line_seq != seq) {
58 			mailbox_set_critical(&mbox->box,
59 				"Unexpected UIDL seq: %s != %u", line, seq);
60 			break;
61 		}
62 
63 		cline = p_strdup(mbox->uidl_pool, p);
64 		array_push_back(&uidls, &cline);
65 	}
66 	i_stream_destroy(&input);
67 	if (line != NULL) {
68 		pool_unref(&mbox->uidl_pool);
69 		return -1;
70 	}
71 	if (seq == 0) {
72 		/* make msg_uidls non-NULL */
73 		array_append_zero(&uidls);
74 	}
75 	mbox->msg_uidls = array_front(&uidls);
76 	mbox->msg_count = seq;
77 	return 0;
78 }
79 
pop3c_sync_get_sizes(struct pop3c_mailbox * mbox)80 int pop3c_sync_get_sizes(struct pop3c_mailbox *mbox)
81 {
82 	struct istream *input;
83 	const char *error;
84 	char *line, *p;
85 	unsigned int seq, line_seq;
86 
87 	i_assert(mbox->msg_sizes == NULL);
88 
89 	if (mbox->msg_uidls == NULL) {
90 		if (pop3c_sync_get_uidls(mbox) < 0)
91 			return -1;
92 	}
93 	if (mbox->msg_count == 0) {
94 		mbox->msg_sizes = i_new(uoff_t, 1);
95 		return 0;
96 	}
97 
98 	if (pop3c_client_cmd_stream(mbox->client, "LIST\r\n",
99 				    &input, &error) < 0) {
100 		mailbox_set_critical(&mbox->box, "LIST failed: %s", error);
101 		return -1;
102 	}
103 
104 	mbox->msg_sizes = i_new(uoff_t, mbox->msg_count); seq = 0;
105 	while ((line = i_stream_read_next_line(input)) != NULL) {
106 		if (++seq > mbox->msg_count) {
107 			mailbox_set_critical(&mbox->box,
108 				"Too much data in LIST: %s", line);
109 			break;
110 		}
111 		p = strchr(line, ' ');
112 		if (p == NULL) {
113 			mailbox_set_critical(&mbox->box,
114 				"Invalid LIST line: %s", line);
115 			break;
116 		}
117 		*p++ = '\0';
118 		if (str_to_uint(line, &line_seq) < 0 || line_seq != seq) {
119 			mailbox_set_critical(&mbox->box,
120 				"Unexpected LIST seq: %s != %u", line, seq);
121 			break;
122 		}
123 		if (str_to_uoff(p, &mbox->msg_sizes[seq-1]) < 0) {
124 			mailbox_set_critical(&mbox->box,
125 				"Invalid LIST size: %s", p);
126 			break;
127 		}
128 	}
129 	i_stream_destroy(&input);
130 	if (line != NULL) {
131 		i_free_and_null(mbox->msg_sizes);
132 		return -1;
133 	}
134 	return 0;
135 }
136 
137 static void
pop3c_get_local_msgs(pool_t pool,ARRAY_TYPE (pop3c_sync_msg)* local_msgs,uint32_t messages_count,struct mail_cache_view * cache_view,unsigned int cache_idx)138 pop3c_get_local_msgs(pool_t pool, ARRAY_TYPE(pop3c_sync_msg) *local_msgs,
139 		     uint32_t messages_count,
140 		     struct mail_cache_view *cache_view,
141 		     unsigned int cache_idx)
142 {
143 	string_t *str = t_str_new(128);
144 	struct pop3c_sync_msg msg;
145 	uint32_t seq;
146 
147 	i_zero(&msg);
148 	for (seq = 1; seq <= messages_count; seq++) {
149 		str_truncate(str, 0);
150 		if (mail_cache_lookup_field(cache_view, str, seq,
151 					    cache_idx) > 0)
152 			msg.uidl = p_strdup(pool, str_c(str));
153 		msg.seq = seq;
154 		array_idx_set(local_msgs, seq-1, &msg);
155 	}
156 }
157 
158 static void
pop3c_get_remote_msgs(ARRAY_TYPE (pop3c_sync_msg)* remote_msgs,struct pop3c_mailbox * mbox)159 pop3c_get_remote_msgs(ARRAY_TYPE(pop3c_sync_msg) *remote_msgs,
160 		      struct pop3c_mailbox *mbox)
161 {
162 	struct pop3c_sync_msg *msg;
163 	uint32_t seq;
164 
165 	for (seq = 1; seq <= mbox->msg_count; seq++) {
166 		msg = array_append_space(remote_msgs);
167 		msg->seq = seq;
168 		msg->uidl = mbox->msg_uidls[seq-1];
169 	}
170 }
171 
pop3c_sync_msg_uidl_cmp(const struct pop3c_sync_msg * msg1,const struct pop3c_sync_msg * msg2)172 static int pop3c_sync_msg_uidl_cmp(const struct pop3c_sync_msg *msg1,
173 				   const struct pop3c_sync_msg *msg2)
174 {
175 	return null_strcmp(msg1->uidl, msg2->uidl);
176 }
177 
178 static void
pop3c_sync_messages(struct pop3c_mailbox * mbox,struct mail_index_view * sync_view,struct mail_index_transaction * sync_trans,struct mail_cache_view * cache_view)179 pop3c_sync_messages(struct pop3c_mailbox *mbox,
180 		    struct mail_index_view *sync_view,
181 		    struct mail_index_transaction *sync_trans,
182 		    struct mail_cache_view *cache_view)
183 {
184 	struct index_mailbox_context *ibox =
185 		INDEX_STORAGE_CONTEXT(&mbox->box);
186 	const struct mail_index_header *hdr;
187 	struct mail_cache_transaction_ctx *cache_trans;
188 	ARRAY_TYPE(pop3c_sync_msg) local_msgs, remote_msgs;
189 	const struct pop3c_sync_msg *lmsg, *rmsg;
190 	uint32_t seq1, seq2, next_uid;
191 	unsigned int lidx, ridx, lcount, rcount;
192 	unsigned int cache_idx = ibox->cache_fields[MAIL_CACHE_POP3_UIDL].idx;
193 	pool_t pool;
194 
195 	i_assert(mbox->msg_uids == NULL);
196 
197 	/* set our uidvalidity */
198 	hdr = mail_index_get_header(sync_view);
199 	if (hdr->uid_validity == 0) {
200 		uint32_t uid_validity = ioloop_time;
201 		mail_index_update_header(sync_trans,
202 			offsetof(struct mail_index_header, uid_validity),
203 			&uid_validity, sizeof(uid_validity), TRUE);
204 	}
205 
206 	pool = pool_alloconly_create(MEMPOOL_GROWING"pop3c sync", 10240);
207 	p_array_init(&local_msgs, pool, hdr->messages_count);
208 	pop3c_get_local_msgs(pool, &local_msgs, hdr->messages_count,
209 			     cache_view, cache_idx);
210 	p_array_init(&remote_msgs, pool, mbox->msg_count);
211 	pop3c_get_remote_msgs(&remote_msgs, mbox);
212 
213 	/* sort the messages by UIDLs, because some servers reorder messages */
214 	array_sort(&local_msgs, pop3c_sync_msg_uidl_cmp);
215 	array_sort(&remote_msgs, pop3c_sync_msg_uidl_cmp);
216 
217 	/* skip over existing messages with matching UIDLs and expunge the ones
218 	   that no longer exist in remote. */
219 	mbox->msg_uids = mbox->msg_count == 0 ?
220 		i_new(uint32_t, 1) : /* avoid malloc(0) assert */
221 		i_new(uint32_t, mbox->msg_count);
222 	cache_trans = mail_cache_get_transaction(cache_view, sync_trans);
223 
224 	lmsg = array_get(&local_msgs, &lcount);
225 	rmsg = array_get(&remote_msgs, &rcount);
226 	next_uid = hdr->next_uid;
227 	lidx = ridx = 0;
228 	while (lidx < lcount || ridx < rcount) {
229 		uint32_t lseq = lidx < lcount ? lmsg[lidx].seq : 0;
230 		uint32_t rseq = ridx < rcount ? rmsg[ridx].seq : 0;
231 		int ret;
232 
233 		if (lidx >= lcount)
234 			ret = 1;
235 		else if (ridx >= rcount || lmsg[lidx].uidl == NULL)
236 			ret = -1;
237 		else
238 			ret = strcmp(lmsg[lidx].uidl, rmsg[ridx].uidl);
239 		if (ret < 0) {
240 			/* message expunged in remote, or we didn't have a
241 			   local message's UIDL in cache. */
242 			mail_index_expunge(sync_trans, lseq);
243 			lidx++;
244 		} else if (ret > 0) {
245 			/* new message in remote */
246 			i_assert(mbox->msg_uids[rseq-1] == 0);
247 			mbox->msg_uids[rseq-1] = next_uid;
248 			mail_index_append(sync_trans, next_uid++, &lseq);
249 			mail_cache_add(cache_trans, lseq, cache_idx,
250 				       rmsg[ridx].uidl,
251 				       strlen(rmsg[ridx].uidl));
252 			ridx++;
253 		} else {
254 			/* UIDL matched */
255 			i_assert(mbox->msg_uids[rseq-1] == 0);
256 			mail_index_lookup_uid(sync_view, lseq,
257 					      &mbox->msg_uids[rseq-1]);
258 			lidx++;
259 			ridx++;
260 		}
261 	}
262 
263 	/* mark the newly seen messages as recent */
264 	if (mail_index_lookup_seq_range(sync_view, hdr->first_recent_uid,
265 					hdr->next_uid, &seq1, &seq2))
266 		mailbox_recent_flags_set_seqs(&mbox->box, sync_view, seq1, seq2);
267 	pool_unref(&pool);
268 }
269 
pop3c_sync(struct pop3c_mailbox * mbox)270 int pop3c_sync(struct pop3c_mailbox *mbox)
271 {
272         struct mail_index_sync_ctx *index_sync_ctx;
273 	struct mail_index_view *sync_view, *trans_view;
274 	struct mail_index_transaction *sync_trans;
275 	struct mail_index_sync_rec sync_rec;
276 	struct mail_cache_view *cache_view = NULL;
277 	enum mail_index_sync_flags sync_flags;
278 	unsigned int idx;
279 	string_t *str;
280 	const char *reply;
281 	int ret;
282 	bool deletions = FALSE;
283 
284 	if (pop3c_sync_get_uidls(mbox) < 0)
285 		return -1;
286 
287 	sync_flags = index_storage_get_sync_flags(&mbox->box) |
288 		MAIL_INDEX_SYNC_FLAG_FLUSH_DIRTY;
289 
290 	ret = mail_index_sync_begin(mbox->box.index, &index_sync_ctx,
291 				    &sync_view, &sync_trans, sync_flags);
292 	if (ret <= 0) {
293 		if (ret < 0)
294 			mailbox_set_index_error(&mbox->box);
295 		return ret;
296 	}
297 
298 	if (mbox->msg_uids == NULL) {
299 		trans_view = mail_index_transaction_open_updated_view(sync_trans);
300 		cache_view = mail_cache_view_open(mbox->box.cache, trans_view);
301 		pop3c_sync_messages(mbox, sync_view, sync_trans, cache_view);
302 	}
303 
304 	/* mark expunges messages as deleted in this pop3 session,
305 	   if those exist */
306 	str = t_str_new(32);
307 	while (mail_index_sync_next(index_sync_ctx, &sync_rec)) {
308 		if (sync_rec.type != MAIL_INDEX_SYNC_TYPE_EXPUNGE)
309 			continue;
310 
311 		if (!bsearch_insert_pos(&sync_rec.uid1, mbox->msg_uids,
312 					mbox->msg_count, sizeof(uint32_t),
313 					uint32_cmp, &idx)) {
314 			/* no such messages in this session */
315 			continue;
316 		}
317 		for (; idx < mbox->msg_count; idx++) {
318 			i_assert(mbox->msg_uids[idx] >= sync_rec.uid1);
319 			if (mbox->msg_uids[idx] > sync_rec.uid2)
320 				break;
321 
322 			str_truncate(str, 0);
323 			str_printfa(str, "DELE %u\r\n", idx+1);
324 			pop3c_client_cmd_line_async_nocb(mbox->client, str_c(str));
325 			deletions = TRUE;
326 		}
327 	}
328 
329 	if (mail_index_sync_commit(&index_sync_ctx) < 0) {
330 		mailbox_set_index_error(&mbox->box);
331 		return -1;
332 	}
333 	if (cache_view != NULL) {
334 		mail_cache_view_close(&cache_view);
335 		mail_index_view_close(&trans_view);
336 	}
337 	if (deletions) {
338 		if (pop3c_client_cmd_line(mbox->client, "QUIT\r\n",
339 					  &reply) < 0) {
340 			mail_storage_set_error(mbox->box.storage,
341 					       MAIL_ERROR_TEMP, reply);
342 			return -1;
343 		}
344 	}
345 	return 0;
346 }
347 
348 struct mailbox_sync_context *
pop3c_storage_sync_init(struct mailbox * box,enum mailbox_sync_flags flags)349 pop3c_storage_sync_init(struct mailbox *box, enum mailbox_sync_flags flags)
350 {
351 	struct pop3c_mailbox *mbox = POP3C_MAILBOX(box);
352 	int ret = 0;
353 
354 	if ((flags & MAILBOX_SYNC_FLAG_FULL_READ) != 0 &&
355 	    mbox->msg_uidls == NULL) {
356 		/* FIXME: reconnect */
357 	}
358 
359 	ret = pop3c_sync(mbox);
360 	return index_mailbox_sync_init(box, flags, ret < 0);
361 }
362