1 /*
2 
3  Copyright (c) 2004-2012 NFG Net Facilities Group BV support@nfg.nl
4 
5  This program is free software; you can redistribute it and/or
6  modify it under the terms of the GNU General Public License
7  as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later
9  version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20 
21 #include <string.h>
22 
23 #include "dbmail.h"
24 #include "dm_mailboxstate.h"
25 #include "dm_db.h"
26 
27 #define THIS_MODULE "MailboxState"
28 
29 /*
30  */
31 extern DBParam_T db_params;
32 extern Mempool_T small_pool;
33 extern const char *imap_flag_desc_escaped[];
34 #define DBPFX db_params.pfx
35 
36 #define T MailboxState_T
37 
38 
39 static void db_getmailbox_seq(T M, Connection_T c);
40 static void db_getmailbox_permission(T M, Connection_T c);
41 static void state_load_metadata(T M, Connection_T c);
42 static void MailboxState_setMsginfo(T M, GTree *msginfo);
43 /* */
44 
MailboxState_uid_msn_new(T M)45 static void MailboxState_uid_msn_new(T M)
46 {
47 	if (M->msn) g_tree_destroy(M->msn);
48 	M->msn = g_tree_new_full((GCompareDataFunc)ucmpdata,NULL,NULL,NULL);
49 
50 	if (M->ids) g_tree_destroy(M->ids);
51 	M->ids = g_tree_new_full((GCompareDataFunc)ucmpdata,NULL,NULL,(GDestroyNotify)g_free);
52 }
53 
MessageInfo_free(MessageInfo * m)54 static void MessageInfo_free(MessageInfo *m)
55 {
56 	g_list_destroy(m->keywords);
57 	g_free(m);
58 }
59 
60 
state_load_messages(T M,Connection_T c,gboolean coldLoad)61 static T state_load_messages(T M, Connection_T c, gboolean coldLoad)
62 {
63 	unsigned nrows = 0, i = 0, j;
64 	struct timeval before, after;
65 	const char *query_result;
66 	uint64_t tempId;
67 	MessageInfo *result;
68 	GTree *msginfo;
69 	uint64_t *uid, id = 0;
70 	ResultSet_T r;
71 	PreparedStatement_T stmt;
72 	Field_T frag;
73 	INIT_QUERY;
74 	char filterCondition[64];  memset(filterCondition,0,64);
75 	/* the initialization should be done elsewhere, see ols MailboxState_new and MailboxState_update */
76 	msginfo=MailboxState_getMsginfo(M);
77 
78 	if (coldLoad){
79 	    //msginfo = g_tree_new_full((GCompareDataFunc)ucmpdata, NULL,(GDestroyNotify)g_free,(GDestroyNotify)MessageInfo_free);
80 	    TRACE(TRACE_DEBUG, "SEQ New");
81 	    snprintf(filterCondition,64-1,"/*SEQ New*/ AND m.status < %d ", MESSAGE_STATUS_DELETE);
82 	}else{
83 	    uint64_t seq=MailboxState_getSeq(M);
84 	    //msginfo=MailboxState_getMsginfo(M);
85 
86 	    TRACE(TRACE_DEBUG, "SEQ RENew");
87 	    //MailboxState_uid_msn_new(M);
88 	    /* use seq to select only changed elements, event this which are deleted*/
89 	    snprintf(filterCondition,64-1,"/*SEQ ReNew*/ AND m.seq >= %" PRIu64 "-1 AND m.status <= %d ", seq, MESSAGE_STATUS_DELETE );
90 	}
91 
92 
93 	date2char_str("internal_date", &frag);
94 	snprintf(query, DEF_QUERYSIZE-1,
95 			"SELECT seen_flag, answered_flag, deleted_flag, flagged_flag, "
96 			"draft_flag, recent_flag, %s, rfcsize, seq, m.message_idnr, status, m.physmessage_id "
97 			"FROM %smessages m "
98 			"LEFT JOIN %sphysmessage p ON p.id = m.physmessage_id "
99 			"WHERE m.mailbox_idnr = ? %s "
100 			"ORDER BY m.seq DESC",
101 			frag,
102 			DBPFX, DBPFX,
103 			filterCondition);
104 
105 
106 
107 	stmt = db_stmt_prepare(c, query);
108 	db_stmt_set_u64(stmt, 1, M->id);
109 	gettimeofday(&before, NULL);
110 	r = db_stmt_query(stmt);
111 	gettimeofday(&after, NULL);
112 	log_query_time(query,before,after);
113 	i = 0;
114 	gettimeofday(&before, NULL);
115 	int shouldAdd = 0;
116 	while (db_result_next(r)) {
117 		i++;
118 
119 		id = db_result_get_u64(r, IMAP_NFLAGS + 3);
120 
121 		/* reset */
122 		shouldAdd=0;
123 		if (coldLoad){
124 		    /* new element*/
125 		    result = g_new0(MessageInfo,1);
126 			uid = g_new0(uint64_t,1);
127 			*uid = id;
128 		    shouldAdd=1;
129 		    result->expunge=0;
130 		    result->expunged=0;
131 			//TRACE(TRACE_DEBUG, "SEQ CREATED %ld",id);
132 		}else{
133 		    /* soft renew, so search */
134 		    result = g_tree_lookup(msginfo, &id);
135 		    if (result == NULL){
136 				/* not found so create*/
137 				result = g_new0(MessageInfo,1);
138 				uid = g_new0(uint64_t,1);
139 				*uid = id;
140 				shouldAdd=1;
141 				result->expunge=0;
142 				result->expunged=0;
143 		    }else{
144 				//TRACE(TRACE_DEBUG, "SEQ FOUND %ld",id);
145 		    }
146 		}
147 
148 		/* id */
149 		result->uid = id;
150 
151 		/* mailbox_id */
152 		result->mailbox_id = M->id;
153 
154 		/* flags */
155 		for (j = 0; j < IMAP_NFLAGS; j++)
156 			result->flags[j] = db_result_get_bool(r,j);
157 
158 		/* internal date */
159 		query_result = db_result_get(r,IMAP_NFLAGS);
160 		strncpy(result->internaldate,
161 				(query_result) ? query_result :
162 				"01-Jan-1970 00:00:01 +0100",
163 				IMAP_INTERNALDATE_LEN-1);
164 
165 		/* rfcsize */
166 		result->rfcsize = db_result_get_u64(r,IMAP_NFLAGS + 1);
167 		/* modseq */
168 		result->seq = db_result_get_u64(r,IMAP_NFLAGS + 2);
169 		/* status */
170 		result->status = db_result_get_int(r, IMAP_NFLAGS + 4);
171 		/* physmessage_id */
172 		result->phys_id = db_result_get_int(r, IMAP_NFLAGS + 5);
173 
174 		if (result->status>=MESSAGE_STATUS_DELETE /*|| result->flags[IMAP_FLAG_DELETED]==1*/ ){
175 		    /* message is delete so mark as to be expunged */
176 		    result->expunge++;
177 		    if (result->expunged==1){
178 				if (shouldAdd==0){
179 					/* remove the node if exists  from message info, should be removed, but we will not due to some references present*/
180 					//g_tree_remove(msginfo, &id);
181 					continue;
182 				}
183 		    }else{
184 				if (shouldAdd==1){
185 					/* message is in state of state=2 or already deleted but not in our state */
186 					g_tree_remove(msginfo,uid);
187 					g_free(result);
188 					g_free(uid);
189 					continue;
190 				}
191 
192 		    }
193 		}
194 
195 		if (shouldAdd==1){
196 			//TRACE(TRACE_DEBUG, "SEQ ADDED %ld",id);
197 		    /* it's new */
198 			g_tree_insert(msginfo, uid, result);
199 		}else{
200 		    /* no need, result was updated */
201 		}
202 
203 	}
204 	gettimeofday(&after, NULL);
205 	log_query_time("Parsing State ",before,after);
206 	if (! i) { // empty mailbox
207 		MailboxState_setMsginfo(M, msginfo);
208 		return M;
209 	}
210 
211 	db_con_clear(c);
212 
213 	memset(query, 0, sizeof(query));
214 	snprintf(query, DEF_QUERYSIZE-1,
215 		"SELECT k.message_idnr, k.keyword FROM %skeywords k "
216 		"LEFT JOIN %smessages m ON k.message_idnr=m.message_idnr "
217 		"WHERE m.mailbox_idnr = ? %s "
218 		"order by m.message_idnr "
219 		,
220 		DBPFX, DBPFX,
221 		filterCondition);
222 
223 	nrows = 0;
224 	stmt = db_stmt_prepare(c, query);
225 	db_stmt_set_u64(stmt, 1, M->id);
226 	r = db_stmt_query(stmt);
227 	gettimeofday(&before, NULL);
228 	tempId=0;
229 
230 	while (db_result_next(r)) {
231 		nrows++;
232 		id = db_result_get_u64(r,0);
233 
234 		const char * keyword = db_result_get(r,1);
235 		if (strlen(keyword)>0){
236 			TRACE(TRACE_INFO, "Keyword line [%d %s]", nrows, keyword);
237 			/* id is presented via query in ordered fashion so, we use tempId as a cached last tree lookup */
238 			if ( tempId!=id || tempId==0 ){
239 				result = g_tree_lookup(msginfo, &id);
240 				tempId=id;
241 			}
242 		    if ( result != NULL ){
243 				result->keywords = g_list_append(result->keywords, g_strdup(keyword));
244 			}
245 		}
246 	}
247 	if (! nrows) TRACE(TRACE_DEBUG, "no keywords");
248 
249 	gettimeofday(&after, NULL);
250 	log_query_time("Parsing Keywords ",before,after);
251 	/* on both cases can use the same function due to some checks at MailboxState_setMsgInfo*/
252 	MailboxState_setMsginfo(M, msginfo);
253 	return M;
254 }
255 
_compare_data(gconstpointer a,gconstpointer b,gpointer UNUSED data)256 gboolean _compare_data(gconstpointer a, gconstpointer b, gpointer UNUSED data)
257 {
258 	return strcmp((const char *)a,(const char *)b);
259 }
260 
MailboxState_new(Mempool_T pool,uint64_t id)261 T MailboxState_new(Mempool_T pool, uint64_t id)
262 {
263 	T M; Connection_T c;
264 	volatile int t = DM_SUCCESS;
265 	gboolean freepool = FALSE;
266 
267 	if (! pool) {
268 		pool = mempool_open();
269 		freepool = TRUE;
270 	}
271 
272 	M = mempool_pop(pool, sizeof(*M));
273 	M->pool = pool;
274 	M->freepool = freepool;
275 
276 	if (! id) return M;
277 
278 	M->id = id;
279 	M->recent_queue = g_tree_new((GCompareFunc)ucmp);
280 	M->keywords     = g_tree_new_full((GCompareDataFunc)_compare_data,NULL,g_free,NULL);
281 	M->msginfo		= g_tree_new_full((GCompareDataFunc)ucmpdata, NULL,(GDestroyNotify)g_free,(GDestroyNotify)MessageInfo_free);
282 	M->differential_iterations = 0;
283 	c = db_con_get();
284 	TRY
285 		db_begin_transaction(c); // we need read-committed isolation
286 		state_load_metadata(M, c);
287 		state_load_messages(M, c, true);
288 		db_commit_transaction(c);
289 	CATCH(SQLException)
290 		LOG_SQLERROR;
291 		db_rollback_transaction(c);
292 		t = DM_EQUERY;
293 	FINALLY
294 		db_con_close(c);
295 	END_TRY;
296 
297 	if (t == DM_EQUERY) {
298 		TRACE(TRACE_ERR, "Error opening mailbox");
299 		MailboxState_free(&M);
300 	}
301 
302 	return M;
303 }
304 
305 /**
306  * Update only the mailbox.
307  * @param M
308  * @return
309  */
310 
MailboxState_update(Mempool_T pool,T OldM)311 T MailboxState_update(Mempool_T pool, T OldM)
312 {
313 
314 	T M; Connection_T c;
315 	volatile int t = DM_SUCCESS;
316 	gboolean freepool = FALSE;
317 	uint64_t id;
318 
319 	/* differential mode, evaluate max iterations */
320 	int mailbox_diffential_max_iterations = config_get_value_default_int("mailbox_update_strategy_2_max_iterations", "IMAP", 300);
321 	if (mailbox_diffential_max_iterations > 0 &&  OldM->differential_iterations >= mailbox_diffential_max_iterations-1 ){
322 		TRACE(TRACE_DEBUG, "Strategy differential mode override due to max iterations, see config [IMAP] mailbox_update_strategy_2_max_iterations");
323 		return MailboxState_new(pool, OldM->id);
324 	}
325 
326 
327 	if (! pool) {
328 		pool = mempool_open();
329 		freepool = TRUE;
330 	}
331 	id = OldM->id;
332 	M = mempool_pop(pool, sizeof(*M));
333 	M->pool = pool;
334 	M->freepool = freepool;
335 
336 	if (! id) return M;
337 
338 	M->id = id;
339 	M->recent_queue = g_tree_new((GCompareFunc)ucmp);
340 
341 	M->keywords     = g_tree_new_full((GCompareDataFunc)_compare_data,NULL,g_free,NULL);
342 	M->msginfo     = g_tree_new_full((GCompareDataFunc)ucmpdata, NULL,(GDestroyNotify)g_free,(GDestroyNotify)MessageInfo_free);
343 	// increase differential iterations in order to apply mailbox_update_strategy_2_max_iterations
344 	M->differential_iterations = OldM->differential_iterations + 1;
345 	TRACE(TRACE_DEBUG, "Strategy SEQ UPDATE, iterations %d", M->differential_iterations);
346 	//M->ids     = g_tree_new_full((GCompareDataFunc)_compare_data,NULL,g_free,NULL);
347 	//M->msn     = g_tree_new_full((GCompareDataFunc)_compare_data,NULL,g_free,NULL);
348 
349 	//g_tree_merge(M->recent, OldM->recent, IST_SUBSEARCH_OR);
350 
351 
352 	//g_tree_merge(M->msginfo, OldM->msginfo, IST_SUBSEARCH_OR);
353 	g_tree_copy_MessageInfo(M->msginfo,OldM->msginfo);
354 
355 	//no need, those keywords will be populated at metadata
356 	//@todo change this behaviour in state_load_metadata
357 	//g_tree_copy_String(M->keywords,OldM->keywords);
358 
359 
360 	//MailboxState_remap(M);
361 	/* reset the sequence */
362 	MailboxState_resetSeq(OldM);
363 	//uint64_t seq = MailboxState_getSeq(OldM);
364 
365 	c = db_con_get();
366 	TRY
367 		db_begin_transaction(c); // we need read-committed isolation
368 		state_load_metadata(M, c);
369 		state_load_messages(M, c, false); // do a soft refresh
370 		db_commit_transaction(c);
371 	CATCH(SQLException)
372 		LOG_SQLERROR;
373 		db_rollback_transaction(c);
374 		t = DM_EQUERY;
375 	FINALLY
376 		db_con_close(c);
377 	END_TRY;
378 
379 	if (t == DM_EQUERY) {
380 		TRACE(TRACE_ERR, "SEQ Error opening mailbox");
381 		MailboxState_free(&M);
382 	}
383 
384 	return M;
385 }
386 
MailboxState_remap(T M)387 void MailboxState_remap(T M)
388 {
389 	GList *ids = NULL;
390 	uint64_t *uid, *msn = NULL, rows = 1;
391 	MessageInfo *msginfo;
392 
393 	MailboxState_uid_msn_new(M);
394 
395 	ids = g_tree_keys(M->msginfo);
396 	ids = g_list_first(ids);
397 	while (ids) {
398 		uid = (uint64_t *)ids->data;
399 
400 		msginfo = g_tree_lookup(M->msginfo, uid);
401 		if (msginfo->status < MESSAGE_STATUS_DELETE) {
402 			msn = g_new0(uint64_t,1);
403 			*msn = msginfo->msn = rows++;
404 
405 			g_tree_insert(M->ids, uid, msn);
406 			g_tree_insert(M->msn, msn, uid);
407 		}
408 
409 		if (! g_list_next(ids)) break;
410 		ids = g_list_next(ids);
411 	}
412 
413 	g_list_free(g_list_first(ids));
414 }
415 
MailboxState_getMsginfo(T M)416 GTree * MailboxState_getMsginfo(T M)
417 {
418 	return M->msginfo;
419 }
420 
MailboxState_setMsginfo(T M,GTree * msginfo)421 static void MailboxState_setMsginfo(T M, GTree *msginfo)
422 {
423 	GTree *oldmsginfo = M->msginfo;
424 	M->msginfo = msginfo;
425 	MailboxState_remap(M);
426 	if (oldmsginfo){
427 		/* might be situations when old msginfo is the same as the new */
428 		if (msginfo != oldmsginfo)
429 			g_tree_destroy(oldmsginfo);
430 	}
431 }
432 
MailboxState_addMsginfo(T M,uint64_t uid,MessageInfo * msginfo)433 void MailboxState_addMsginfo(T M, uint64_t uid, MessageInfo *msginfo)
434 {
435 	uint64_t *id = g_new0(uint64_t,1);
436 	*id = uid;
437 	g_tree_insert(M->msginfo, id, msginfo);
438 	if (msginfo->flags[IMAP_FLAG_RECENT] == 1) {
439 		M->seq--; // force resync
440 		M->recent++;
441 	}
442 	MailboxState_build_recent(M);
443 	MailboxState_remap(M);
444 }
445 
MailboxState_removeUid(T M,uint64_t uid)446 int MailboxState_removeUid(T M, uint64_t uid)
447 {
448 	MessageInfo *msginfo = g_tree_lookup(M->msginfo, &uid);
449 	if (! msginfo) {
450 		TRACE(TRACE_WARNING,"trying to remove unknown UID [%" PRIu64 "]", uid);
451 		return DM_EGENERAL;
452 	}
453 	msginfo->status = MESSAGE_STATUS_DELETE;
454 	M->exists--;
455 
456 	MailboxState_remap(M);
457 
458 	return DM_SUCCESS;
459 }
460 
MailboxState_getIds(T M)461 GTree * MailboxState_getIds(T M)
462 {
463 	return M->ids;
464 }
465 
MailboxState_getMsn(T M)466 GTree * MailboxState_getMsn(T M)
467 {
468 	return M->msn;
469 }
470 
MailboxState_setId(T M,uint64_t id)471 void MailboxState_setId(T M, uint64_t id)
472 {
473 	M->id = id;
474 }
MailboxState_getId(T M)475 uint64_t MailboxState_getId(T M)
476 {
477 	return M->id;
478 }
479 
MailboxState_getSeq(T M)480 uint64_t MailboxState_getSeq(T M)
481 {
482  	if (! M->seq) {
483 		Connection_T c = db_con_get();
484 		TRY
485 			db_getmailbox_seq(M, c);
486 		CATCH(SQLException)
487 			LOG_SQLERROR;
488 		FINALLY
489 			db_con_close(c);
490 		END_TRY;
491 	}
492 
493 	return M->seq;
494 }
495 
496 /**
497  * Reset the sequence stored at structure level.
498  * Subsequent call for MailboxState_getSeq will be required to retrieve the the sequence
499  * @param M
500  * @return void
501  */
MailboxState_resetSeq(T M)502 void MailboxState_resetSeq(T M)
503 {
504     M->seq=0;
505 
506 }
507 
508 
509 /**
510  * Reset the sequence stored at structure level and store the new seq from db
511  * Internally it will use MailboxState_getSeq
512  * @param M
513  * @return void
514  */
MailboxState_resyncSeq(T M)515 uint64_t MailboxState_resyncSeq(T M)
516 {
517     M->seq=0;
518     return MailboxState_getSeq(M);
519 }
520 
521 
MailboxState_getExists(T M)522 unsigned MailboxState_getExists(T M)
523 {
524 	int real = g_tree_nnodes(M->ids);
525 	if (real > (int)M->exists) {
526 		TRACE(TRACE_DEBUG, "[%" PRIu64 "] exists [%u] -> [%d]",
527 				M->id, M->exists, real);
528 		M->exists = (unsigned)real;
529 	}
530 	return M->exists;
531 }
532 
MailboxState_setExists(T M,unsigned exists)533 void MailboxState_setExists(T M, unsigned exists)
534 {
535 	TRACE(TRACE_DEBUG, "[%" PRIu64 "] exists [%u] -> [%u]",
536 			M->id, M->exists, exists);
537 	M->exists = exists;
538 }
539 
MailboxState_getRecent(T M)540 unsigned MailboxState_getRecent(T M)
541 {
542 	return M->recent;
543 }
544 
MailboxState_getUidnext(T M)545 uint64_t MailboxState_getUidnext(T M)
546 {
547 	return M->uidnext;
548 }
MailboxState_setOwner(T M,uint64_t owner_id)549 void MailboxState_setOwner(T M, uint64_t owner_id)
550 {
551 	M->owner_id = owner_id;
552 }
MailboxState_getOwner(T M)553 uint64_t MailboxState_getOwner(T M)
554 {
555 	return M->owner_id;
556 }
557 
MailboxState_setPermission(T M,int permission)558 void MailboxState_setPermission(T M, int permission)
559 {
560 	M->permission = permission;
561 }
562 
MailboxState_getPermission(T M)563 unsigned MailboxState_getPermission(T M)
564 {
565 	if (! M->permission) {
566 		Connection_T c = db_con_get();
567 		TRY
568 			db_getmailbox_permission(M, c);
569 		CATCH(SQLException)
570 			LOG_SQLERROR;
571 		FINALLY
572 			db_con_close(c);
573 		END_TRY;
574 	}
575 	return M->permission;
576 }
577 
MailboxState_setName(T M,const char * name)578 void MailboxState_setName(T M, const char *name)
579 {
580 	String_T old = M->name;
581 	M->name = p_string_new(M->pool, name);
582 	if (old)
583 		p_string_free(old, TRUE);
584 }
585 
MailboxState_getName(T M)586 const char * MailboxState_getName(T M)
587 {
588 	return p_string_str(M->name);
589 }
590 
MailboxState_setIsUsers(T M,gboolean t)591 void MailboxState_setIsUsers(T M, gboolean t)
592 {
593 	M->is_users = t;
594 }
595 
MailboxState_isUsers(T M)596 gboolean MailboxState_isUsers(T M)
597 {
598 	return M->is_users;
599 }
600 
MailboxState_setIsPublic(T M,gboolean t)601 void MailboxState_setIsPublic(T M, gboolean t)
602 {
603 	M->is_public = t;
604 }
MailboxState_isPublic(T M)605 gboolean MailboxState_isPublic(T M)
606 {
607 	return M->is_public;
608 }
609 
MailboxState_hasKeyword(T M,const char * keyword)610 gboolean MailboxState_hasKeyword(T M, const char *keyword)
611 {
612 	if (g_tree_lookup(M->keywords, (gpointer)keyword))
613 		return TRUE;
614 	return FALSE;
615 }
MailboxState_addKeyword(T M,const char * keyword)616 void MailboxState_addKeyword(T M, const char *keyword)
617 {
618 	char *kw = g_strdup(keyword);
619 	g_tree_insert(M->keywords, kw, kw);
620 }
621 
MailboxState_setNoSelect(T M,gboolean no_select)622 void MailboxState_setNoSelect(T M, gboolean no_select)
623 {
624 	M->no_select = no_select;
625 }
626 
MailboxState_noSelect(T M)627 gboolean MailboxState_noSelect(T M)
628 {
629 	return M->no_select;
630 }
MailboxState_setNoChildren(T M,gboolean no_children)631 void MailboxState_setNoChildren(T M, gboolean no_children)
632 {
633 	M->no_children = no_children;
634 }
635 
MailboxState_noChildren(T M)636 gboolean MailboxState_noChildren(T M)
637 {
638 	return M->no_children;
639 }
640 
MailboxState_noInferiors(T M)641 gboolean MailboxState_noInferiors(T M)
642 {
643 	return M->no_inferiors;
644 }
645 
MailboxState_getUnseen(T M)646 unsigned MailboxState_getUnseen(T M)
647 {
648 	return M->unseen;
649 }
650 
651 struct filter_range_helper {
652         gboolean uid;
653         uint64_t min;
654         uint64_t max;
655         GTree *a;
656 };
657 
filter_range(gpointer key,gpointer value,gpointer data)658 static int filter_range(gpointer key, gpointer value, gpointer data)
659 {
660 	uint64_t *k, *v;
661 	struct filter_range_helper *d = (struct filter_range_helper *)data;
662 
663 	if (*(uint64_t *)key < d->min) return FALSE; // skip
664 	if (*(uint64_t *)key > d->max) return TRUE; // done
665 
666 	k = mempool_pop(small_pool, sizeof(uint64_t));
667 	v = mempool_pop(small_pool, sizeof(uint64_t));
668 
669 	*k = *(uint64_t *)key;
670 	*v = *(uint64_t *)value;
671 
672 	if (d->uid)
673 		g_tree_insert(d->a, k, v);
674 	else
675 		g_tree_insert(d->a, v, k);
676 
677 	return FALSE;
678 }
679 
find_range(GTree * c,uint64_t l,uint64_t r,GTree * a,gboolean uid)680 static void find_range(GTree *c, uint64_t l, uint64_t r, GTree *a, gboolean uid)
681 {
682 	struct filter_range_helper data;
683 
684 	data.uid = uid;
685 	data.min = l;
686 	data.max = r;
687 	data.a = a;
688 
689 	g_tree_foreach(c, (GTraverseFunc)filter_range, &data);
690 }
691 
MailboxState_get_set(MailboxState_T M,const char * set,gboolean uid)692 GTree * MailboxState_get_set(MailboxState_T M, const char *set, gboolean uid)
693 {
694 	GTree *inset, *a, *b;
695 	GList *sets = NULL;
696 	GString *t;
697 	uint64_t lo = 0, hi = 0;
698 	gboolean error = FALSE;
699 
700 	if (uid)
701 		inset = MailboxState_getIds(M);
702 	else
703 		inset = MailboxState_getMsn(M);
704 
705 	a = g_tree_new_full((GCompareDataFunc)ucmpdata,NULL, (GDestroyNotify)uint64_free, (GDestroyNotify)uint64_free);
706 	b = g_tree_new_full((GCompareDataFunc)ucmpdata,NULL, (GDestroyNotify)uint64_free, (GDestroyNotify)uint64_free);
707 
708 	if (! uid) {
709 		lo = 1;
710 		hi = MailboxState_getExists(M);
711 	} else {
712 		GList *ids = g_tree_keys(inset);
713 		if (ids) {
714 			ids = g_list_last(ids);
715 			hi = *((uint64_t *)ids->data);
716 			ids = g_list_first(ids);
717 			lo = *((uint64_t *)ids->data);
718 			g_list_free(g_list_first(ids));
719 		}
720 	}
721 
722 	t = g_string_new(set);
723 	sets = g_string_split(t, ",");
724 	g_string_free(t,TRUE);
725 
726 	sets = g_list_first(sets);
727 	while(sets) {
728 		uint64_t l = 0, r = 0;
729 
730 		char *rest = (char *)sets->data;
731 
732 		if (strlen(rest) < 1) break;
733 
734 		if (g_tree_nnodes(inset) == 0) { // empty box
735 			if (rest[0] == '*') {
736 				uint64_t *k = mempool_pop(small_pool, sizeof(uint64_t));
737 				uint64_t *v = mempool_pop(small_pool, sizeof(uint64_t));
738 
739 				*k = 1;
740 				*v = MailboxState_getUidnext(M);
741 
742 				g_tree_insert(b, k, v);
743 			} else {
744 				if (! (l = dm_strtoull(sets->data, &rest, 10))) {
745 					error = TRUE;
746 					break;
747 				}
748 				if (rest[0]) {
749 					if (rest[0] != ':') {
750 						error = TRUE;
751 						break;
752 					}
753 					rest++;
754 					if ((rest[0] != '*') && (! dm_strtoull(rest, NULL, 10))) {
755 						error = TRUE;
756 						break;
757 					}
758 				}
759 				uint64_t *k = mempool_pop(small_pool, sizeof(uint64_t));
760 				uint64_t *v = mempool_pop(small_pool, sizeof(uint64_t));
761 
762 				*k = 1;
763 				*v = MailboxState_getUidnext(M);
764 
765 				g_tree_insert(b, k, v);
766 			}
767 		} else {
768 			if (rest[0] == '*') {
769 				l = hi;
770 				r = l;
771 				if (strlen(rest) > 1)
772 					rest++;
773 			} else {
774 				if (! (l = dm_strtoull(sets->data,&rest,10))) {
775 					error = TRUE;
776 					break;
777 				}
778 
779 				if (l == 0xffffffff) l = hi; // outlook
780 
781 				l = max(l,lo);
782 				r = l;
783 			}
784 
785 			if (rest[0]==':') {
786 				if (strlen(rest)>1) rest++;
787 				if (rest[0] == '*') r = hi;
788 				else {
789 					if (! (r = dm_strtoull(rest,NULL,10))) {
790 						error = TRUE;
791 						break;
792 					}
793 
794 					if (r == 0xffffffff) r = hi; // outlook
795 				}
796 
797 				if (!r) break;
798 			}
799 
800 			if (! (l && r)) break;
801 
802 			find_range(inset, min(l,r), max(l,r), a, uid);
803 
804 			if (g_tree_merge(b,a,IST_SUBSEARCH_OR)) {
805 				error = TRUE;
806 				TRACE(TRACE_ERR, "cannot compare null trees");
807 				break;
808 			}
809 		}
810 
811 		if (! g_list_next(sets)) break;
812 		sets = g_list_next(sets);
813 	}
814 
815 	g_list_destroy(sets);
816 
817 	if (a) g_tree_destroy(a);
818 
819 	if (error) {
820 		g_tree_destroy(b);
821 		b = NULL;
822 		TRACE(TRACE_DEBUG, "return NULL");
823 	}
824 
825 	return b;
826 }
827 
_free_recent_queue(gpointer key,gpointer UNUSED value,gpointer data)828 static gboolean _free_recent_queue(gpointer key, gpointer UNUSED value, gpointer data)
829 {
830 	T M = (T)data;
831 	mempool_push(M->pool, key, sizeof(uint64_t));
832 	return FALSE;
833 }
834 
835 
MailboxState_free(T * M)836 void MailboxState_free(T *M)
837 {
838 
839 	T s = *M;
840 	if (s->name)
841 		p_string_free(s->name, TRUE);
842 
843 	g_tree_destroy(s->keywords);
844 	s->keywords = NULL;
845 
846 	if (s->msn) g_tree_destroy(s->msn);
847 	s->msn = NULL;
848 
849 	if (s->ids) g_tree_destroy(s->ids);
850 	s->ids = NULL;
851 
852 	if (s->msginfo) g_tree_destroy(s->msginfo);
853 	s->msginfo = NULL;
854 
855 	if (s->recent_queue) {
856 		g_tree_foreach(s->recent_queue, (GTraverseFunc)_free_recent_queue, s);
857 		g_tree_destroy(s->recent_queue);
858 	}
859 	s->recent_queue = NULL;
860 
861 	gboolean freepool = s->freepool;
862 	Mempool_T pool = s->pool;
863 	mempool_push(pool, s, sizeof(*s));
864 
865 	if (freepool) {
866 		mempool_close(&pool);
867 	}
868 
869 	s = NULL;
870 }
871 
db_getmailbox_permission(T M,Connection_T c)872 void db_getmailbox_permission(T M, Connection_T c)
873 {
874 	ResultSet_T r;
875 	PreparedStatement_T stmt;
876 	g_return_if_fail(M->id);
877 
878 	stmt = db_stmt_prepare(c,
879 			"SELECT permission FROM %smailboxes WHERE mailbox_idnr = ?",
880 			DBPFX);
881 	db_stmt_set_u64(stmt, 1, M->id);
882 	r = db_stmt_query(stmt);
883 
884 	if (db_result_next(r))
885 		M->permission = db_result_get_int(r, 0);
886 }
887 
db_getmailbox_info(T M,Connection_T c)888 static void db_getmailbox_info(T M, Connection_T c)
889 {
890 	/* query mailbox for LIST results */
891 	ResultSet_T r;
892 	char *mbxname, *name, *pattern;
893 	struct mailbox_match *mailbox_like = NULL;
894 	GString *fqname, *qs;
895 	int i=0, prml;
896 	PreparedStatement_T stmt;
897 
898 	stmt = db_stmt_prepare(c,
899 		 "SELECT "
900 		 "CASE WHEN user_id IS NULL THEN 0 ELSE 1 END, " // subscription
901 		 "owner_idnr, name, no_select, no_inferiors "
902 		 "FROM %smailboxes b LEFT OUTER JOIN %ssubscription s ON "
903 		 "b.mailbox_idnr = s.mailbox_id WHERE b.mailbox_idnr = ?",
904 		 DBPFX, DBPFX);
905 	db_stmt_set_u64(stmt, 1, M->id);
906 	r = db_stmt_query(stmt);
907 
908 	if (db_result_next(r)) {
909 
910 		/* subsciption */
911 		M->is_subscribed = db_result_get_bool(r, i++);
912 
913 		/* owner_idnr */
914 		M->owner_id = db_result_get_u64(r, i++);
915 
916 		/* name */
917 		name = g_strdup(db_result_get(r,i++));
918 		if (MATCH(name, "INBOX")) {
919 			M->is_inbox = TRUE;
920 			M->is_subscribed = TRUE;
921 		}
922 
923 		mbxname = mailbox_add_namespace(name, M->owner_id, M->owner_id);
924 		fqname = g_string_new(mbxname);
925 		fqname = g_string_truncate(fqname,IMAP_MAX_MAILBOX_NAMELEN);
926 		MailboxState_setName(M, fqname->str);
927 		g_string_free(fqname,TRUE);
928 		g_free(mbxname);
929 
930 		/* no_select */
931 		M->no_select=db_result_get_bool(r,i++);
932 
933 		/* no_inferior */
934 		M->no_inferiors=db_result_get_bool(r,i++);
935 
936 		/* no_children search pattern*/
937 		pattern = g_strdup_printf("%s/%%", name);
938 		mailbox_like = mailbox_match_new(pattern);
939 		g_free(pattern);
940 		g_free(name);
941 	}
942 
943 	db_con_clear(c);
944 
945 	qs = g_string_new("");
946 	g_string_printf(qs, "SELECT COUNT(*) AS nr_children FROM %smailboxes WHERE owner_idnr = ? ", DBPFX);
947 
948 	if (mailbox_like && mailbox_like->insensitive)
949 		g_string_append_printf(qs, "AND name %s ? ", db_get_sql(SQL_INSENSITIVE_LIKE));
950 	if (mailbox_like && mailbox_like->sensitive)
951 		g_string_append_printf(qs, "AND name %s ? ", db_get_sql(SQL_SENSITIVE_LIKE));
952 
953 	stmt = db_stmt_prepare(c, qs->str);
954 	prml = 1;
955 	db_stmt_set_u64(stmt, prml++, M->owner_id);
956 
957 	if (mailbox_like && mailbox_like->insensitive)
958 		db_stmt_set_str(stmt, prml++, mailbox_like->insensitive);
959 	if (mailbox_like && mailbox_like->sensitive)
960 		db_stmt_set_str(stmt, prml++, mailbox_like->sensitive);
961 
962 	r = db_stmt_query(stmt);
963 	if (db_result_next(r)) {
964 		int nr_children = db_result_get_int(r,0);
965 		M->no_children=nr_children ? 0 : 1;
966 	} else {
967 		M->no_children=1;
968 	}
969 
970 	mailbox_match_free(mailbox_like);
971 	g_string_free(qs, TRUE);
972 }
973 
db_getmailbox_count(T M,Connection_T c)974 static void db_getmailbox_count(T M, Connection_T c)
975 {
976 	ResultSet_T r;
977 	PreparedStatement_T stmt;
978 	unsigned result[3];
979 
980 	result[0] = result[1] = result[2] = 0;
981 
982 	g_return_if_fail(M->id);
983 
984 	/* count messages */
985 	stmt = db_stmt_prepare(c,
986 			"SELECT "
987 			"SUM( CASE WHEN seen_flag = 0 THEN 1 ELSE 0 END) AS unseen, "
988 			"SUM( CASE WHEN seen_flag = 1 THEN 1 ELSE 0 END) AS seen, "
989 			"SUM( CASE WHEN recent_flag = 1 THEN 1 ELSE 0 END) AS recent "
990 			"FROM %smessages WHERE mailbox_idnr=? AND status < %d ",
991 			DBPFX, MESSAGE_STATUS_DELETE);
992 
993 	db_stmt_set_u64(stmt, 1, M->id);
994 
995 	r = db_stmt_query(stmt);
996 
997 	if (db_result_next(r)) {
998 		result[0] = (unsigned)db_result_get_int(r,0); // unseen
999 		result[1] = (unsigned)db_result_get_int(r,1); // seen
1000 		result[2] = (unsigned)db_result_get_int(r,2); // recent
1001 	}
1002 
1003 	M->exists = result[0] + result[1];
1004 	M->unseen = result[0];
1005 	M->recent = result[2];
1006 
1007 	TRACE(TRACE_DEBUG, "exists [%d] unseen [%d] recent [%d]", M->exists, M->unseen, M->recent);
1008 	/* now determine the next message UID
1009 	 * NOTE:
1010 	 * - expunged messages are selected as well in order to be able to restore them
1011 	 * - the next uit MUST NOT change unless messages are added to THIS mailbox
1012 	 * */
1013 
1014 	if (M->exists == 0) {
1015 		M->uidnext = 1;
1016 		return;
1017 	}
1018 
1019 	db_con_clear(c);
1020 	stmt = db_stmt_prepare(c,
1021 			"SELECT MAX(message_idnr)+1 FROM %smessages WHERE mailbox_idnr=?",
1022 			DBPFX);
1023 	db_stmt_set_u64(stmt, 1, M->id);
1024 	r = db_stmt_query(stmt);
1025 
1026 	if (db_result_next(r))
1027 		M->uidnext = db_result_get_u64(r,0);
1028 	else
1029 		M->uidnext = 1;
1030 }
1031 
db_getmailbox_keywords(T M,Connection_T c)1032 static void db_getmailbox_keywords(T M, Connection_T c)
1033 {
1034 	ResultSet_T r;
1035 	PreparedStatement_T stmt;
1036 
1037 	stmt = db_stmt_prepare(c,
1038 			"SELECT DISTINCT(keyword) FROM %skeywords k "
1039 			"LEFT JOIN %smessages m ON k.message_idnr=m.message_idnr "
1040 			"WHERE m.mailbox_idnr=? AND m.status < %d ",
1041 			DBPFX, DBPFX, MESSAGE_STATUS_DELETE);
1042 	db_stmt_set_u64(stmt, 1, M->id);
1043 	r = db_stmt_query(stmt);
1044 
1045 	while (db_result_next(r))
1046 		MailboxState_addKeyword(M, db_result_get(r, 0));
1047 }
1048 
db_getmailbox_seq(T M,Connection_T c)1049 void db_getmailbox_seq(T M, Connection_T c)
1050 {
1051 	ResultSet_T r;
1052 	PreparedStatement_T stmt;
1053 
1054 	stmt = db_stmt_prepare(c,
1055 		       	"SELECT name,seq FROM %smailboxes WHERE mailbox_idnr=?",
1056 		       	DBPFX);
1057 	db_stmt_set_u64(stmt, 1, M->id);
1058 	r = db_stmt_query(stmt);
1059 
1060 	if (db_result_next(r)) {
1061 		if (! M->name)
1062 			M->name = p_string_new(M->pool, db_result_get(r, 0));
1063 		M->seq = db_result_get_u64(r,1);
1064 		TRACE(TRACE_DEBUG,"id: [%" PRIu64 "] name: [%s] seq [%" PRIu64 "]", M->id, p_string_str(M->name), M->seq);
1065 	} else {
1066 		TRACE(TRACE_ERR,"Aii. No such mailbox mailbox_idnr: [%" PRIu64 "]", M->id);
1067 	}
1068 }
1069 
MailboxState_info(T M)1070 int MailboxState_info(T M)
1071 {
1072 	volatile int t = DM_SUCCESS;
1073 	Connection_T c = db_con_get();
1074 	TRY
1075 		db_begin_transaction(c);
1076 		db_getmailbox_info(M, c);
1077 		db_commit_transaction(c);
1078 	CATCH(SQLException)
1079 		LOG_SQLERROR;
1080 		db_rollback_transaction(c);
1081 		t = DM_EQUERY;
1082 	FINALLY
1083 		db_con_close(c);
1084 	END_TRY;
1085 
1086 	return t;
1087 }
1088 
state_load_metadata(T M,Connection_T c)1089 static void state_load_metadata(T M, Connection_T c)
1090 {
1091 	uint64_t oldseq;
1092 
1093 	g_return_if_fail(M->id);
1094 
1095 	oldseq = M->seq;
1096 	db_getmailbox_seq(M, c);
1097 	if (M->uidnext && (M->seq == oldseq))
1098 		return;
1099 
1100 	db_getmailbox_permission(M, c);
1101 	db_getmailbox_count(M, c);
1102 	db_getmailbox_keywords(M, c);
1103 	db_getmailbox_info(M, c);
1104 
1105 	TRACE(TRACE_DEBUG, "[%" PRIu64 "] exists [%d] recent [%d]",
1106 			M->id, M->exists, M->recent);
1107 }
1108 
MailboxState_count(T M)1109 int MailboxState_count(T M)
1110 {
1111 	Connection_T c;
1112 	volatile int t = DM_SUCCESS;
1113 
1114 	c = db_con_get();
1115 	TRY
1116 		db_begin_transaction(c);
1117 		db_getmailbox_count(M, c);
1118 		db_commit_transaction(c);
1119 	CATCH(SQLException)
1120 		LOG_SQLERROR;
1121 		db_rollback_transaction(c);
1122 		t = DM_EQUERY;
1123 	FINALLY
1124 		db_con_close(c);
1125 	END_TRY;
1126 
1127 	return t;
1128 }
1129 
MailboxState_flags(T M)1130 char * MailboxState_flags(T M)
1131 {
1132 	char *s = NULL;
1133 	GString *string = g_string_new("\\Seen \\Answered \\Deleted \\Flagged \\Draft");
1134 	assert(M);
1135 
1136 	if (M->keywords) {
1137 		GList *k = g_tree_keys(M->keywords);
1138 		GString *keywords = g_list_join(k," ");
1139 		g_string_append_printf(string, " %s", keywords->str);
1140 		g_string_free(keywords,TRUE);
1141 		g_list_free(g_list_first(k));
1142 	}
1143 
1144 	s = string->str;
1145 	g_string_free(string, FALSE);
1146 	return g_strchomp(s);
1147 }
1148 
MailboxState_hasPermission(T M,uint64_t userid,const char * right_flag)1149 int MailboxState_hasPermission(T M, uint64_t userid, const char *right_flag)
1150 {
1151 	PreparedStatement_T stmt;
1152 	Connection_T c;
1153        	ResultSet_T r;
1154 	volatile int result = FALSE;
1155 	volatile gboolean owner_acl = false;
1156 	uint64_t owner_id, mboxid;
1157 
1158 	mboxid = MailboxState_getId(M);
1159 
1160 	TRACE(TRACE_DEBUG, "checking ACL [%s] for user [%" PRIu64 "] on mailbox [%" PRIu64 "]",
1161 			right_flag, userid, mboxid);
1162 
1163 	/* If we don't know who owns the mailbox, look it up. */
1164 	owner_id = MailboxState_getOwner(M);
1165 	if (! owner_id) {
1166 		result = db_get_mailbox_owner(mboxid, &owner_id);
1167 		MailboxState_setOwner(M, owner_id);
1168 		if (! (result > 0))
1169 			return result;
1170 	}
1171 
1172 	if (owner_id == userid) {
1173 		c = db_con_get();
1174 		TRY
1175 			stmt = db_stmt_prepare(c,
1176 					"SELECT * FROM %sacl WHERE "
1177 					"user_id = ? AND mailbox_id = ?",
1178 					DBPFX);
1179 			db_stmt_set_u64(stmt, 1, userid);
1180 			db_stmt_set_u64(stmt, 2, mboxid);
1181 			r = db_stmt_query(stmt);
1182 
1183 			if (db_result_next(r))
1184 				owner_acl = true;
1185 		CATCH(SQLException)
1186 			LOG_SQLERROR;
1187 			result = DM_EQUERY;
1188 		FINALLY
1189 			db_con_close(c);
1190 		END_TRY;
1191 
1192 		if (! owner_acl) {
1193 			TRACE(TRACE_DEBUG, "mailbox [%" PRIu64 "] is owned by user [%" PRIu64 "]"
1194 					"and no ACL in place. Giving all rights",
1195 					mboxid, userid);
1196 			return 1;
1197 		} else {
1198 			TRACE(TRACE_DEBUG, "mailbox [%" PRIu64 "] is owned by user [%" PRIu64 "]"
1199 					"but ACL in place. Restricted access for owner.",
1200 					mboxid, userid);
1201 		}
1202 
1203 	}
1204 
1205 	result = FALSE;
1206 	c = db_con_get();
1207 	TRY
1208 		stmt = db_stmt_prepare(c,
1209 			       	"SELECT * FROM %sacl WHERE "
1210 				"user_id = ? AND mailbox_id = ? AND %s = 1",
1211 				DBPFX, right_flag);
1212 		db_stmt_set_u64(stmt, 1, userid);
1213 		db_stmt_set_u64(stmt, 2, mboxid);
1214 		r = db_stmt_query(stmt);
1215 
1216 		if (db_result_next(r))
1217 			result = TRUE;
1218 	CATCH(SQLException)
1219 		LOG_SQLERROR;
1220 		result = DM_EQUERY;
1221 	FINALLY
1222 		db_con_close(c);
1223 	END_TRY;
1224 
1225 	return result;
1226 }
1227 
MailboxState_getAcl(T M,uint64_t userid,struct ACLMap * map)1228 int MailboxState_getAcl(T M, uint64_t userid, struct ACLMap *map)
1229 {
1230 	int i;
1231 	volatile int t = DM_SUCCESS;
1232 	gboolean gotrow = FALSE;
1233 	uint64_t anyone;
1234 	Connection_T c; ResultSet_T r; PreparedStatement_T s;
1235 
1236 	g_return_val_if_fail(MailboxState_getId(M),DM_EGENERAL);
1237 
1238 	if (! (auth_user_exists(DBMAIL_ACL_ANYONE_USER, &anyone)))
1239 		return DM_EQUERY;
1240 
1241 	c = db_con_get();
1242 	TRY
1243 		s = db_stmt_prepare(c, "SELECT lookup_flag,read_flag,seen_flag,"
1244 			"write_flag,insert_flag,post_flag,"
1245 			"create_flag,delete_flag,deleted_flag,expunge_flag,administer_flag "
1246 			"FROM %sacl "
1247 			"WHERE mailbox_id = ? AND user_id = ?",DBPFX);
1248 		db_stmt_set_u64(s, 1, MailboxState_getId(M));
1249 		db_stmt_set_u64(s, 2, userid);
1250 		r = db_stmt_query(s);
1251 		if (! db_result_next(r)) {
1252 			/* else check the 'anyone' user */
1253 			db_stmt_set_u64(s, 2, anyone);
1254 			r = db_stmt_query(s);
1255 			if (db_result_next(r))
1256 				gotrow = TRUE;
1257 		} else {
1258 			gotrow = TRUE;
1259 		}
1260 
1261 		if (gotrow) {
1262 			i = 0;
1263 			map->lookup_flag	= db_result_get_bool(r,i++);
1264 			map->read_flag		= db_result_get_bool(r,i++);
1265 			map->seen_flag		= db_result_get_bool(r,i++);
1266 			map->write_flag		= db_result_get_bool(r,i++);
1267 			map->insert_flag	= db_result_get_bool(r,i++);
1268 			map->post_flag		= db_result_get_bool(r,i++);
1269 			map->create_flag	= db_result_get_bool(r,i++);
1270 			map->delete_flag	= db_result_get_bool(r,i++);
1271 			map->deleted_flag	= db_result_get_bool(r,i++);
1272 			map->expunge_flag	= db_result_get_bool(r,i++);
1273 			map->administer_flag	= db_result_get_bool(r,i++);
1274 		}
1275 	CATCH(SQLException)
1276 		LOG_SQLERROR;
1277 		t = DM_EQUERY;
1278 	FINALLY
1279 		db_con_close(c);
1280 	END_TRY;
1281 
1282 	return t;
1283 }
1284 
1285 
mailbox_build_recent(uint64_t * uid,MessageInfo * msginfo,T M)1286 static gboolean mailbox_build_recent(uint64_t *uid, MessageInfo *msginfo, T M)
1287 {
1288 	if (msginfo->flags[IMAP_FLAG_RECENT]) {
1289 		uint64_t *copy = mempool_pop(M->pool, sizeof(uint64_t));
1290 		*copy = *uid;
1291 		g_tree_insert(M->recent_queue, copy, copy);
1292 	}
1293 	return FALSE;
1294 }
1295 
MailboxState_build_recent(T M)1296 int MailboxState_build_recent(T M)
1297 {
1298         if (MailboxState_getPermission(M) == IMAPPERM_READWRITE && MailboxState_getMsginfo(M)) {
1299 		GTree *info = MailboxState_getMsginfo(M);
1300 		g_tree_foreach(info, (GTraverseFunc)mailbox_build_recent, M);
1301 		TRACE(TRACE_DEBUG, "build list of [%d] [%d] recent messages...",
1302 				g_tree_nnodes(info), g_tree_nnodes(M->recent_queue));
1303 	}
1304 	return 0;
1305 }
1306 
_update_recent(volatile GList * slices,uint64_t seq)1307 static long long int _update_recent(volatile GList *slices, uint64_t seq)
1308 {
1309 	INIT_QUERY;
1310 	Connection_T c;
1311 	volatile long long int count = 0;
1312 
1313 	if (! (slices = g_list_first((GList*)slices)))
1314 		return count;
1315 
1316 	c = db_con_get();
1317 	TRY
1318 		db_begin_transaction(c);
1319 		while (slices) {
1320 			Connection_execute(c, "UPDATE %smessages SET recent_flag = 0, seq = %" PRIu64
1321 					" WHERE recent_flag = 1 AND seq < %" PRIu64
1322 					" AND message_idnr IN (%s)",
1323 					DBPFX, seq, seq, (gchar *)slices->data);
1324 			count += Connection_rowsChanged(c);
1325 			if (! g_list_next(slices)) break;
1326 			slices = g_list_next(slices);
1327 		}
1328 		db_commit_transaction(c);
1329 	CATCH(SQLException)
1330 		LOG_SQLERROR;
1331 		count = DM_EQUERY;
1332 		db_rollback_transaction(c);
1333 	FINALLY
1334 		db_con_close(c);
1335 		g_list_destroy((GList*)slices);
1336 	END_TRY;
1337 
1338 	return count;
1339 }
1340 
MailboxState_flush_recent(T M)1341 int MailboxState_flush_recent(T M)
1342 {
1343 	GList *recent;
1344 
1345 	if ((!M) || (M && MailboxState_getPermission(M) != IMAPPERM_READWRITE))
1346 		return DM_SUCCESS;
1347 
1348 	if (! g_tree_nnodes(M->recent_queue))
1349 		return DM_SUCCESS;
1350 
1351 	TRACE(TRACE_DEBUG,"flush [%d] recent messages", g_tree_nnodes(M->recent_queue));
1352 
1353 	recent = g_tree_keys(M->recent_queue);
1354 
1355 	if (recent) {
1356 		long long int changed = 0;
1357 		uint64_t seq = MailboxState_getSeq(M);
1358 		changed = _update_recent(g_list_slices_u64(recent,100), seq+1);
1359 		if (changed)
1360 			db_mailbox_seq_update(MailboxState_getId(M), 0);
1361 	}
1362 
1363 	g_list_free(g_list_first(recent));
1364 
1365 	g_tree_foreach(M->recent_queue, (GTraverseFunc)_free_recent_queue, M);
1366 	g_tree_destroy(M->recent_queue);
1367 	M->recent_queue = g_tree_new((GCompareFunc)ucmp);
1368 
1369 	return 0;
1370 }
1371 
mailbox_clear_recent(uint64_t * uid,MessageInfo * msginfo,T M)1372 static gboolean mailbox_clear_recent(uint64_t *uid, MessageInfo *msginfo, T M)
1373 {
1374 	msginfo->flags[IMAP_FLAG_RECENT] = 0;
1375 	gpointer value;
1376 	gpointer orig_key;
1377 	if (g_tree_lookup_extended(M->recent_queue, uid, &orig_key, &value)) {
1378 		g_tree_remove(M->recent_queue, orig_key);
1379 		mempool_push(M->pool, orig_key, sizeof(uint64_t));
1380 	}
1381 	return FALSE;
1382 }
1383 
MailboxState_clear_recent(T M)1384 int MailboxState_clear_recent(T M)
1385 {
1386         if (MailboxState_getPermission(M) == IMAPPERM_READWRITE && MailboxState_getMsginfo(M)) {
1387 		GTree *info = MailboxState_getMsginfo(M);
1388 		g_tree_foreach(info, (GTraverseFunc)mailbox_clear_recent, M);
1389 	}
1390 
1391 	return 0;
1392 }
1393 
MailboxState_message_flags(T M,MessageInfo * msginfo)1394 GList * MailboxState_message_flags(T M, MessageInfo *msginfo)
1395 {
1396 	GList *t, *sublist = NULL;
1397 	int j;
1398 	uint64_t uid = msginfo->uid;
1399 
1400 	for (j = 0; j < IMAP_NFLAGS; j++) {
1401 		if (msginfo->flags[j])
1402 			sublist = g_list_append(sublist,g_strdup((gchar *)imap_flag_desc_escaped[j]));
1403 	}
1404 	if ((msginfo->flags[IMAP_FLAG_RECENT] == 0) && g_tree_lookup(M->recent_queue, &uid)) {
1405 		TRACE(TRACE_DEBUG,"set \\recent flag");
1406 		sublist = g_list_append(sublist, g_strdup((gchar *)imap_flag_desc_escaped[IMAP_FLAG_RECENT]));
1407 	}
1408 
1409 	t = g_list_first(msginfo->keywords);
1410 	while (t) {
1411 		if (MailboxState_hasKeyword(M, t->data))
1412 			sublist = g_list_append(sublist, g_strdup((gchar *)t->data));
1413 		if (! g_list_next(t)) break;
1414 		t = g_list_next(t);
1415 	}
1416 
1417 	return sublist;
1418 }
1419 
MailboxState_merge_recent(T M,T N)1420 int MailboxState_merge_recent(T M, T N)
1421 {
1422 	GTree *recent_queue = N->recent_queue;
1423 	N->recent_queue = NULL;
1424 	g_tree_merge(M->recent_queue, recent_queue, IST_SUBSEARCH_OR);
1425 	g_tree_foreach(recent_queue, (GTraverseFunc)_free_recent_queue, M);
1426 	g_tree_destroy(recent_queue);
1427 	M->recent = g_tree_nnodes(M->recent_queue);
1428 	return 0;
1429 }
1430