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 /*
22  *
23  * IMAP-server utility functions implementations
24  */
25 
26 #include "dbmail.h"
27 #include "dm_mempool.h"
28 
29 #define THIS_MODULE "imap"
30 #define BUFLEN 2048
31 #define SEND_BUF_SIZE 8192
32 #define MAX_ARGS 512
33 #define IDLE_TIMEOUT 30
34 
35 
36 
37 extern DBParam_T db_params;
38 #define DBPFX db_params.pfx
39 
40 extern Mempool_T queue_pool;
41 extern Mempool_T small_pool;
42 
43 gboolean imap_feature_idle_status = FALSE;
44 
45 extern const char *month_desc[];
46 extern const int month_len[];
47 extern const char *imap_flag_desc[];
48 extern const char *imap_flag_desc_escaped[];
49 extern volatile sig_atomic_t alarm_occured;
50 
51 extern GAsyncQueue *queue;
52 extern ServerConfig_T *server_conf;
53 
54 /*
55  * send_data()
56  *
57  */
send_data(ImapSession * self,const String_T stream,size_t offset,size_t len)58 static void send_data(ImapSession *self, const String_T stream, size_t offset, size_t len)
59 {
60 	char buf[SEND_BUF_SIZE];
61 	size_t l = 0;
62 	char *head;
63 
64 	assert(stream);
65 	if (p_string_len(stream) < (offset+len))
66 		return;
67 
68 	head = (char *)p_string_str(stream)+offset;
69 
70 	TRACE(TRACE_DEBUG,"[%p] stream [%p] offset [%ld] len [%ld]", self, stream, offset, len);
71 	while (len > 0) {
72 		l = min(len, sizeof(buf)-1);
73 		memset(buf,0,sizeof(buf));
74 		strncpy(buf, head, l);
75 		dbmail_imap_session_buff_printf(self, "%s", buf);
76 		head += l;
77 		len -= l;
78 	}
79 }
80 
mailboxstate_destroy(MailboxState_T M)81 static void mailboxstate_destroy(MailboxState_T M)
82 {
83 	MailboxState_free(&M);
84 }
85 
86 
87 /*
88  * initializer and accessors for ImapSession
89  */
dbmail_imap_session_new(Mempool_T pool)90 ImapSession * dbmail_imap_session_new(Mempool_T pool)
91 {
92 	ImapSession * self;
93 	Field_T val;
94 	gboolean login_disabled = TRUE;
95 
96 	self = mempool_pop(pool, sizeof(ImapSession));
97 
98 	if (! queue_pool)
99 		self->buff = p_string_new(pool, "");
100 	else
101 		self->buff = p_string_new(queue_pool, "");
102 
103 	self->pool = pool;
104 
105 	pthread_mutex_init(&self->lock, NULL);
106 
107 	GETCONFIGVALUE("login_disabled", "IMAP", val);
108 	if (SMATCH(val, "no"))
109 		login_disabled = FALSE;
110 
111 	self->state = CLIENTSTATE_NON_AUTHENTICATED;
112 	self->args = mempool_pop(self->pool, sizeof(String_T) * MAX_ARGS);
113 	self->fi = mempool_pop(self->pool, sizeof(fetch_items));
114 	self->capa = Capa_new(self->pool);
115 	self->preauth_capa = Capa_new(self->pool);
116 	Capa_remove(self->preauth_capa, "ACL");
117 	Capa_remove(self->preauth_capa, "RIGHTS=texk");
118 	Capa_remove(self->preauth_capa, "NAMESPACE");
119 	Capa_remove(self->preauth_capa, "CHILDREN");
120 	Capa_remove(self->preauth_capa, "SORT");
121 	Capa_remove(self->preauth_capa, "QUOTA");
122 	Capa_remove(self->preauth_capa, "THREAD=ORDEREDSUBJECT");
123 	Capa_remove(self->preauth_capa, "UNSELECT");
124 	Capa_remove(self->preauth_capa, "IDLE");
125 	Capa_remove(self->preauth_capa, "UIDPLUS");
126 	Capa_remove(self->preauth_capa, "WITHIN");
127 	Capa_remove(self->preauth_capa, "CONDSTORE");
128 	Capa_remove(self->preauth_capa, "ENABLE");
129 	Capa_remove(self->preauth_capa, "QRESYNC");
130 
131 	if (! (server_conf && server_conf->ssl))
132 		Capa_remove(self->preauth_capa, "STARTTLS");
133 
134 	if (! Capa_match(self->preauth_capa, "STARTTLS"))
135 		login_disabled = FALSE;
136 
137 	if (login_disabled) {
138 		if (! Capa_match(self->preauth_capa, "LOGINDISABLED"))
139 			Capa_add(self->preauth_capa, "LOGINDISABLED");
140 		Capa_remove(self->preauth_capa, "AUTH=LOGIN");
141 		Capa_remove(self->preauth_capa, "AUTH=CRAM-MD5");
142 	} else {
143 		Capa_remove(self->preauth_capa, "LOGINDISABLED");
144 	}
145 	if (MATCH(db_params.authdriver, "LDAP")) {
146 		Capa_remove(self->capa, "AUTH=CRAM-MD5");
147 		Capa_remove(self->preauth_capa, "AUTH=CRAM-MD5");
148 	}
149 
150 	if (! (server_conf && server_conf->ssl)) {
151 		Capa_remove(self->capa, "STARTTLS");
152 		Capa_remove(self->preauth_capa, "STARTTLS");
153 	}
154 
155 	self->physids = g_tree_new((GCompareFunc)ucmp);
156 	self->mbxinfo = g_tree_new_full((GCompareDataFunc)ucmpdata,NULL,(GDestroyNotify)uint64_free,(GDestroyNotify)mailboxstate_destroy);
157 
158 	TRACE(TRACE_DEBUG,"imap session [%p] created", self);
159 	return self;
160 }
161 
dbmail_imap_session_message_load(ImapSession * self)162 static uint64_t dbmail_imap_session_message_load(ImapSession *self)
163 {
164 	TRACE(TRACE_DEBUG, "Call: dbmail_imap_session_message_load");
165 	uint64_t *id = NULL;
166 
167 
168 	if (! (id = g_tree_lookup(self->physids, &(self->msg_idnr)))) {
169 		uint64_t *uid;
170 
171 		id = mempool_pop(self->pool, sizeof(uint64_t));
172 
173 		if (self->mailbox->mbstate != NULL){
174 			/* the state is ready, get the phys id from state, avoid one query in database */
175 			MessageInfo *msginfo = g_tree_lookup(MailboxState_getMsginfo(self->mailbox->mbstate), &self->msg_idnr);
176 			*id=msginfo->phys_id;
177 		}else{
178 			/* previous behavior, a query will be performed in db */
179 			if ((db_get_physmessage_id(self->msg_idnr, id)) != DM_SUCCESS) {
180 				TRACE(TRACE_ERR,"can't find physmessage_id for message_idnr [%" PRIu64 "]", self->msg_idnr);
181 				g_free(id);
182 				return 0;
183 			}
184 		}
185 		uid = mempool_pop(self->pool, sizeof(uint64_t));
186 		*uid = self->msg_idnr;
187 		g_tree_insert(self->physids, uid, id);
188 
189 	}
190 
191 	if (self->message) {
192 		if (*id != self->message->id) {
193 			dbmail_message_free(self->message);
194 			self->message = NULL;
195 		}
196 	}
197 
198 	assert(id);
199 
200 	if (! self->message) {
201 		DbmailMessage *msg = dbmail_message_new(self->pool);
202 		if ((msg = dbmail_message_retrieve(msg, *id)) != NULL)
203 			self->message = msg;
204 	}
205 
206 	if (! self->message) {
207 		TRACE(TRACE_ERR,"message retrieval failed");
208 		return 0;
209 	}
210 
211 	assert(*id == self->message->id);
212 	return 1;
213 }
214 
dbmail_imap_session_set_command(ImapSession * self,const char * command)215 ImapSession * dbmail_imap_session_set_command(ImapSession * self, const char * command)
216 {
217 	g_strlcpy(self->command, command, sizeof(self->command));
218 	return self;
219 }
220 
_physids_free(gpointer key,gpointer value,gpointer data)221 static gboolean _physids_free(gpointer key, gpointer value, gpointer data)
222 {
223 	ImapSession *self = (ImapSession *)data;
224 	mempool_push(self->pool, key, sizeof(uint64_t));
225 	mempool_push(self->pool, value, sizeof(uint64_t));
226 	return FALSE;
227 }
228 
dbmail_imap_session_delete(ImapSession ** s)229 void dbmail_imap_session_delete(ImapSession ** s)
230 {
231 	ImapSession *self = *s;
232 	Mempool_T pool;
233 
234 	TRACE(TRACE_DEBUG, "[%p]", self);
235 	Capa_free(&self->preauth_capa);
236 	Capa_free(&self->capa);
237 
238 	dbmail_imap_session_args_free(self, TRUE);
239 	dbmail_imap_session_fetch_free(self, TRUE);
240 
241 	if (self->mailbox) {
242 		dbmail_mailbox_free(self->mailbox);
243 		self->mailbox = NULL;
244 	}
245 	if (self->mbxinfo) {
246 		g_tree_destroy(self->mbxinfo);
247 		self->mbxinfo = NULL;
248 	}
249 	if (self->message) {
250 		dbmail_message_free(self->message);
251 		self->message = NULL;
252 	}
253 	if (self->physids) {
254 		g_tree_foreach(self->physids, (GTraverseFunc)_physids_free, (gpointer)self);
255 		g_tree_destroy(self->physids);
256 		self->physids = NULL;
257 	}
258 	if (self->buff) {
259 		p_string_free(self->buff, TRUE);
260 		self->buff = NULL;
261 	}
262 
263 	pthread_mutex_destroy(&self->lock);
264 	pool = self->pool;
265 	mempool_push(pool, self, sizeof(ImapSession));
266 	mempool_close(&pool);
267 	self = NULL;
268 }
269 
dbmail_imap_session_fetch_free(ImapSession * self,gboolean all)270 void dbmail_imap_session_fetch_free(ImapSession *self, gboolean all)
271 {
272 	if (self->envelopes) {
273 		g_tree_destroy(self->envelopes);
274 		self->envelopes = NULL;
275 	}
276 	if (self->ids) {
277 		g_tree_destroy(self->ids);
278 		self->ids = NULL;
279 	}
280 	if (self->ids_list) {
281 		g_list_free(g_list_first(self->ids_list));
282 		self->ids_list = NULL;
283 	}
284 	if (self->fi->bodyfetch) {
285 		dbmail_imap_session_bodyfetch_free(self);
286 		self->fi->bodyfetch = NULL;
287 	}
288 	if (all) {
289 		mempool_push(self->pool, self->fi, sizeof(fetch_items));
290 		self->fi = NULL;
291 	} else {
292 		memset(self->fi, 0, sizeof(fetch_items));
293 	}
294 }
295 
dbmail_imap_session_args_free(ImapSession * self,gboolean all)296 void dbmail_imap_session_args_free(ImapSession *self, gboolean all)
297 {
298 	int i;
299 	for (i = 0; i < MAX_ARGS && self->args[i]; i++) {
300 		if (self->args[i])
301 		       	p_string_free(self->args[i], TRUE);
302 		self->args[i] = NULL;
303 	}
304 	self->args_idx = 0;
305 
306 	if (all) {
307 		mempool_push(self->pool, self->args, sizeof(String_T) * MAX_ARGS);
308 		self->args = NULL;
309 	}
310 }
311 
312 /*************************************************************************************
313  *
314  *
315  * imap utilities using ImapSession
316  *
317  *
318  ************************************************************************************/
dbmail_imap_session_bodyfetch_set_partspec(ImapSession * self,char * partspec,int length)319 static int dbmail_imap_session_bodyfetch_set_partspec(ImapSession *self, char *partspec, int length)
320 {
321 	assert(self->fi);
322 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
323 	memset(bodyfetch->partspec,'\0',IMAP_MAX_PARTSPEC_LEN);
324 	memcpy(bodyfetch->partspec,partspec,length);
325 	return 0;
326 }
dbmail_imap_session_bodyfetch_set_itemtype(ImapSession * self,int itemtype)327 int dbmail_imap_session_bodyfetch_set_itemtype(ImapSession *self, int itemtype)
328 {
329 	assert(self->fi);
330 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
331 	bodyfetch->itemtype = itemtype;
332 	return 0;
333 }
dbmail_imap_session_bodyfetch_set_argstart(ImapSession * self)334 int dbmail_imap_session_bodyfetch_set_argstart(ImapSession *self)
335 {
336 	assert(self->fi);
337 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
338 	bodyfetch->argstart = self->args_idx;
339 	return bodyfetch->argstart;
340 }
dbmail_imap_session_bodyfetch_set_argcnt(ImapSession * self)341 int dbmail_imap_session_bodyfetch_set_argcnt(ImapSession *self)
342 {
343 	assert(self->fi);
344 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
345 	bodyfetch->argcnt = self->args_idx - bodyfetch->argstart;
346 	return bodyfetch->argcnt;
347 }
dbmail_imap_session_bodyfetch_get_last_argcnt(ImapSession * self)348 int dbmail_imap_session_bodyfetch_get_last_argcnt(ImapSession *self)
349 {
350 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
351 	return bodyfetch->argcnt;
352 }
dbmail_imap_session_bodyfetch_set_octetstart(ImapSession * self,guint64 octet)353 int dbmail_imap_session_bodyfetch_set_octetstart(ImapSession *self, guint64 octet)
354 {
355 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
356 	bodyfetch->octetstart = octet;
357 	return 0;
358 }
dbmail_imap_session_bodyfetch_get_last_octetstart(ImapSession * self)359 guint64 dbmail_imap_session_bodyfetch_get_last_octetstart(ImapSession *self)
360 {
361 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
362 	return bodyfetch->octetstart;
363 }
dbmail_imap_session_bodyfetch_set_octetcnt(ImapSession * self,guint64 octet)364 int dbmail_imap_session_bodyfetch_set_octetcnt(ImapSession *self, guint64 octet)
365 {
366 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
367 	bodyfetch->octetcnt = octet;
368 	return 0;
369 }
dbmail_imap_session_bodyfetch_get_last_octetcnt(ImapSession * self)370 guint64 dbmail_imap_session_bodyfetch_get_last_octetcnt(ImapSession *self)
371 {
372 	body_fetch *bodyfetch = p_list_data(self->fi->bodyfetch);
373 	return bodyfetch->octetcnt;
374 }
375 
376 
_imap_session_fetch_parse_partspec(ImapSession * self)377 static int _imap_session_fetch_parse_partspec(ImapSession *self)
378 {
379 	/* check for a partspecifier */
380 	/* first check if there is a partspecifier (numbers & dots) */
381 	int indigit = 0;
382 	unsigned int j = 0;
383 	const char *token, *nexttoken;
384 
385 	token = p_string_str(self->args[self->args_idx]);
386 	nexttoken = p_string_str(self->args[self->args_idx+1]);
387 
388 	TRACE(TRACE_DEBUG,"token [%s], nexttoken [%s]", token, nexttoken);
389 
390 	for (j = 0; token[j]; j++) {
391 		if (isdigit(token[j])) {
392 			indigit = 1;
393 			continue;
394 		} else if (token[j] == '.') {
395 			if (!indigit) return -2; /* error, single dot specified */
396 			indigit = 0;
397 			continue;
398 		} else
399 			break;	/* other char found */
400 	}
401 
402 	if (j > 0) {
403 		if (indigit && token[j]) return -2;		/* error DONE */
404 		/* partspecifier present, save it */
405 		if (j >= IMAP_MAX_PARTSPEC_LEN) return -2;	/* error DONE */
406 		dbmail_imap_session_bodyfetch_set_partspec(self, (char *)token, j);
407 	}
408 
409 	const char *partspec = &token[j];
410 	int shouldclose = 0;
411 
412 	if (MATCH(partspec, "header.fields")) {
413 		dbmail_imap_session_bodyfetch_set_itemtype(self, BFIT_HEADER_FIELDS);
414 	} else if (MATCH(partspec, "header.fields.not")) {
415 		dbmail_imap_session_bodyfetch_set_itemtype(self, BFIT_HEADER_FIELDS_NOT);
416 	} else if (MATCH(partspec, "text")) {
417 		self->fi->msgparse_needed=1;
418 		dbmail_imap_session_bodyfetch_set_itemtype(self, BFIT_TEXT);
419 		shouldclose = 1;
420 	} else if (MATCH(partspec, "header")) {
421 		self->fi->msgparse_needed=1;
422 		dbmail_imap_session_bodyfetch_set_itemtype(self, BFIT_HEADER);
423 		shouldclose = 1;
424 	} else if (MATCH(partspec, "mime")) {
425 		self->fi->msgparse_needed=1;
426 		if (j == 0) return -2;				/* error DONE */
427 		dbmail_imap_session_bodyfetch_set_itemtype(self, BFIT_MIME);
428 		shouldclose = 1;
429 	} else if (token[j] == '\0') {
430 		self->fi->msgparse_needed=1;
431 		dbmail_imap_session_bodyfetch_set_itemtype(self, BFIT_ALL);
432 		shouldclose = 1;
433 	} else {
434 		return -2;					/* error DONE */
435 	}
436 	if (shouldclose) {
437 		if (! MATCH(nexttoken, "]")) return -2;		/* error DONE */
438 	} else {
439 		self->args_idx++;	/* should be at '(' now */
440 		token = p_string_str(self->args[self->args_idx]);
441 		nexttoken = p_string_str(self->args[self->args_idx+1]);
442 
443 		if (! MATCH(token,"(")) return -2;		/* error DONE */
444 
445 		self->args_idx++;	/* at first item of field list now, remember idx */
446 		dbmail_imap_session_bodyfetch_set_argstart(self);
447 		/* walk on untill list terminates (and it does 'cause parentheses are matched) */
448 		while (! MATCH(p_string_str(self->args[self->args_idx]),")") )
449 			self->args_idx++;
450 
451 		token = p_string_str(self->args[self->args_idx]);
452 		nexttoken = p_string_str(self->args[self->args_idx+1]);
453 
454 		TRACE(TRACE_DEBUG,"token [%s], nexttoken [%s]", token, nexttoken);
455 
456 		dbmail_imap_session_bodyfetch_set_argcnt(self);
457 
458 		if (dbmail_imap_session_bodyfetch_get_last_argcnt(self) == 0 || ! MATCH(nexttoken,"]") )
459 			return -2;				/* error DONE */
460 	}
461 
462 	return 0;
463 }
464 
465 #define RANGESIZE 128
466 
_imap_session_fetch_parse_octet_range(ImapSession * self)467 static int _imap_session_fetch_parse_octet_range(ImapSession *self)
468 {
469 	/* check if octet start/cnt is specified */
470 	int delimpos;
471 	unsigned int j = 0;
472 	char token[RANGESIZE];
473 
474 	if (! self->args[self->args_idx])
475 		return 0;
476 
477 	memset(token, 0, sizeof(token));
478 	g_strlcpy(token, p_string_str(self->args[self->args_idx]), sizeof(token));
479 
480 	if (token[0] == '\0')
481 		return 0;
482 
483 	TRACE(TRACE_DEBUG,"[%p] parse token [%s]", self, token);
484 
485 	if (token[0] == '<') {
486 
487 		/* check argument */
488 		if (token[strlen(token) - 1] != '>') return -2;	/* error DONE */
489 		delimpos = -1;
490 		for (j = 1; j < strlen(token) - 1; j++) {
491 			if (token[j] == '.') {
492 				if (delimpos != -1)
493 					return -2;
494 				delimpos = j;
495 			} else if (!isdigit (token[j]))
496 				return -2;
497 		}
498 		if (delimpos == -1 || delimpos == 1 || delimpos == (int) (strlen(token) - 2))
499 			return -2;	/* no delimiter found or at first/last pos OR invalid args DONE */
500 
501 		/* read the numbers */
502 		token[strlen(token) - 1] = '\0';
503 		token[delimpos] = '\0';
504 		self->fi->msgparse_needed=1;
505 		guint64 octetstart = strtoull(&token[1], NULL, 10);
506 		gint64 octetcnt = strtoll(&token [delimpos + 1], NULL, 10);
507 		//TRACE(TRACE_DEBUG, "octetstart [%lu] octetcnt [%lu]", octetstart, octetcnt);
508 		dbmail_imap_session_bodyfetch_set_octetstart(self, octetstart);
509 		dbmail_imap_session_bodyfetch_set_octetcnt(self, octetcnt);
510 
511 		/* restore argument */
512 		token[delimpos] = '.';
513 		token[strlen(token) - 1] = '>';
514 	} else {
515 		self->args_idx--;
516 		return self->args_idx;
517 	}
518 
519 	self->args_idx++;
520 	return 0;	/* DONE */
521 }
522 
523 
524 #define TOKENAT(a) (self->args[self->args_idx+a]?p_string_str(self->args[self->args_idx+a]):NULL)
525 #define NEXTTOKEN TOKENAT(1)
526 #define TOKEN TOKENAT(0)
527 
dbmail_imap_session_fetch_parse_args(ImapSession * self)528 int dbmail_imap_session_fetch_parse_args(ImapSession * self)
529 {
530 	int ispeek = 0;
531 	const char *token = TOKEN;
532 	const char *nexttoken = NEXTTOKEN;
533 
534 	if (!token) return -1; // done
535 	if ((token[0] == ')') && (! nexttoken)) return -1; // done
536 	if (token[0] == '(') return 1; // skip
537 
538 	TRACE(TRACE_DEBUG,"[%p] parse args[%" PRIu64 "] = [%s]", self, self->args_idx, token);
539 
540 	if ((! nexttoken) && (strcmp(token,")") == 0))
541 		return -1; // done
542 
543 	if (MATCH(token,"flags")) {
544 		self->fi->getFlags = 1;
545 	} else if (MATCH(token,"internaldate")) {
546 		self->fi->getInternalDate=1;
547 	} else if (MATCH(token,"uid")) {
548 		self->fi->getUID=1;
549 	} else if (MATCH(token,"rfc822.size")) {
550 		self->fi->getSize = 1;
551 	} else if (MATCH(token,"fast")) {
552 		self->fi->getInternalDate = 1;
553 		self->fi->getFlags = 1;
554 		self->fi->getSize = 1;
555 	} else if (MATCH(token,"rfc822")) {
556 		self->fi->msgparse_needed=1;
557 		self->fi->getRFC822=1;
558 	} else if (MATCH(token,"rfc822.header")) {
559 		self->fi->msgparse_needed=1;
560 		self->fi->getRFC822Header = 1;
561 	} else if (MATCH(token,"rfc822.peek")) {
562 		self->fi->msgparse_needed=1;
563 		self->fi->getRFC822Peek = 1;
564 	} else if (MATCH(token,"rfc822.text")) {
565 		self->fi->msgparse_needed=1;
566 		self->fi->getRFC822Text = 1;
567 
568 	} else if (MATCH(token,"body") || MATCH(token,"body.peek")) {
569 
570 		body_fetch *bodyfetch = mempool_pop(self->pool, sizeof(body_fetch));
571 		self->fi->bodyfetch = p_list_append(self->fi->bodyfetch, bodyfetch);
572 
573 		if (MATCH(token,"body.peek")) ispeek=1;
574 
575 		nexttoken = NEXTTOKEN;
576 
577 		if (! nexttoken || ! MATCH(nexttoken,"[")) {
578 			if (ispeek) return -2;	/* error DONE */
579 			self->fi->msgparse_needed = 1;
580 			self->fi->getMIME_IMB_noextension = 1;	/* just BODY specified */
581 		} else {
582 			int res = 0;
583 			/* now read the argument list to body */
584 			self->args_idx++;	/* now pointing at '[' (not the last arg, parentheses are matched) */
585 			self->args_idx++;	/* now pointing at what should be the item type */
586 
587 			token = TOKEN;
588 			nexttoken = NEXTTOKEN;
589 
590 			TRACE(TRACE_DEBUG,"[%p] token [%s], nexttoken [%s]", self, token, nexttoken);
591 
592 			if (MATCH(token,"]")) {
593 				if (ispeek) {
594 					self->fi->msgparse_needed = 1;
595 					self->fi->getBodyTotalPeek = 1;
596 				} else {
597 					self->fi->msgparse_needed = 1;
598 					self->fi->getBodyTotal = 1;
599 				}
600 				self->args_idx++;
601 				res = _imap_session_fetch_parse_octet_range(self);
602 				if (res == -2)
603 					TRACE(TRACE_DEBUG,"[%p] fetch_parse_octet_range return with error", self);
604 				return res;
605 
606 			}
607 
608 			if (ispeek) self->fi->noseen = 1;
609 
610 			if (_imap_session_fetch_parse_partspec(self) < 0) {
611 				TRACE(TRACE_DEBUG,"[%p] fetch_parse_partspec return with error", self);
612 				return -2;
613 			}
614 
615 			self->args_idx++; // idx points to ']' now
616 			self->args_idx++; // idx points to octet range now
617 			res = _imap_session_fetch_parse_octet_range(self);
618 			if (res == -2)
619 				TRACE(TRACE_DEBUG,"[%p] fetch_parse_octet_range return with error", self);
620 			return res;
621 		}
622 	} else if (MATCH(token,"all")) {
623 		self->fi->msgparse_needed=1; // because of getEnvelope
624 		self->fi->getInternalDate = 1;
625 		self->fi->getEnvelope = 1;
626 		self->fi->getFlags = 1;
627 		self->fi->getSize = 1;
628 	} else if (MATCH(token,"full")) {
629 		self->fi->msgparse_needed=1;
630 		self->fi->getInternalDate = 1;
631 		self->fi->getEnvelope = 1;
632 		self->fi->getMIME_IMB_noextension = 1;
633 		self->fi->getFlags = 1;
634 		self->fi->getSize = 1;
635 	} else if (MATCH(token,"bodystructure")) {
636 		self->fi->msgparse_needed=1;
637 		self->fi->getMIME_IMB = 1;
638 	} else if (MATCH(token,"envelope")) {
639 		self->fi->getEnvelope = 1;
640 	} else if (Capa_match(self->capa, "CONDSTORE") && (MATCH(token,"changedsince"))) {
641 		char *rest = (char *)nexttoken;
642 		uint64_t seq = dm_strtoull(nexttoken, &rest, 10);
643 		if (rest == nexttoken)
644 			return -2;
645 		self->fi->changedsince = seq;
646 		self->mailbox->condstore = true;
647 		self->args_idx++;
648 	} else if (Capa_match(self->capa, "CONDSTORE") && (MATCH(token, "modseq"))) {
649 		self->mailbox->condstore = true;
650 	} else if (MATCH(token, "vanished")) {
651 		if (self->enabled.qresync && self->use_uid)
652 			self->fi->vanished = true;
653 		else
654 			return -2;
655 	}
656 
657 
658 	return 1; //theres more...
659 }
660 
661 
662 #define SEND_SPACE if (self->fi->isfirstfetchout) \
663 				self->fi->isfirstfetchout = 0; \
664 			else \
665 				dbmail_imap_session_buff_printf(self, " ")
666 
667 #define QUERY_BATCHSIZE 2000
668 
_send_headers(ImapSession * self,const body_fetch * bodyfetch,gboolean not)669 void _send_headers(ImapSession *self, const body_fetch *bodyfetch, gboolean not)
670 {
671 	long long cnt = 0;
672 	gchar *tmp;
673 	gchar *s;
674 	String_T ts;
675 
676 	dbmail_imap_session_buff_printf(self,"HEADER.FIELDS%s %s] ", not ? ".NOT" : "", bodyfetch->hdrplist);
677 
678 	if (! (s = g_tree_lookup(bodyfetch->headers, &(self->msg_idnr)))) {
679 		dbmail_imap_session_buff_printf(self, "{2}\r\n\r\n");
680 		return;
681 	}
682 
683 	TRACE(TRACE_DEBUG,"[%p] [%s] [%s]", self, bodyfetch->hdrplist, s);
684 
685 	ts = p_string_new(self->pool, s);
686 	tmp = get_crlf_encoded(p_string_str(ts));
687 	cnt = strlen(tmp);
688 
689 	if (bodyfetch->octetcnt > 0) {
690 		char *p = tmp;
691 		if (bodyfetch->octetstart > 0 && bodyfetch->octetstart < (guint64)cnt) {
692 			p += bodyfetch->octetstart;
693 			cnt -= bodyfetch->octetstart;
694 		}
695 
696 		if ((guint64)cnt > bodyfetch->octetcnt) {
697 			p[bodyfetch->octetcnt] = '\0';
698 			cnt = bodyfetch->octetcnt;
699 		}
700 
701 		dbmail_imap_session_buff_printf(self, "<%" PRIu64 "> {%" PRIu64 "}\r\n%s\r\n",
702 				bodyfetch->octetstart, cnt+2, p);
703 	} else {
704 		dbmail_imap_session_buff_printf(self, "{%" PRIu64 "}\r\n%s\r\n", cnt+2, tmp);
705 	}
706 
707 	p_string_free(ts,TRUE);
708 	ts = NULL;
709 	g_free(tmp);
710 	tmp = NULL;
711 }
712 
713 
714 /* get headers or not */
_fetch_headers(ImapSession * self,body_fetch * bodyfetch,gboolean not)715 static void _fetch_headers(ImapSession *self, body_fetch *bodyfetch, gboolean not)
716 {
717 	Connection_T c; ResultSet_T r; volatile int t = FALSE;
718 	gchar *fld, *val, *old, *new = NULL;
719 	uint64_t *mid;
720 	uint64_t id;
721 	GList *last;
722 	GString *fieldorder = NULL;
723 	GString *headerIDs = NULL;
724 	int k;
725 	int fieldseq=0;
726 	String_T query = NULL;
727 	String_T range = NULL;
728 
729 	if (! bodyfetch->headers) {
730 		TRACE(TRACE_DEBUG, "[%p] init bodyfetch->headers", self);
731 		bodyfetch->headers = g_tree_new_full((GCompareDataFunc)ucmpdata,NULL,(GDestroyNotify)uint64_free,(GDestroyNotify)g_free);
732 		self->ceiling = 0;
733 		self->hi = 0;
734 		self->lo = 0;
735 	}
736 
737 	if (! bodyfetch->hdrnames) {
738 
739 		GList *tlist = NULL;
740 		GString *h = NULL;
741 
742 		for (k = 0; k < bodyfetch->argcnt; k++)
743 			tlist = g_list_append(tlist, (void *)p_string_str(self->args[k + bodyfetch->argstart]));
744 
745 		bodyfetch->hdrplist = dbmail_imap_plist_as_string(tlist);
746 		h = g_list_join((GList *)tlist,"','");
747 		bodyfetch->names = tlist;
748 
749 		h = g_string_ascii_down(h);
750 
751 		bodyfetch->hdrnames = h->str;
752 
753 		g_string_free(h,FALSE);
754 	}
755 
756 	TRACE(TRACE_DEBUG,"[%p] for %" PRIu64 "%s [%s]", self, self->msg_idnr, not?"NOT":"", bodyfetch->hdrplist);
757 
758 	// did we prefetch this message already?
759 	if (self->msg_idnr <= self->ceiling) {
760 		_send_headers(self, bodyfetch, not);
761 		return;
762 	}
763 
764 	// let's fetch the required message and prefetch a batch if needed.
765 	range = p_string_new(self->pool, "");
766 
767 
768 	if (! (last = g_list_nth(self->ids_list, self->lo+(uint64_t)QUERY_BATCHSIZE)))
769 		last = g_list_last(self->ids_list);
770 	self->hi = *(uint64_t *)last->data;
771 
772 	if (self->msg_idnr == self->hi)
773 		p_string_printf(range, "= %" PRIu64 "", self->msg_idnr);
774 	else
775 		p_string_printf(range, "BETWEEN %" PRIu64 " AND %" PRIu64 "", self->msg_idnr, self->hi);
776 
777 	TRACE(TRACE_DEBUG,"[%p] prefetch %" PRIu64 ":%" PRIu64 " ceiling %" PRIu64 " [%s]", self, self->msg_idnr, self->hi, self->ceiling, bodyfetch->hdrplist);
778 
779 	headerIDs = g_string_new("0");
780 	if (! not) {
781 		fieldorder = g_string_new(", CASE ");
782 
783 		fieldseq = 0;
784 		bodyfetch->names = g_list_first(bodyfetch->names);
785 
786 		while (bodyfetch->names) {
787 			char *raw = (char *)bodyfetch->names->data;
788 			char *name = g_ascii_strdown(raw, strlen(raw));
789 			g_string_append_printf(fieldorder, "WHEN n.headername='%s' THEN %d ",
790 					name, fieldseq);
791 
792 			if (! g_list_next(bodyfetch->names)){
793 				g_free(name);
794 				break;
795 			}
796 			bodyfetch->names = g_list_next(bodyfetch->names);
797 			fieldseq++;
798 			/* get the id of the header */
799 			query = p_string_new(self->pool, "");
800 			p_string_printf(query, "select id from %sheadername "
801 				"where headername='%s' ",
802 				DBPFX,
803 				name);
804 
805 			c = db_con_get();
806 			TRY
807 				r = db_query(c, p_string_str(query));
808 				while (db_result_next(r)) {
809 					id = db_result_get_u64(r, 0);
810 					g_string_append_printf(headerIDs, ",%ld",id);
811 				}
812 			CATCH(SQLException)
813 				LOG_SQLERROR;
814 				t = DM_EQUERY;
815 			FINALLY
816 				db_con_close(c);
817 			END_TRY;
818 			p_string_free(query, TRUE);
819 			g_free(name);
820 		}
821 		fieldseq++;
822 		//adding default value, useful in NOT conditions, Cosmin Cioranu
823 		g_string_append_printf(fieldorder, "ELSE %d END AS seq",fieldseq);
824 	}
825 	TRACE(TRACE_DEBUG, "[headername ids %s] ", headerIDs->str);
826 	query = p_string_new(self->pool, "");
827 	p_string_printf(query, "SELECT m.message_idnr, n.headername, v.headervalue%s "
828 			"FROM %sheader h "
829 			"LEFT JOIN %smessages m ON h.physmessage_id=m.physmessage_id "
830 			"LEFT JOIN %sheadername n ON h.headername_id=n.id "
831 			"LEFT JOIN %sheadervalue v ON h.headervalue_id=v.id "
832 			"WHERE "
833 			"h.headername_id %s IN (%s) "
834 			"AND m.mailbox_idnr = %" PRIu64 " "
835 			"AND m.message_idnr %s "
836 			"AND status < %d "
837 
838 			//"AND n.headername %s IN ('%s') "	//old, from the sql point of view is slow, CC 2020
839 			// "GROUP By m.message_idnr, n.headername, v.headervalue "
840 			// "having seq %s %d "
841 			"ORDER BY m.message_idnr, seq",
842 			not?"":fieldorder->str,
843 			DBPFX, DBPFX, DBPFX, DBPFX,
844 			not?"NOT":"", headerIDs->str,
845 			self->mailbox->id, p_string_str(range),
846 			//not?"NOT":"", bodyfetch->hdrnames	//old
847 			MESSAGE_STATUS_DELETE			//return information only related to valid messages
848 			//not?"=":"<",fieldseq			//patch Cosmin Cioranu, added the having conditions and also the 'not' handler
849 		    );
850 
851 	if (fieldorder)
852 		g_string_free(fieldorder, TRUE);
853 	if (headerIDs)
854 		g_string_free(headerIDs, TRUE);
855 	c = db_con_get();
856 	TRY
857 		r = db_query(c, p_string_str(query));
858 		while (db_result_next(r)) {
859 			int l;
860 			const void *blob;
861 
862 			id = db_result_get_u64(r, 0);
863 
864 			if (! g_tree_lookup(self->ids,&id))
865 				continue;
866 
867 			fld = (char *)db_result_get(r, 1);
868 			blob = db_result_get_blob(r, 2, &l);
869 			char *str = g_new0(char, l + 1);
870 			str = strncpy(str, blob, l);
871 			val = dbmail_iconv_db_to_utf7(str);
872 			g_free(str);
873 			if (! val) {
874 				TRACE(TRACE_DEBUG, "[%p] [%" PRIu64 "] no headervalue [%s]", self, id, fld);
875 			} else {
876 				mid = mempool_pop(small_pool, sizeof(uint64_t));
877 				*mid = id;
878 
879 				old = g_tree_lookup(bodyfetch->headers, (gconstpointer)mid);
880 				fld[0] = toupper(fld[0]);
881 				/* Build content as Header: value \n */
882 				new = g_strdup_printf("%s%s: %s\n", old?old:"", fld, val);
883 				g_free(val);
884 				g_tree_insert(bodyfetch->headers,mid,new);
885 			}
886 
887 		}
888 	CATCH(SQLException)
889 		LOG_SQLERROR;
890 		t = DM_EQUERY;
891 	FINALLY
892 		db_con_close(c);
893 	END_TRY;
894 
895 	p_string_free(range, TRUE);
896 	p_string_free(query, TRUE);
897 
898 	if (t == DM_EQUERY) return;
899 
900 	self->lo += QUERY_BATCHSIZE;
901 	self->ceiling = self->hi;
902 
903 	_send_headers(self, bodyfetch, not);
904 
905 	return;
906 }
907 
get_dumpsize(body_fetch * bodyfetch,uint64_t dumpsize)908 static uint64_t get_dumpsize(body_fetch *bodyfetch, uint64_t dumpsize)
909 {
910 	if (bodyfetch->octetstart > dumpsize)
911 		return 0;
912 	if ((bodyfetch->octetstart + dumpsize) > bodyfetch->octetcnt)
913 		return bodyfetch->octetcnt;
914 	return (dumpsize - bodyfetch->octetstart);
915 }
916 
_imap_send_part(ImapSession * self,GMimeObject * part,body_fetch * bodyfetch,const char * type)917 static void _imap_send_part(ImapSession *self, GMimeObject *part, body_fetch *bodyfetch, const char *type)
918 {
919 	TRACE(TRACE_DEBUG,"[%p] type [%s]", self, type?type:"");
920 	if ( !part ) {
921 		dbmail_imap_session_buff_printf(self, "] NIL");
922 	} else {
923 		char *tmp = imap_get_logical_part(part,type);
924 		String_T str = p_string_new(self->pool, tmp);
925 		size_t len = p_string_len(str);
926 		g_free(tmp);
927 
928 		if (len < 1) {
929 			dbmail_imap_session_buff_printf(self, "] NIL");
930 		} else {
931 			uint64_t cnt = 0;
932 			if (bodyfetch->octetcnt > 0) {
933 				cnt = get_dumpsize(bodyfetch, len);
934 				dbmail_imap_session_buff_printf(self, "]<%" PRIu64 "> {%" PRIu64 "}\r\n", bodyfetch->octetstart, cnt);
935 				p_string_erase(str,0,min(bodyfetch->octetstart,len));
936 				p_string_truncate(str,cnt);
937 			} else {
938 				dbmail_imap_session_buff_printf(self, "] {%" PRIu64 "}\r\n", len);
939 			}
940 			dbmail_imap_session_buff_printf(self,"%s", p_string_str(str));
941 		}
942 		p_string_free(str,TRUE);
943 	}
944 }
945 
946 
_imap_show_body_section(body_fetch * bodyfetch,gpointer data)947 static int _imap_show_body_section(body_fetch *bodyfetch, gpointer data)
948 {
949 	GMimeObject *part = NULL;
950 	gboolean condition = FALSE;
951 	ImapSession *self = (ImapSession *)data;
952 
953 	if (bodyfetch->itemtype < BFIT_TEXT)
954 	       	return 0;
955 
956 	TRACE(TRACE_DEBUG,"[%p] itemtype [%d] partspec [%s]", self, bodyfetch->itemtype, bodyfetch->partspec);
957 
958 	if (self->fi->msgparse_needed) {
959 		if (bodyfetch->partspec[0]) {
960 			if (bodyfetch->partspec[0] == '0') {
961 				dbmail_imap_session_buff_printf(self, "\r\n%s BAD protocol error\r\n", self->tag);
962 				TRACE(TRACE_ERR, "[%p] PROTOCOL ERROR", self);
963 				return 1;
964 			}
965 			part = imap_get_partspec(GMIME_OBJECT((self->message)->content), bodyfetch->partspec);
966 		} else {
967 			part = GMIME_OBJECT((self->message)->content);
968 		}
969 	}
970 
971 	SEND_SPACE;
972 
973 	if (! self->fi->noseen) self->fi->setseen = 1;
974 	dbmail_imap_session_buff_printf(self, "BODY[%s", bodyfetch->partspec);
975 
976 	switch (bodyfetch->itemtype) {
977 
978 		case BFIT_ALL:
979 			_imap_send_part(self, part, bodyfetch, NULL);
980 			break;
981 		case BFIT_TEXT:
982 			dbmail_imap_session_buff_printf(self, "TEXT");
983 			_imap_send_part(self, part, bodyfetch, "TEXT");
984 			break;
985 			// fall-through
986 		case BFIT_HEADER:
987 			dbmail_imap_session_buff_printf(self, "HEADER");
988 			_imap_send_part(self, part, bodyfetch, "HEADER");
989 			break;
990 		case BFIT_MIME:
991 			dbmail_imap_session_buff_printf(self, "MIME");
992 			_imap_send_part(self, part, bodyfetch, "MIME");
993 			break;
994 		case BFIT_HEADER_FIELDS_NOT:
995 			condition=TRUE;
996 			// fall-through
997 		case BFIT_HEADER_FIELDS:
998 			_fetch_headers(self, bodyfetch, condition);
999 			break;
1000 		default:
1001 			dbmail_imap_session_buff_clear(self);
1002 			dbmail_imap_session_buff_printf(self, "\r\n* BYE internal server error\r\n");
1003 			return -1;
1004 
1005 	}
1006 	return 0;
1007 }
1008 
1009 /* get envelopes */
_fetch_envelopes(ImapSession * self)1010 static void _fetch_envelopes(ImapSession *self)
1011 {
1012 	Connection_T c; ResultSet_T r; volatile int t = FALSE;
1013 	INIT_QUERY;
1014 	gchar *s;
1015 	uint64_t *mid;
1016 	uint64_t id;
1017 	char range[DEF_FRAGSIZE];
1018 	GList *last;
1019 	memset(range,0,sizeof(range));
1020 
1021 	if (! self->envelopes) {
1022 		self->envelopes = g_tree_new_full((GCompareDataFunc)ucmpdata,NULL,(GDestroyNotify)uint64_free,(GDestroyNotify)g_free);
1023 		self->lo = 0;
1024 		self->hi = 0;
1025 	}
1026 
1027 	if ((s = g_tree_lookup(self->envelopes, &(self->msg_idnr))) != NULL) {
1028 		dbmail_imap_session_buff_printf(self, "ENVELOPE %s", s);
1029 		return;
1030 	}
1031 
1032 	TRACE(TRACE_DEBUG,"[%p] lo: %" PRIu64 "", self, self->lo);
1033 
1034 	if (! (last = g_list_nth(self->ids_list, self->lo+(uint64_t)QUERY_BATCHSIZE)))
1035 		last = g_list_last(self->ids_list);
1036 	self->hi = *(uint64_t *)last->data;
1037 
1038 	if (self->msg_idnr == self->hi)
1039 		snprintf(range,DEF_FRAGSIZE-1,"= %" PRIu64 "", self->msg_idnr);
1040 	else
1041 		snprintf(range,DEF_FRAGSIZE-1,"BETWEEN %" PRIu64 " AND %" PRIu64 "", self->msg_idnr, self->hi);
1042 
1043 	snprintf(query, DEF_QUERYSIZE-1, "SELECT message_idnr,envelope "
1044 			"FROM %senvelope e "
1045 			"LEFT JOIN %smessages m USING (physmessage_id) "
1046 			"WHERE m.mailbox_idnr = %" PRIu64 " "
1047 			"AND message_idnr %s",
1048 			DBPFX, DBPFX,
1049 			self->mailbox->id, range);
1050 	c = db_con_get();
1051 	TRY
1052 		r = db_query(c, query);
1053 		while (db_result_next(r)) {
1054 			id = db_result_get_u64(r, 0);
1055 
1056 			if (! g_tree_lookup(self->ids,&id))
1057 				continue;
1058 
1059 			mid = mempool_pop(small_pool, sizeof(uint64_t));
1060 			*mid = id;
1061 
1062 			g_tree_insert(self->envelopes,mid,g_strdup(ResultSet_getString(r, 2)));
1063 		}
1064 	CATCH(SQLException)
1065 		LOG_SQLERROR;
1066 		t = DM_EQUERY;
1067 	FINALLY
1068 		db_con_close(c);
1069 	END_TRY;
1070 
1071 	if (t == DM_EQUERY) return;
1072 
1073 	self->lo += QUERY_BATCHSIZE;
1074 
1075 	s = g_tree_lookup(self->envelopes, &(self->msg_idnr));
1076 	dbmail_imap_session_buff_printf(self, "ENVELOPE %s", s?s:"");
1077 }
1078 
_imap_show_body_sections(ImapSession * self)1079 static void _imap_show_body_sections(ImapSession *self)
1080 {
1081 	List_T head;
1082 	if (! self->fi->bodyfetch)
1083 		return;
1084 
1085 	head = p_list_first(self->fi->bodyfetch);
1086 	while (head) {
1087 		body_fetch *bodyfetch = (body_fetch *)p_list_data(head);
1088 		if (bodyfetch)
1089 			_imap_show_body_section(bodyfetch, self);
1090 		head = p_list_next(head);
1091 	}
1092 }
1093 
_fetch_get_items(ImapSession * self,uint64_t * uid)1094 static int _fetch_get_items(ImapSession *self, uint64_t *uid)
1095 {
1096 
1097 	int result;
1098 	uint64_t size = 0;
1099 	gchar *s = NULL;
1100 	uint64_t *id = uid;
1101 	gboolean reportflags = FALSE;
1102 	String_T stream = NULL;
1103 
1104 	TRACE(TRACE_DEBUG,"Call: _fetch_get_items");
1105 
1106 	MessageInfo *msginfo = g_tree_lookup(MailboxState_getMsginfo(self->mailbox->mbstate), uid);
1107 
1108 	if (! msginfo) {
1109 		TRACE(TRACE_INFO, "[%p] failed to lookup msginfo struct for message [%" PRIu64 "]", self, *uid);
1110 		return 0;
1111 	}
1112 
1113 	id = g_tree_lookup(MailboxState_getIds(self->mailbox->mbstate),uid);
1114 
1115 	g_return_val_if_fail(id,-1);
1116 
1117 	if (self->fi->changedsince && (msginfo->seq <= self->fi->changedsince))
1118 		return 0;
1119 
1120 	self->msg_idnr = *uid;
1121 	self->fi->isfirstfetchout = 1;
1122 
1123 	if (self->fi->msgparse_needed) {
1124 		if (! (dbmail_imap_session_message_load(self)))
1125 			return 0;
1126 
1127 		stream = self->message->crlf;
1128 		size = p_string_len(stream);
1129 	}
1130 
1131 	dbmail_imap_session_buff_printf(self, "* %" PRIu64 " FETCH (", *id);
1132 
1133 	if (self->mailbox->condstore || self->enabled.qresync) {
1134 		SEND_SPACE;
1135 		dbmail_imap_session_buff_printf(self, "MODSEQ (%" PRIu64 ")",
1136 				msginfo->seq?msginfo->seq:1);
1137 	}
1138 	if (self->fi->getInternalDate) {
1139 		SEND_SPACE;
1140 		char *s =date_sql2imap(msginfo->internaldate);
1141 		dbmail_imap_session_buff_printf(self, "INTERNALDATE \"%s\"", s);
1142 		g_free(s);
1143 	}
1144 	if (self->fi->getSize) {
1145 		uint64_t rfcsize = msginfo->rfcsize;
1146 		SEND_SPACE;
1147 		dbmail_imap_session_buff_printf(self, "RFC822.SIZE %" PRIu64 "", rfcsize);
1148 	}
1149 	if (self->fi->getFlags) {
1150 		SEND_SPACE;
1151 
1152 		GList *sublist = MailboxState_message_flags(self->mailbox->mbstate, msginfo);
1153 		s = dbmail_imap_plist_as_string(sublist);
1154 		g_list_destroy(sublist);
1155 		dbmail_imap_session_buff_printf(self,"FLAGS %s",s);
1156 		g_free(s);
1157 	}
1158 	if (self->fi->getUID) {
1159 		SEND_SPACE;
1160 		dbmail_imap_session_buff_printf(self, "UID %" PRIu64 "", msginfo->uid);
1161 	}
1162 	if (self->fi->getMIME_IMB) {
1163 		SEND_SPACE;
1164 		if ((s = imap_get_structure(GMIME_MESSAGE((self->message)->content), 1))==NULL) {
1165 			dbmail_imap_session_buff_clear(self);
1166 			dbmail_imap_session_buff_printf(self, "\r\n* BYE error fetching body structure\r\n");
1167 			return -1;
1168 		}
1169 		dbmail_imap_session_buff_printf(self, "BODYSTRUCTURE %s", s);
1170 		g_free(s);
1171 	}
1172 
1173 	if (self->fi->getMIME_IMB_noextension) {
1174 		SEND_SPACE;
1175 		if ((s = imap_get_structure(GMIME_MESSAGE((self->message)->content), 0))==NULL) {
1176 			dbmail_imap_session_buff_clear(self);
1177 			dbmail_imap_session_buff_printf(self, "\r\n* BYE error fetching body\r\n");
1178 			return -1;
1179 		}
1180 		dbmail_imap_session_buff_printf(self, "BODY %s",s);
1181 		g_free(s);
1182 	}
1183 
1184 	if (self->fi->getEnvelope) {
1185 		SEND_SPACE;
1186 		_fetch_envelopes(self);
1187 	}
1188 
1189 	if (self->fi->getRFC822 || self->fi->getRFC822Peek) {
1190 		SEND_SPACE;
1191 		dbmail_imap_session_buff_printf(self, "RFC822 {%" PRIu64 "}\r\n", size);
1192 		send_data(self, stream, 0, size);
1193 		if (self->fi->getRFC822)
1194 			self->fi->setseen = 1;
1195 
1196 	}
1197 
1198 	if (self->fi->getBodyTotal || self->fi->getBodyTotalPeek) {
1199 		SEND_SPACE;
1200 		if (dbmail_imap_session_bodyfetch_get_last_octetcnt(self) == 0) {
1201 			dbmail_imap_session_buff_printf(self, "BODY[] {%" PRIu64 "}\r\n", size);
1202 			send_data(self, stream, 0, size);
1203 		} else {
1204 			uint64_t start = dbmail_imap_session_bodyfetch_get_last_octetstart(self);
1205 			uint64_t count = dbmail_imap_session_bodyfetch_get_last_octetcnt(self);
1206 			uint64_t length = 0;
1207 			if (start <= size)
1208 				length = ((start + count) > size)?(size - start):count;
1209 			dbmail_imap_session_buff_printf(self, "BODY[]<%" PRIu64 "> {%" PRIu64 "}\r\n",
1210 					start, length);
1211 			send_data(self, stream, start, length);
1212 		}
1213 		if (self->fi->getBodyTotal)
1214 			self->fi->setseen = 1;
1215 	}
1216 
1217 	if (self->fi->getRFC822Header) {
1218 		SEND_SPACE;
1219 		char *tmp = imap_get_logical_part(self->message->content, "HEADER");
1220 		dbmail_imap_session_buff_printf(self, "RFC822.HEADER {%ld}\r\n%s", strlen(tmp), tmp);
1221 		free(tmp);
1222 		tmp = NULL;
1223 	}
1224 
1225 	if (self->fi->getRFC822Text) {
1226 		SEND_SPACE;
1227 		char *tmp = imap_get_logical_part(self->message->content, "TEXT");
1228 		dbmail_imap_session_buff_printf(self, "RFC822.TEXT {%ld}\r\n%s", strlen(tmp), tmp);
1229 		free(tmp);
1230 		tmp = NULL;
1231 		self->fi->setseen = 1;
1232 
1233 	}
1234 
1235 	_imap_show_body_sections(self);
1236 
1237 	/* set \Seen flag if necessary; note the absence of an error-check
1238 	 * for db_get_msgflag()!
1239 	 */
1240 	int setSeenSet[IMAP_NFLAGS] = { 1, 0, 0, 0, 0, 0 };
1241 	if (self->fi->setseen && db_get_msgflag("seen", self->msg_idnr) != 1) {
1242 		/* only if the user has an ACL which grants
1243 		   him rights to set the flag should the
1244 		   flag be set! */
1245 		result = acl_has_right(self->mailbox->mbstate, self->userid, ACL_RIGHT_SEEN);
1246 		if (result == -1) {
1247 			dbmail_imap_session_buff_clear(self);
1248 			dbmail_imap_session_buff_printf(self, "\r\n* BYE internal dbase error\r\n");
1249 			return -1;
1250 		}
1251 
1252 		if (result == 1) {
1253 			reportflags = TRUE;
1254 			result = db_set_msgflag(self->msg_idnr, setSeenSet, NULL, IMAPFA_ADD, 0, msginfo);
1255 			if (result == -1) {
1256 				dbmail_imap_session_buff_clear(self);
1257 				dbmail_imap_session_buff_printf(self, "\r\n* BYE internal dbase error\r\n");
1258 				return -1;
1259 			}
1260 			db_mailbox_seq_update(MailboxState_getId(self->mailbox->mbstate), self->msg_idnr);
1261 		}
1262 
1263 		self->fi->getFlags = 1;
1264 	}
1265 
1266 	dbmail_imap_session_buff_printf(self, ")\r\n");
1267 
1268 	if (reportflags) {
1269 		char *t = NULL;
1270 		GList *sublist = NULL;
1271 		if (self->use_uid)
1272 			t = g_strdup_printf("UID %" PRIu64 " ", *uid);
1273 
1274 		sublist = MailboxState_message_flags(self->mailbox->mbstate, msginfo);
1275 		s = dbmail_imap_plist_as_string(sublist);
1276 		g_list_destroy(sublist);
1277 
1278 		dbmail_imap_session_buff_printf(self,"* %" PRIu64 " FETCH (%sFLAGS %s)\r\n", *id, t?t:"", s);
1279 		if (t) g_free(t);
1280 		g_free(s);
1281 	}
1282 
1283 	return 0;
1284 }
1285 
_do_fetch(uint64_t * uid,gpointer UNUSED value,ImapSession * self)1286 static gboolean _do_fetch(uint64_t *uid, gpointer UNUSED value, ImapSession *self)
1287 {
1288 	/* go fetch the items */
1289 	if (_fetch_get_items(self,uid) < 0) {
1290 		TRACE(TRACE_ERR, "[%p] _fetch_get_items returned with error", self);
1291 		dbmail_imap_session_buff_clear(self);
1292 		self->error = TRUE;
1293 		return TRUE;
1294 	}
1295 	dbmail_imap_session_buff_flush(self);
1296 
1297 	return FALSE;
1298 }
1299 
dbmail_imap_session_fetch_get_items(ImapSession * self)1300 int dbmail_imap_session_fetch_get_items(ImapSession *self)
1301 {
1302 	if (! self->ids)
1303 		TRACE(TRACE_INFO, "[%p] self->ids is NULL", self);
1304 	else {
1305 		self->error = FALSE;
1306 		g_tree_foreach(self->ids, (GTraverseFunc) _do_fetch, self);
1307 		dbmail_imap_session_buff_flush(self);
1308 		if (self->error) return -1;
1309 	}
1310 	return 0;
1311 
1312 }
1313 
1314 /*
1315  * check_state_and_args()
1316  *
1317  * checks if the user is in the right state & the numbers of arguments;
1318  * a state of -1 specifies any state
1319  * arguments can be grouped by means of parentheses
1320  *
1321  * returns 1 on succes, 0 on failure
1322  */
dbmail_imap_session_buff_clear(ImapSession * self)1323 void dbmail_imap_session_buff_clear(ImapSession *self)
1324 {
1325 	self->buff = p_string_truncate(self->buff, 0);
1326 }
1327 
dbmail_imap_session_buff_flush(ImapSession * self)1328 void dbmail_imap_session_buff_flush(ImapSession *self)
1329 {
1330 	if (self->state >= CLIENTSTATE_LOGOUT) return;
1331 	if (p_string_len(self->buff) < 1) return;
1332 
1333 	gpointer session = self;
1334 	gpointer data = self->buff;
1335 	if (! queue_pool)
1336 		self->buff = p_string_new(self->pool, "");
1337 	else
1338 		self->buff = p_string_new(queue_pool, "");
1339 
1340 	dm_queue_push(dm_thread_data_sendmessage, session, data);
1341 }
1342 
1343 #define IMAP_BUF_SIZE 4096
1344 
dbmail_imap_session_buff_printf(ImapSession * self,char * message,...)1345 int dbmail_imap_session_buff_printf(ImapSession * self, char * message, ...)
1346 {
1347 	va_list ap, cp;
1348 	uint64_t j = 0, l;
1349 
1350 	assert(message);
1351 	j = p_string_len(self->buff);
1352 
1353 	va_start(ap, message);
1354 	va_copy(cp, ap);
1355 	p_string_append_vprintf(self->buff, message, cp);
1356 	va_end(cp);
1357 	va_end(ap);
1358 	l = p_string_len(self->buff);
1359 
1360 	if (l >= IMAP_BUF_SIZE) dbmail_imap_session_buff_flush(self);
1361 
1362 	return (int)(l-j);
1363 }
1364 
dbmail_imap_session_handle_auth(ImapSession * self,const char * username,const char * password)1365 int dbmail_imap_session_handle_auth(ImapSession * self, const char * username, const char * password)
1366 {
1367 	uint64_t userid = 0;
1368 	int valid = 0;
1369 
1370 	if (self->ci->auth)
1371 		username = Cram_getUsername(self->ci->auth);
1372 
1373 	TRACE(TRACE_DEBUG, "[%p] trying to validate user [%s]", self, username);
1374 
1375 	valid = auth_validate(self->ci, username, password, &userid);
1376 
1377 
1378 	switch(valid) {
1379 		case -1: /* a db-error occurred */
1380 			dbmail_imap_session_buff_printf(self, "* BYE internal db error validating user\r\n");
1381 			return -1;
1382 
1383 		case 0:
1384 			sleep(2);	/* security */
1385 			ci_authlog_init(self->ci, THIS_MODULE, username, AUTHLOG_ERR);
1386 			if (self->ci->auth) { // CRAM-MD5 auth failed
1387 				char *enctype = NULL;
1388 				if (userid) enctype = auth_getencryption(userid);
1389 				if ((! enctype) || (! MATCH(enctype,""))) {
1390 					Capa_remove(self->capa,"AUTH=CRAM-MD5");
1391 					dbmail_imap_session_buff_printf(self, "* CAPABILITY %s\r\n", Capa_as_string(self->capa));
1392 				}
1393 				if (enctype) g_free(enctype);
1394 			}
1395 			dbmail_imap_session_buff_printf(self, "%s NO login rejected\r\n", self->tag);
1396 			TRACE(TRACE_NOTICE, "[%p] login rejected: user [%s] from [%s:%s]", self, username,
1397 				self->ci->src_ip, self->ci->src_port);
1398 			return 1;
1399 
1400 		case 1:
1401 			self->userid = userid;
1402 			ci_authlog_init(self->ci, THIS_MODULE, username, AUTHLOG_ACT);
1403 			TRACE(TRACE_NOTICE, "[%p] login accepted: id [%" PRIu64 "] user [%s] from [%s:%s]",
1404 				       	self, userid, username, self->ci->src_ip, self->ci->src_port);
1405 			break;
1406 
1407 		default:
1408 			TRACE(TRACE_ERR, "[%p] auth_validate returned [%d]", self, valid);
1409 			return -1;
1410 	}
1411 
1412 	dbmail_imap_session_set_state(self,CLIENTSTATE_AUTHENTICATED);
1413 
1414 	return 0;
1415 
1416 }
1417 
dbmail_imap_session_prompt(ImapSession * self,char * prompt)1418 static int dbmail_imap_session_prompt(ImapSession * self, char * prompt)
1419 {
1420 	char *prompt64, *promptcat;
1421 
1422 	g_return_val_if_fail(prompt != NULL, -1);
1423 
1424 	/* base64 encoding increases string length by about 40%. */
1425 	promptcat = g_strdup_printf("%s\r\n", prompt);
1426 	prompt64 = (char *)g_base64_encode((const guchar *)promptcat, strlen(promptcat));
1427 	dbmail_imap_session_buff_printf(self, "+ %s\r\n", prompt64);
1428 	dbmail_imap_session_buff_flush(self);
1429 
1430 	g_free(prompt64);
1431 	g_free(promptcat);
1432 
1433 	return 0;
1434 }
1435 
notify_fetch(ImapSession * self,MailboxState_T N,uint64_t * uid)1436 static void notify_fetch(ImapSession *self, MailboxState_T N, uint64_t *uid)
1437 {
1438 	uint64_t *msn;
1439 
1440 	GList *ol = NULL, *nl = NULL;
1441 	char *oldflags = NULL, *newflags = NULL;
1442 	MessageInfo *old = NULL, *new = NULL;
1443 	MailboxState_T M = self->mailbox->mbstate;
1444 	gboolean flagschanged = false, modseqchanged = false;
1445 
1446 	assert(uid);
1447 
1448 	if (! (MailboxState_getMsginfo(N) && *uid && (new = g_tree_lookup(MailboxState_getMsginfo(N), uid))))
1449 		return;
1450 
1451 	if (! (msn = g_tree_lookup(MailboxState_getIds(M), uid)))
1452 		return;
1453 
1454 	MailboxState_merge_recent(N, M);
1455 
1456 	// FETCH
1457 	if ((old = g_tree_lookup(MailboxState_getMsginfo(M), uid)))
1458 		ol = MailboxState_message_flags(M, old);
1459 	oldflags = dbmail_imap_plist_as_string(ol);
1460 
1461 	nl = MailboxState_message_flags(N, new);
1462 	newflags = dbmail_imap_plist_as_string(nl);
1463 
1464 	g_list_destroy(ol);
1465 	g_list_destroy(nl);
1466 
1467 	if ((!old) || (old->seq < new->seq))
1468 		modseqchanged = true;
1469 	if (oldflags && (! MATCH(oldflags, newflags)))
1470 		flagschanged = true;
1471 
1472 	if (modseqchanged || flagschanged) {
1473 		GList *plist = NULL;
1474 		char *response = NULL;
1475 		if (self->use_uid) {
1476 			char *u = g_strdup_printf("UID %" PRIu64, *uid);
1477 			plist = g_list_append(plist, u);
1478 		}
1479 
1480 		if (modseqchanged && self->mailbox->condstore) {
1481 			TRACE(TRACE_DEBUG, "seq [%" PRIu64 "] -> [%" PRIu64 "]", old?old->seq:0, new->seq);
1482 			char *m = g_strdup_printf("MODSEQ (%" PRIu64 ")", new->seq);
1483 			plist = g_list_append(plist, m);
1484 		}
1485 
1486 		if (flagschanged) {
1487 			TRACE(TRACE_DEBUG, "flags [%s] -> [%s]", oldflags, newflags);
1488 			char *f = g_strdup_printf("FLAGS %s", newflags);
1489 			plist = g_list_append(plist, f);
1490 		}
1491 
1492 		response = dbmail_imap_plist_as_string(plist);
1493 
1494 		dbmail_imap_session_buff_printf(self, "* %" PRIu64 " FETCH %s\r\n",
1495 				*msn, response);
1496 		g_free(response);
1497 		g_list_destroy(plist);
1498 	}
1499 
1500 	if (oldflags) g_free(oldflags);
1501 	g_free(newflags);
1502 }
1503 
notify_expunge(ImapSession * self,uint64_t * uid)1504 static gboolean notify_expunge(ImapSession *self, uint64_t *uid)
1505 {
1506 	uint64_t *msn = NULL, m = 0;
1507 
1508 	if (! (msn = g_tree_lookup(MailboxState_getIds(self->mailbox->mbstate), uid))) {
1509 		TRACE(TRACE_DEBUG,"[%p] can't find uid [%" PRIu64 "]", self, *uid);
1510 		return TRUE;
1511 	}
1512 
1513 	switch (self->command_type) {
1514 		case IMAP_COMM_FETCH:
1515 		case IMAP_COMM_STORE:
1516 		case IMAP_COMM_SEARCH:
1517 			break;
1518 		default:
1519 			m = *msn;
1520 			if (MailboxState_removeUid(self->mailbox->mbstate, *uid) == DM_SUCCESS)
1521 				dbmail_imap_session_buff_printf(self, "* %" PRIu64 " EXPUNGE\r\n", m);
1522 			else
1523 				return TRUE;
1524 		break;
1525 	}
1526 
1527 	return FALSE;
1528 }
1529 
mailbox_notify_expunge(ImapSession * self,MailboxState_T N)1530 static void mailbox_notify_expunge(ImapSession *self, MailboxState_T N)
1531 {
1532 	uint64_t *uid, *msn;
1533 	MailboxState_T M;
1534 	GList *ids;
1535 	if (! N) return;
1536 
1537 	M = self->mailbox->mbstate;
1538 
1539 	ids  = g_tree_keys(MailboxState_getIds(M));
1540 	ids = g_list_reverse(ids);
1541 
1542 	// send expunge updates
1543 
1544 	if (ids) {
1545 		uid = (uint64_t *)ids->data;
1546 		GTree * treeM=MailboxState_getIds(M);
1547 		if (treeM != NULL){
1548 		    msn = g_tree_lookup(treeM, uid);
1549 		    if (msn && (*msn > MailboxState_getExists(M))) {
1550 			    TRACE(TRACE_DEBUG,"exists new [%d] old: [%d]", MailboxState_getExists(N), MailboxState_getExists(M));
1551 			    dbmail_imap_session_buff_printf(self, "* %d EXISTS\r\n", MailboxState_getExists(M));
1552 		    }
1553 		}
1554 	}
1555 	while (ids) {
1556 		uid = (uint64_t *)ids->data;
1557 		MessageInfo *messageInfo=g_tree_lookup(MailboxState_getMsginfo(N), uid);
1558 		if (messageInfo!=NULL && !g_tree_lookup(MailboxState_getIds(N), uid)) {
1559 			/* mark message as expunged, it should be ok to be removed from list, see state_load_message */
1560 			messageInfo->expunged=1;
1561 			notify_expunge(self, uid);
1562 		}
1563 
1564 		if (! g_list_next(ids)) break;
1565 		ids = g_list_next(ids);
1566 	}
1567 	ids = g_list_first(ids);
1568 	g_list_free(ids);
1569 }
1570 
mailbox_notify_fetch(ImapSession * self,MailboxState_T N)1571 static void mailbox_notify_fetch(ImapSession *self, MailboxState_T N)
1572 {
1573 	uint64_t *uid, *id;
1574 	GList *ids;
1575 	if (! N) return;
1576 
1577 	// send fetch updates
1578 	ids = g_tree_keys(MailboxState_getIds(self->mailbox->mbstate));
1579 	ids = g_list_first(ids);
1580 	while (ids) {
1581 		uid = (uint64_t *)ids->data;
1582 		notify_fetch(self, N, uid);
1583 		if (! g_list_next(ids)) break;
1584 		ids = g_list_next(ids);
1585 	}
1586 	g_list_free(g_list_first(ids));
1587 
1588 	// switch active mailbox view
1589 	self->mailbox->mbstate = N;
1590 	id = mempool_pop(small_pool, sizeof(uint64_t));
1591 	*id = MailboxState_getId(N);
1592 
1593 
1594 
1595 	g_tree_replace(self->mbxinfo, id, N);
1596 
1597 	MailboxState_flush_recent(N);
1598 }
1599 
dbmail_imap_session_mailbox_status(ImapSession * self,gboolean update)1600 int dbmail_imap_session_mailbox_status(ImapSession * self, gboolean update)
1601 {
1602 	/*
1603 		C: a047 NOOP
1604 		S: * 22 EXPUNGE
1605 		S: * 23 EXISTS
1606 		S: * 3 RECENT
1607 		S: * 14 FETCH (FLAGS (\Seen \Deleted))
1608 	*/
1609 
1610 	MailboxState_T M, N = NULL;
1611 	gboolean showexists = FALSE, showrecent = FALSE, showflags = FALSE;
1612 	unsigned oldexists;
1613 
1614 	if (self->state != CLIENTSTATE_SELECTED) return FALSE;
1615 
1616 	if (update) {
1617 		unsigned oldseq, newseq;
1618 		uint64_t olduidnext;
1619 		char *oldflags, *newflags;
1620 
1621 		M = self->mailbox->mbstate;
1622 		oldseq = MailboxState_getSeq(M);
1623 		oldflags = MailboxState_flags(M);
1624 		oldexists = MailboxState_getExists(M);
1625 		olduidnext = MailboxState_getUidnext(M);
1626 
1627                 // check the mailbox sequence without a
1628 		// full reload
1629 		N = MailboxState_new(self->pool, 0);
1630 		MailboxState_setId(N, self->mailbox->id);
1631 		newseq = MailboxState_getSeq(N);
1632 		MailboxState_free(&N);
1633 		N = NULL;
1634 
1635 		TRACE(TRACE_DEBUG, "seq: [%u] -> [%u]", oldseq, newseq);
1636 		if (oldseq != newseq) {
1637 			int mailbox_update_strategy = config_get_value_default_int("mailbox_update_strategy", "IMAP", 1);
1638 
1639 			if (mailbox_update_strategy == 1){
1640 			    TRACE(TRACE_DEBUG, "Strategy reload: 1 (full reload)");
1641 			    /* do a full reload: re-read flags and counters */
1642 			    N = MailboxState_new(self->pool, self->mailbox->id);
1643 			}else{
1644 			    if (mailbox_update_strategy == 2){
1645 					TRACE(TRACE_DEBUG, "Strategy reload: 2 (differential reload)");
1646 					/* do a diff reload, experimental */
1647 					N = MailboxState_update(self->pool, M);
1648 				}else{
1649 					TRACE(TRACE_DEBUG, "Strategy reload: default (full reload)");
1650 					/* default strategy is full reload, case 1*/
1651 					N = MailboxState_new(self->pool, self->mailbox->id);
1652 			    }
1653 			}
1654 
1655 			unsigned newexists = MailboxState_getExists(N);
1656 			MailboxState_setExists(N, max(oldexists, newexists));
1657 
1658 			// rebuild uid/msn trees
1659 			// ATTN: new messages shouldn't be visible in any way to a
1660 			// client session until it has been announced with EXISTS
1661 
1662 			// EXISTS response may never decrease
1663 			if ((MailboxState_getUidnext(N) > olduidnext)) {
1664 				showexists = TRUE;
1665 			}
1666 
1667 			if (MailboxState_getRecent(N))
1668 				showrecent = TRUE;
1669 
1670 			newflags = MailboxState_flags(N);
1671 			if (! MATCH(newflags,oldflags))
1672 				showflags = TRUE;
1673 			g_free(newflags);
1674 		}
1675 		g_free(oldflags);
1676 	}
1677 
1678 	// command specific overrides
1679 	switch (self->command_type) {
1680 		case IMAP_COMM_EXAMINE:
1681 		case IMAP_COMM_SELECT:
1682 		case IMAP_COMM_SEARCH:
1683 		case IMAP_COMM_SORT:
1684 			showexists = showrecent = TRUE;
1685 		break;
1686 
1687 		default:
1688 			// ok show them if needed
1689 		break;
1690 	}
1691 
1692 	// never decrease without first sending expunge !!
1693 	if (N) {
1694 		if (showexists && MailboxState_getExists(N))
1695 			dbmail_imap_session_buff_printf(self, "* %u EXISTS\r\n", MailboxState_getExists(N));
1696 
1697 		if (showrecent && MailboxState_getRecent(N))
1698 			dbmail_imap_session_buff_printf(self, "* %u RECENT\r\n", MailboxState_getRecent(N));
1699 
1700 		mailbox_notify_expunge(self, N);
1701 
1702 		if (showflags) {
1703 			char *flags = MailboxState_flags(N);
1704 			dbmail_imap_session_buff_printf(self, "* FLAGS (%s)\r\n", flags);
1705 			dbmail_imap_session_buff_printf(self, "* OK [PERMANENTFLAGS (%s \\*)] Flags allowed.\r\n", flags);
1706 			g_free(flags);
1707 		}
1708 
1709 		mailbox_notify_fetch(self, N);
1710 	}
1711 
1712 	return 0;
1713 }
1714 
dbmail_imap_session_mbxinfo_lookup(ImapSession * self,uint64_t mailbox_id)1715 MailboxState_T dbmail_imap_session_mbxinfo_lookup(ImapSession *self, uint64_t mailbox_id)
1716 {
1717 	MailboxState_T M = NULL;
1718 	uint64_t *id;
1719 	if (self->mailbox && self->mailbox->mbstate && (MailboxState_getId(self->mailbox->mbstate) == mailbox_id)) {
1720 		// selected state
1721 		M = self->mailbox->mbstate;
1722 	} else {
1723 		M = (MailboxState_T)g_tree_lookup(self->mbxinfo, &mailbox_id);
1724 		if (! M) {
1725 			id = mempool_pop(small_pool, sizeof(uint64_t));
1726 			*id = mailbox_id;
1727 			M = MailboxState_new(self->pool, mailbox_id);
1728 			g_tree_replace(self->mbxinfo, id, M);
1729 		} else {
1730 			unsigned newseq = 0, oldseq = 0;
1731 			unsigned newexists = 0, oldexists = 0;
1732 			MailboxState_T N = NULL;
1733 			N = MailboxState_new(self->pool, 0);
1734 			MailboxState_setId(N, mailbox_id);
1735 			oldseq = MailboxState_getSeq(M);
1736 			newseq = MailboxState_getSeq(N);
1737 			oldexists = MailboxState_getExists(M);
1738 			MailboxState_free(&N);
1739 			if (oldseq < newseq) {
1740 				id = mempool_pop(small_pool, sizeof(uint64_t));
1741 				*id = mailbox_id;
1742 				M = MailboxState_new(self->pool, mailbox_id);
1743 				newexists = MailboxState_getExists(M);
1744 				MailboxState_setExists(M, max(oldexists, newexists));
1745 				g_tree_replace(self->mbxinfo, id, M);
1746 			}
1747 		}
1748 
1749 
1750 	}
1751 
1752 	assert(M);
1753 
1754 	return M;
1755 }
1756 
dbmail_imap_session_set_state(ImapSession * self,ClientState_T state)1757 int dbmail_imap_session_set_state(ImapSession *self, ClientState_T state)
1758 {
1759 	ClientState_T current;
1760 
1761 	PLOCK(self->lock);
1762 	current = self->state;
1763 	PUNLOCK(self->lock);
1764 
1765 	if ((current == state) || (current == CLIENTSTATE_QUIT_QUEUED))
1766 		return 1;
1767 
1768 	switch (state) {
1769 		case CLIENTSTATE_ERROR:
1770 			assert(self->ci);
1771 			if (self->ci->wev) event_del(self->ci->wev);
1772 			// fall-through...
1773 
1774 		case CLIENTSTATE_LOGOUT:
1775 			assert(self->ci);
1776 			if (self->ci->rev) event_del(self->ci->rev);
1777 			break;
1778 
1779 		case CLIENTSTATE_AUTHENTICATED:
1780 			// change from login_timeout to main timeout
1781 			assert(self->ci);
1782 			TRACE(TRACE_DEBUG,"[%p] set timeout to [%d]", self, server_conf->timeout);
1783 			self->ci->timeout.tv_sec = server_conf->timeout;
1784 			Capa_remove(self->capa, "AUTH=login");
1785 			Capa_remove(self->capa, "AUTH=CRAM-MD5");
1786 
1787 			break;
1788 
1789 		default:
1790 			break;
1791 	}
1792 
1793 	TRACE(TRACE_DEBUG,"[%p] state [%d]->[%d]", self, current, state);
1794 	PLOCK(self->lock);
1795 	self->state = state;
1796 	PUNLOCK(self->lock);
1797 
1798 	return 0;
1799 }
1800 
_do_expunge(uint64_t * id,ImapSession * self)1801 static gboolean _do_expunge(uint64_t *id, ImapSession *self)
1802 {
1803 	MessageInfo *msginfo = g_tree_lookup(MailboxState_getMsginfo(self->mailbox->mbstate), id);
1804 	assert(msginfo);
1805 
1806 	if (! msginfo->flags[IMAP_FLAG_DELETED]) return FALSE;
1807 
1808 	if (db_exec(self->c, "UPDATE %smessages SET status=%d WHERE message_idnr=%" PRIu64 " ", DBPFX, MESSAGE_STATUS_DELETE, *id) == DM_EQUERY)
1809 		return TRUE;
1810 
1811 	return notify_expunge(self, id);
1812 }
1813 
dbmail_imap_session_mailbox_expunge(ImapSession * self,const char * set,uint64_t * modseq)1814 int dbmail_imap_session_mailbox_expunge(ImapSession *self, const char *set, uint64_t *modseq)
1815 {
1816 	uint64_t mailbox_size;
1817 	int i;
1818 	GList *ids;
1819 	GTree *uids = NULL;
1820 	MailboxState_T M = self->mailbox->mbstate;
1821 
1822 	if (! (i = g_tree_nnodes(MailboxState_getIds(M))))
1823 		return DM_SUCCESS;
1824 
1825 	if (db_get_mailbox_size(self->mailbox->id, 1, &mailbox_size) == DM_EQUERY)
1826 		return DM_EQUERY;
1827 
1828 	if (set) {
1829 		uids = dbmail_mailbox_get_set(self->mailbox, set, self->use_uid);
1830 		ids = g_tree_keys(uids);
1831 	} else {
1832 		ids = g_tree_keys(MailboxState_getIds(M));
1833 	}
1834 
1835 	ids = g_list_reverse(ids);
1836 
1837 	if (ids) {
1838 		self->c = db_con_get();
1839 		db_begin_transaction(self->c);
1840 		g_list_foreach(ids, (GFunc) _do_expunge, self);
1841 		db_commit_transaction(self->c);
1842 		db_con_close(self->c);
1843 		self->c = NULL;
1844 		g_list_free(g_list_first(ids));
1845 	}
1846 
1847 	if (uids)
1848 		g_tree_destroy(uids);
1849 
1850 	*modseq = 0;
1851 	if (i > g_tree_nnodes(MailboxState_getIds(M))) {
1852 		*modseq = db_mailbox_seq_update(self->mailbox->id, 0);
1853 		if (! dm_quota_user_dec(self->userid, mailbox_size))
1854 			return DM_EQUERY;
1855 	}
1856 
1857 	return 0;
1858 }
1859 
1860 /*****************************************************************************
1861  *
1862  *
1863  *   bodyfetch
1864  *
1865  *
1866  ****************************************************************************/
_body_fetch_free(body_fetch * bodyfetch,gpointer data)1867 static void _body_fetch_free(body_fetch *bodyfetch, gpointer data)
1868 {
1869 	ImapSession *self = (ImapSession *)data;
1870 	if (! bodyfetch) return;
1871 	if (bodyfetch->names) {
1872 		g_list_free(g_list_first(bodyfetch->names));
1873 		bodyfetch->names = NULL;
1874 	}
1875 
1876 	if (bodyfetch->hdrnames) g_free(bodyfetch->hdrnames);
1877 	if (bodyfetch->hdrplist) g_free(bodyfetch->hdrplist);
1878 	if (bodyfetch->headers) {
1879 		g_tree_destroy(bodyfetch->headers);
1880 		bodyfetch->headers = NULL;
1881 	}
1882 	mempool_push(self->pool, bodyfetch, sizeof(body_fetch));
1883 }
1884 
dbmail_imap_session_bodyfetch_free(ImapSession * self)1885 void dbmail_imap_session_bodyfetch_free(ImapSession *self)
1886 {
1887 	List_T first, head = p_list_first(self->fi->bodyfetch);
1888 	first = head;
1889 	while (head) {
1890 		body_fetch *bodyfetch = p_list_data(head);
1891 		_body_fetch_free(bodyfetch, self);
1892 		head = p_list_next(head);
1893 	}
1894 	p_list_free(&first);
1895 	return;
1896 }
1897 
1898 
1899 /* local defines */
1900 #define NORMPAR 1
1901 #define SQUAREPAR 2
1902 #define NOPAR 0
1903 
1904 /*
1905  * imap4_tokenizer_main()
1906  *
1907  * imap command tokenizer
1908  *
1909  * builds an dimensional array of strings containing arguments based upon
1910  * strings on cmd line specified by {##}\0
1911  * (\r\n had been removed from string)
1912  *
1913  * normal/square parentheses have special meaning:
1914  * '(body [all header])' will result in the following array:
1915  * [0] = '('
1916  * [1] = 'body'
1917  * [2] = '['
1918  * [3] = 'all'
1919  * [4] = 'header'
1920  * [5] = ']'
1921  * [6] = ')'
1922  *
1923  * quoted strings are those enclosed by double quotation marks and returned as a single argument
1924  * WITHOUT the enclosing quotation marks
1925  *
1926  * parentheses loose their special meaning if inside (double)quotation marks;
1927  * data should be 'clarified' (see clarify_data() function below)
1928  *
1929  * The returned array will be NULL-terminated.
1930  * Will return NULL upon errors.
1931  */
1932 
imap4_tokenizer_main(ImapSession * self,const char * buffer)1933 int imap4_tokenizer_main(ImapSession *self, const char *buffer)
1934 {
1935 	int inquote = 0, quotestart = 0;
1936 	int nnorm = 0, nsquare = 0, paridx = 0, argstart = 0;
1937 	unsigned int i = 0;
1938 	size_t max;
1939 	char parlist[MAX_LINESIZE];
1940 	char *s, *lastchar;
1941 
1942 	assert(buffer);
1943 
1944 	s = (char *)buffer;
1945 	max = strlen(s);
1946 
1947 	if (max < 1)
1948 		goto finalize;
1949 
1950 
1951 //	assert(max <= MAX_LINESIZE);
1952 
1953 	/* find the arguments */
1954 	paridx = 0;
1955 	parlist[paridx] = NOPAR;
1956 
1957 	inquote = 0;
1958 
1959 	// if we're not fetching string-literals it's safe to strip NL
1960 	if (self->ci->rbuff_size) {
1961 		assert(max <= self->ci->rbuff_size);
1962 
1963 		if (! self->args[self->args_idx])
1964 			self->args[self->args_idx] = p_string_new(self->pool, "");
1965 
1966 		p_string_append_len(self->args[self->args_idx], buffer, max);
1967 		self->ci->rbuff_size -= max;
1968 		if (self->ci->rbuff_size == 0) {
1969 			self->args_idx++; // move on to next token
1970 			TRACE(TRACE_DEBUG, "string literal complete. last-char [%c]", s[max-1]);
1971 		}
1972 
1973 		return 0;
1974 
1975 	} else {
1976 		g_strchomp(s);
1977 		max = strlen(s);
1978 	}
1979 
1980 	TRACE(TRACE_DEBUG,"[%p] tokenize [%" PRIu64 "/%" PRIu64 "] [%s]", self,
1981 			(uint64_t)max, (uint64_t)self->ci->rbuff_size, s);
1982 
1983 	if (self->args[0]) {
1984 		if (MATCH(s, "*")) {
1985 			/* cancel the authentication exchange */
1986 			return -1;
1987 		}
1988 
1989 		if (MATCH(p_string_str(self->args[0]),"LOGIN")) {
1990 			uint64_t len;
1991 			char *tmp = dm_base64_decode(s, &len);
1992 			if (! tmp) {
1993 				return -1;
1994 			}
1995 			self->args[self->args_idx++] = p_string_new(self->pool, tmp);
1996 			g_free(tmp);
1997 
1998 			if (self->args_idx == 3) {
1999 				/* got password */
2000 				goto finalize;
2001 			} else if (self->args_idx == 2) {
2002 				/* got username, ask for password */
2003 				dbmail_imap_session_prompt(self,"password");
2004 				return 0;
2005 			}
2006 		} else if (MATCH(p_string_str(self->args[0]),"CRAM-MD5")) {
2007 			if (self->args_idx == 1) {
2008 				/* decode and store the response */
2009 				if (! Cram_decode(self->ci->auth, s)) {
2010 					Cram_free(&self->ci->auth);
2011 					return -1;
2012 				}
2013 				self->args_idx++;
2014 				goto finalize; // done
2015 			}
2016 		}
2017 	}
2018 
2019 	for (i = 0; (i < max) && s[i] && (self->args_idx < MAX_ARGS - 1); i++) {
2020 		/* check quotes */
2021 		if ((s[i] == '"') && ((i > 0 && s[i - 1] != '\\') || i == 0)) {
2022 			if (inquote) {
2023 				/* quotation end, treat quoted string as argument */
2024 				self->args[self->args_idx] = p_string_new(self->pool, "");
2025 				p_string_append_len(self->args[self->args_idx], &s[quotestart + 1], i - quotestart - 1);
2026 				TRACE(TRACE_DEBUG, "arg[%" PRIu64 "] [%s]", self->args_idx, p_string_str(self->args[self->args_idx]));
2027 				self->args_idx++;
2028 				inquote = 0;
2029 			} else {
2030 				inquote = 1;
2031 				quotestart = i;
2032 			}
2033 			continue;
2034 		}
2035 
2036 		if (inquote) continue;
2037 
2038 		//if strchr("[]()",s[i]) {
2039 		if (s[i] == '(' || s[i] == ')' || s[i] == '[' || s[i] == ']') {
2040 			switch (s[i]) {
2041 				/* check parenthese structure */
2042 				case ')':
2043 
2044 				if (paridx < 0 || parlist[paridx] != NORMPAR)
2045 					paridx = -1;
2046 				else {
2047 					nnorm--;
2048 					paridx--;
2049 				}
2050 
2051 				break;
2052 
2053 				case ']':
2054 
2055 				if (paridx < 0 || parlist[paridx] != SQUAREPAR)
2056 					paridx = -1;
2057 				else {
2058 					paridx--;
2059 					nsquare--;
2060 				}
2061 
2062 				break;
2063 
2064 				case '(':
2065 
2066 				parlist[++paridx] = NORMPAR;
2067 				nnorm++;
2068 
2069 				break;
2070 
2071 				case '[':
2072 
2073 				parlist[++paridx] = SQUAREPAR;
2074 				nsquare++;
2075 
2076 				break;
2077 			}
2078 
2079 			if (paridx < 0) return -1; /* error in parenthesis structure */
2080 
2081 			/* add this parenthesis to the arg list and continue */
2082 			self->args[self->args_idx] = p_string_new(self->pool, "");
2083 			p_string_printf(self->args[self->args_idx], "%c", s[i]);
2084 			TRACE(TRACE_DEBUG, "arg[%" PRIu64 "] [%s]", self->args_idx, p_string_str(self->args[self->args_idx]));
2085 			self->args_idx++;
2086 			continue;
2087 		}
2088 
2089 		if (s[i] == ' ') continue;
2090 
2091 		/* check for {number}\0 */
2092 		if (s[i] == '{') {
2093 			unsigned long int octets = strtoul(&s[i + 1], &lastchar, 10);
2094 
2095 			/* only continue if the number is followed by '}\0' */
2096 			if (
2097 					(*lastchar == '}' && *(lastchar + 1) == '\0') ||
2098 					(*lastchar == '+' && *(lastchar + 1) == '}' && *(lastchar + 2) == '\0')
2099 			   ) {
2100 				Field_T maxsize;
2101 				unsigned long maxoctets = 0;
2102 				config_get_value("MAX_MESSAGE_SIZE", "IMAP", maxsize);
2103 				if (strlen(maxsize)) {
2104 					maxoctets = strtoul(maxsize, NULL, 0);
2105 					TRACE(TRACE_DEBUG, "using MAX_MESSAGE_SIZE [%s -> %" PRIu64 " octets]",
2106 							maxsize, (uint64_t)maxoctets);
2107 				}
2108 
2109 				if ((maxoctets > 0) && (octets > maxoctets)) {
2110 					dbmail_imap_session_buff_printf(self,
2111 							"%s NO %s failed: message size too large\r\n",
2112 							self->tag, self->command);
2113 					self->command_state = TRUE;
2114 				} else {
2115 					self->ci->rbuff_size += octets;
2116 					if (*lastchar == '}')
2117 						dbmail_imap_session_buff_printf(self, "+ OK\r\n");
2118 				}
2119 				dbmail_imap_session_buff_flush(self);
2120 				return 0;
2121 			}
2122 		}
2123 		/* at an argument start now, walk on until next delimiter
2124 		 * and save argument
2125 		 */
2126 		for (argstart = i; i < strlen(s) && !strchr(" []()", s[i]); i++) {
2127 			if (s[i] == '"') {
2128 				if (s[i - 1] == '\\')
2129 					continue;
2130 				else
2131 					break;
2132 			}
2133 		}
2134 
2135 		self->args[self->args_idx] = p_string_new(self->pool, "");
2136 		p_string_append_len(self->args[self->args_idx], &s[argstart], i - argstart);
2137 		TRACE(TRACE_DEBUG, "arg[%" PRIu64 "] [%s]", self->args_idx, p_string_str(self->args[self->args_idx]));
2138 		self->args_idx++;
2139 		i--;		/* walked one too far */
2140 	}
2141 
2142 	if (paridx != 0) return -1; /* error in parenthesis structure */
2143 
2144 finalize:
2145 	if (self->args_idx == 1) {
2146 		if (Capa_match(self->preauth_capa, "AUTH=LOGIN") && MATCH(p_string_str(self->args[0]),"LOGIN")) {
2147 			TRACE(TRACE_DEBUG, "[%p] prompt for authenticate tokens", self);
2148 
2149 			/* ask for username */
2150 			dbmail_imap_session_prompt(self,"username");
2151 			return 0;
2152 		} else if (Capa_match(self->preauth_capa, "AUTH=CRAM-MD5") && MATCH(p_string_str(self->args[0]),"CRAM-MD5")) {
2153 			const gchar *s;
2154 			gchar *t;
2155 			self->ci->auth = Cram_new();
2156 			s = Cram_getChallenge(self->ci->auth);
2157 			t = (char *)g_base64_encode((const guchar *)s, strlen(s));
2158 			dbmail_imap_session_buff_printf(self, "+ %s\r\n", t);
2159 			dbmail_imap_session_buff_flush(self);
2160 			g_free(t);
2161 
2162 			return 0;
2163 		}
2164 	}
2165 
2166 
2167 	TRACE(TRACE_DEBUG, "[%p] tag: [%s], command: [%s], [%" PRIu64 "] args", self, self->tag, self->command, self->args_idx);
2168 	self->args[self->args_idx] = NULL;	/* terminate */
2169 
2170 #ifdef DEBUG
2171 	for (i = 0; i<=self->args_idx && self->args[i]; i++) {
2172 		TRACE(TRACE_DEBUG, "[%p] arg[%d]: '%s'\n", self, i, p_string_str(self->args[i]));
2173 	}
2174 #endif
2175 	self->args_idx = 0;
2176 
2177 	return 1;
2178 }
2179 
2180 #undef NOPAR
2181 #undef NORMPAR
2182 #undef RIGHTPAR
2183 
2184 
2185 
2186