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