1 /*
2  Copyright (c) 2004-2012 NFG Net Facilities Group BV support@nfg.nl
3 
4   This program is free software; you can redistribute it and/or
5   modify it under the terms of the GNU General Public License
6   as published by the Free Software Foundation; either
7   version 2 of the License, or (at your option) any later
8   version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 
20 /**
21  *
22  * implements DbmailMessage object
23  */
24 
25 #include "dbmail.h"
26 
27 extern DBParam_T db_params;
28 #define DBPFX db_params.pfx
29 #define DBMAIL_TEMPMBOX "INBOX"
30 #define THIS_MODULE "message"
31 
32 
33 /*
34  * used for debugging message de/re-construction
35  */
36 #ifdef DEBUG_MESSAGE
37 #define dprint(fmt, args...) TRACE(TRACE_DEBUG, fmt, ##args); printf(fmt, ##args)
38 #endif
39 
40 #ifndef dprint
41 #define DPRINT 0
42 #define dprint(fmt, args...)
43 #else
44 #define DPRINT 1
45 #endif
46 
47 static void _header_cache(const char *, const char *, gpointer);
48 
49 static gboolean _header_insert(uint64_t physmessage_id, uint64_t headername_id, uint64_t headervalue_id);
50 static int _header_name_get_id(const DbmailMessage *self, const char *header, uint64_t *id);
51 static int _header_value_get_id(const char *value, const char *sortfield, const char *datefield, uint64_t *id);
52 
53 static DbmailMessage * _retrieve(DbmailMessage *self, const char *query_template);
54 static int _message_insert(DbmailMessage *self,
55 		uint64_t user_idnr,
56 		const char *mailbox,
57 		const char *unique_id);
58 
59 
60 /* general mime utils (missing from gmime?) */
61 
find_end_of_header(const char * h)62 unsigned find_end_of_header(const char *h)
63 {
64 	gchar c, p1 = 0, p2 = 0;
65 	unsigned i = 0;
66 	size_t l;
67 
68 	assert(h);
69 
70 	l  = strlen(h);
71 
72 	while (h++ && i<l) {
73 		i++;
74 		c = *h;
75 		if (c == '\n' && ((p1 == '\n') || (p1 == '\r' && p2 == '\n'))) {			if (l > i)
76 				i++;
77 			break;
78 		}
79 		p2 = p1;
80 		p1 = c;
81 	}
82 	return i;
83 }
84 
g_mime_object_get_body(const GMimeObject * object)85 gchar * g_mime_object_get_body(const GMimeObject *object)
86 {
87 	gchar *s = NULL, *b = NULL;
88         unsigned i;
89 	size_t l;
90 
91 	g_return_val_if_fail(object != NULL, NULL);
92 
93 	s = g_mime_object_to_string(GMIME_OBJECT(object));
94 	assert(s);
95 
96 	i = find_end_of_header(s);
97 	if (i >= strlen(s)) {
98 		g_free(s);
99 		return g_strdup("");
100 	}
101 
102 	b = s+i;
103 	l = strlen(b);
104 	memmove(s,b,l);
105 	s[l] = '\0';
106 	s = g_realloc(s, l+1);
107 
108 	return s;
109 }
110 
blob_exists(const char * buf,const char * hash)111 static uint64_t blob_exists(const char *buf, const char *hash)
112 {
113 	volatile uint64_t id = 0;
114 	volatile uint64_t id_old = 0;
115 	size_t l;
116 	assert(buf);
117 	Connection_T c; PreparedStatement_T s; ResultSet_T r;
118 	char blob_cmp[DEF_FRAGSIZE];
119 	memset(blob_cmp, 0, sizeof(blob_cmp));
120 
121 	l = strlen(buf);
122 	c = db_con_get();
123 	TRY
124 		if (db_params.db_driver == DM_DRIVER_ORACLE  && l > DM_ORA_MAX_BYTES_LOB_CMP) {
125 			db_begin_transaction(c);
126 			/** XXX Due to specific Oracle behavior and limitation of
127 			 * libzdb methods we can't perform direct comparision lob data
128 			 * with some constant more then 4000 chars. So the only way to
129 			 * avoid data duplication - insert record and check if it alread
130 			 * exists in table. If it exists - rollback the transaction */
131 			s = db_stmt_prepare(c, "INSERT INTO %smimeparts (hash, data, %ssize%s) VALUES (?, ?, ?)",
132 				DBPFX, db_get_sql(SQL_ESCAPE_COLUMN), db_get_sql(SQL_ESCAPE_COLUMN));
133 			db_stmt_set_str(s, 1, hash);
134 			db_stmt_set_blob(s, 2, buf, l);
135 			db_stmt_set_int(s, 3, l);
136 			db_stmt_exec(s);
137 			id = db_get_pk(c, "mimeparts");
138 			s = db_stmt_prepare(c, "SELECT a.id, b.id FROM dbmail_mimeparts a INNER JOIN "
139 					"%smimeparts b ON a.hash=b.hash AND DBMS_LOB.COMPARE(a.data, b.data) = 0 "
140 					" AND a.id<>b.id AND b.id=?", DBPFX);
141 			db_stmt_set_u64(s, 1, id);
142 			r = db_stmt_query(s);
143 			if (db_result_next(r))
144 				id_old = db_result_get_u64(r,0);
145 			if (id_old) {
146 				//  BLOB already exists - rollback insert
147 				id = id_old;
148 				db_rollback_transaction(c);
149 			} else {
150 				db_commit_transaction(c);
151 			}
152 		} else {
153 			snprintf(blob_cmp, DEF_FRAGSIZE-1, db_get_sql(SQL_COMPARE_BLOB), "data");
154 			s = db_stmt_prepare(c,"SELECT id FROM %smimeparts WHERE hash=? AND %ssize%s=? AND %s",
155 					DBPFX,db_get_sql(SQL_ESCAPE_COLUMN), db_get_sql(SQL_ESCAPE_COLUMN),
156 					blob_cmp);
157 			db_stmt_set_str(s,1,hash);
158 			db_stmt_set_u64(s,2,l);
159 			db_stmt_set_blob(s,3,buf,l);
160 			r = db_stmt_query(s);
161 			if (db_result_next(r))
162 				id = db_result_get_u64(r,0);
163 		}
164 	CATCH(SQLException)
165 		LOG_SQLERROR;
166 		if (db_params.db_driver == DM_DRIVER_ORACLE)
167 			db_rollback_transaction(c);
168 	FINALLY
169 		db_con_close(c);
170 	END_TRY;
171 
172 	return id;
173 }
174 
blob_insert(const char * buf,const char * hash)175 static uint64_t blob_insert(const char *buf, const char *hash)
176 {
177 	Connection_T c; PreparedStatement_T s; ResultSet_T r;
178 	size_t l;
179 	volatile uint64_t id = 0;
180 	char *frag = db_returning("id");
181 
182 	assert(buf);
183 	l = strlen(buf);
184 
185 	c = db_con_get();
186 	TRY
187 		db_begin_transaction(c);
188 		s = db_stmt_prepare(c, "INSERT INTO %smimeparts (hash, data, %ssize%s) VALUES (?, ?, ?) %s",
189 				DBPFX, db_get_sql(SQL_ESCAPE_COLUMN), db_get_sql(SQL_ESCAPE_COLUMN), frag);
190 		db_stmt_set_str(s, 1, hash);
191 		db_stmt_set_blob(s, 2, buf, l);
192 		db_stmt_set_int(s, 3, l);
193 		if (db_params.db_driver == DM_DRIVER_ORACLE) {
194 			db_stmt_exec(s);
195 			id = db_get_pk(c, "mimeparts");
196 		} else {
197 			r = db_stmt_query(s);
198 			id = db_insert_result(c,r);
199 		}
200 		db_commit_transaction(c);
201 	CATCH(SQLException)
202 		LOG_SQLERROR;
203 		db_rollback_transaction(c);
204 	FINALLY
205 		db_con_close(c);
206 	END_TRY;
207 
208 	TRACE(TRACE_DEBUG,"inserted id [%" PRIu64 "]", id);
209 	g_free(frag);
210 
211 	return id;
212 }
213 
register_blob(DbmailMessage * m,uint64_t id,gboolean is_header)214 static int register_blob(DbmailMessage *m, uint64_t id, gboolean is_header)
215 {
216 	Connection_T c; volatile gboolean t = FALSE;
217 	c = db_con_get();
218 
219 	if (m->part_depth > MAX_MIME_DEPTH) {
220 		TRACE(TRACE_WARNING, "MIME part depth exceeds allowed limit. You should recompile "
221 				"with CFLAGS+=-DMAX_MIME_DEPTH=<int> where <int> greater than [%d]",
222 				m->part_depth);
223 	}
224 
225 	TRY
226 		db_begin_transaction(c);
227 		t = db_exec(c, "INSERT INTO %spartlists (physmessage_id, is_header, part_key, part_depth, part_order, part_id) "
228 				"VALUES (%" PRIu64 ",%d,%d,%d,%d,%" PRIu64 ")", DBPFX,
229 				dbmail_message_get_physid(m), is_header, m->part_key, m->part_depth, m->part_order, id);
230 		db_commit_transaction(c);
231 	CATCH(SQLException)
232 		LOG_SQLERROR;
233 		db_rollback_transaction(c);
234 	FINALLY
235 		db_con_close(c);
236 	END_TRY;
237 
238 	return t;
239 }
240 
blob_store(const char * buf)241 static uint64_t blob_store(const char *buf)
242 {
243 	uint64_t id;
244 	char hash[FIELDSIZE];
245 
246 	if (! buf) return 0;
247 
248 	memset(hash, 0, sizeof(hash));
249 	if (dm_get_hash_for_string(buf, hash))
250 		return 0;
251 
252 	// store this message fragment
253 	if ((id = blob_exists(buf, (const char *)hash)) != 0) {
254 		return id;
255 	}
256 
257 	if ((id = blob_insert(buf, (const char *)hash)) != 0) {
258 		return id;
259 	}
260 
261 	return 0;
262 }
263 
store_blob(DbmailMessage * m,const char * buf,gboolean is_header)264 static int store_blob(DbmailMessage *m, const char *buf, gboolean is_header)
265 {
266 	uint64_t id;
267 
268 	if (! buf) return 0;
269 
270 	if (is_header) {
271 		m->part_key++;
272 		m->part_order=0;
273 	}
274 
275 	dprint("<blob is_header=\"%d\" part_depth=\"%d\" part_key=\"%d\" part_order=\"%d\">\n%s\n</blob>\n",
276 			is_header, m->part_depth, m->part_key, m->part_order, buf);
277 
278 	if (! (id = blob_store(buf)))
279 		return DM_EQUERY;
280 
281 	// register this message fragment
282 	if (! register_blob(m, id, is_header))
283 		return DM_EQUERY;
284 
285 	m->part_order++;
286 
287 	return 0;
288 
289 }
290 
find_type_header(const char * s)291 static char *find_type_header(const char *s)
292 {
293 	GString *header;
294 	char *rest, *h = NULL;
295 	int i=0;
296 
297 	rest = g_strcasestr(s, "\nContent-type: ");
298 	if (! rest) {
299 		if ((g_ascii_strncasecmp(s, "Content-type: ", 14)) == 0)
300 			rest = (char *)s;
301 	}
302 	if (! rest) return NULL;
303 
304 	header = g_string_new("");
305 
306 	i = 0;
307 	while (rest[i]) {
308 		if (rest[i] == ':') break;
309 		i++;
310 	}
311 	i++;
312 
313 	while (rest[i]) {
314 		if (((ISLF(rest[i])) || (ISCR(rest[i]))) && (!isspace(rest[i+1]))) {
315 			break;
316 		}
317 		g_string_append_c(header,rest[i++]);
318 	}
319 	h = header->str;
320 	g_string_free(header,FALSE);
321 	header = NULL;
322 	return g_strstrip(h);
323 }
324 
find_type(const char * s)325 static GMimeContentType *find_type(const char *s)
326 {
327 	GMimeContentType *type = NULL;
328 	char *header = find_type_header(s);
329 	if (! header)
330 		return NULL;
331 	type = g_mime_content_type_new_from_string(header);
332 	g_free(header);
333 	return type;
334 }
335 
336 #define MAX_MIME_DEPTH 64
337 #define MAX_MIME_BLEN 128
338 
find_boundary(const char * s,char * boundary)339 static gboolean find_boundary(const char *s, char *boundary)
340 {
341 	const gchar *param;
342 	GMimeContentType *type = find_type(s);
343 	if (! type)
344 		return false;
345 	param = g_mime_content_type_get_parameter(type, "boundary");
346 	if (! param) {
347 		g_object_unref(type);
348 		return false;
349 	}
350 	memset(boundary, 0, MAX_MIME_BLEN);
351 	strncpy(boundary, param, MAX_MIME_BLEN-1);
352 	g_object_unref(type);
353 	return true;
354 }
355 
_mime_retrieve(DbmailMessage * self)356 static DbmailMessage * _mime_retrieve(DbmailMessage *self)
357 {
358 	PreparedStatement_T stmt;
359 	Connection_T c;
360        	ResultSet_T r;
361 	char internal_date[SQL_INTERNALDATE_LEN];
362 	GMimeContentType *mimetype = NULL;
363 	volatile int prevdepth, depth = 0, row = 0;
364 	volatile int t = FALSE;
365 	volatile gboolean got_boundary = FALSE, prev_boundary = FALSE, is_header = TRUE, prev_header, finalized=FALSE;
366 	volatile gboolean prev_is_message = FALSE, is_message = FALSE;
367 	volatile String_T m = NULL, n = NULL;
368 	const void *blob;
369 	Field_T frag;
370 
371 	assert(dbmail_message_get_physid(self));
372 	date2char_str("ph.internal_date", &frag);
373 	n = p_string_new(self->pool, "");
374 	p_string_printf(n,db_get_sql(SQL_ENCODE_ESCAPE), "data");
375 
376 	c = db_con_get();
377 	TRY
378 		char boundary[MAX_MIME_BLEN];
379 		char blist[MAX_MIME_DEPTH+1][MAX_MIME_BLEN];
380 
381 		memset(&boundary, 0, sizeof(boundary));
382 		memset(&blist, 0, sizeof(blist));
383 
384 		stmt = db_stmt_prepare(c,
385 			       	"SELECT l.part_key,l.part_depth,l.part_order,l.is_header,%s,%s "
386 				"FROM %smimeparts p "
387 				"JOIN %spartlists l ON p.id = l.part_id "
388 				"JOIN %sphysmessage ph ON ph.id = l.physmessage_id "
389 				"WHERE l.physmessage_id = ? ORDER BY l.part_key, l.part_order ASC, l.part_depth DESC",
390 				frag, p_string_str(n), DBPFX, DBPFX, DBPFX);
391 		db_stmt_set_u64(stmt, 1, self->id);
392 		r = db_stmt_query(stmt);
393 
394 		m = p_string_new(self->pool, "");
395 
396 		row = 0;
397 		while (db_result_next(r)) {
398 			int l;
399 #if DPRINT
400 			int order;
401 			int key;
402 #endif
403 
404 			prevdepth	= depth;
405 			prev_header	= is_header;
406 #if DPRINT
407 			key		= db_result_get_int(r,0);
408 #endif
409 			depth		= db_result_get_int(r,1);
410 			if (depth > MAX_MIME_DEPTH) {
411 				TRACE(TRACE_WARNING, "MIME part depth exceeds allowed maximum [%d]",
412 						MAX_MIME_DEPTH);
413 				continue;
414 			}
415 
416 #if DPRINT
417 			order		= db_result_get_int(r,2);
418 #endif
419 			is_header	= db_result_get_bool(r,3);
420 			if (row == 0) {
421 				memset(internal_date, 0, sizeof(internal_date));
422 				g_strlcpy(internal_date, db_result_get(r,4), SQL_INTERNALDATE_LEN-1);
423 			}
424 			blob		= db_result_get_blob(r,5,&l);
425 			char *str = g_new0(char, l + 1);
426 			str = strncpy(str, blob, l);
427 
428 			if (is_header) {
429 				prev_boundary = got_boundary;
430 				prev_is_message = is_message;
431 				if ((mimetype = find_type(str))) {
432 					is_message = g_mime_content_type_is_type(mimetype, "message", "rfc822");
433 					g_object_unref(mimetype);
434 				}
435 			}
436 
437 			got_boundary = FALSE;
438 
439 			if (is_header && find_boundary(str, &boundary[0])) {
440 				got_boundary = TRUE;
441 				dprint("<boundary depth=\"%d\">%s</boundary>\n", depth, boundary);
442 				strncpy(blist[depth], boundary, MAX_MIME_BLEN-1);
443 			}
444 
445 			while ((prevdepth > 0) && (prevdepth-1 >= depth) && blist[prevdepth-1][0]) {
446 				dprint("\n--%s at %d -> %d--\n", blist[prevdepth-1], prevdepth, prevdepth-1);
447 				p_string_append_printf(m, "\n--%s--\n", blist[prevdepth-1]);
448 				memset(blist[prevdepth-1], 0, MAX_MIME_BLEN);
449 				prevdepth--;
450 				finalized=TRUE;
451 			}
452 
453 			if ((depth > 0) && (blist[depth-1][0]))
454 				strncpy(boundary, blist[depth-1], MAX_MIME_BLEN-1);
455 
456 			if (is_header){
457 				if (prev_header && depth>0 && !prev_is_message) {
458 					dprint("--%s\n", boundary);
459 					p_string_append_printf(m, "--%s\n", boundary);
460 				}else if (!prev_header || prev_boundary) {
461 					dprint("\n--%s\n", boundary);
462 					p_string_append_printf(m, "\n--%s\n", boundary);
463 				}
464 			}
465 
466 			p_string_append_printf(m, "%s", str);
467 			dprint("<part is_header=\"%d\" depth=\"%d\" key=\"%d\" order=\"%d\">\n%s\n</part>\n",
468 				is_header, depth, key, order, str);
469 
470 			if (is_header)
471 				p_string_append_printf(m,"\n");
472 
473 			g_free(str);
474 			row++;
475 		}
476 
477 		if (row > 2 && boundary[0] && !finalized) {
478 			dprint("\n--%s-- final\n", boundary);
479 			p_string_append_printf(m, "\n--%s--\n", boundary);
480 			finalized=1;
481 		}
482 
483 
484 	CATCH(SQLException)
485 		LOG_SQLERROR;
486 		t = DM_EQUERY;
487 	FINALLY
488 		db_con_close(c);
489 	END_TRY;
490 
491 	if ((row == 0) || (t == DM_EQUERY)) {
492 		if (m) p_string_free(m, TRUE);
493 		p_string_free(n, TRUE);
494 		return NULL;
495 	}
496 
497 	self = dbmail_message_init_with_string(self,p_string_str(m));
498 	dbmail_message_set_internal_date(self, internal_date);
499 	p_string_free(m,TRUE);
500 	p_string_free(n,TRUE);
501 	return self;
502 }
503 
504 static gboolean store_mime_object(GMimeObject *parent, GMimeObject *object, DbmailMessage *m);
505 
store_head(GMimeObject * object,DbmailMessage * m)506 static int store_head(GMimeObject *object, DbmailMessage *m)
507 {
508 	int r;
509 	char *head = g_mime_object_get_headers(object);
510 	r = store_blob(m, head, 1);
511 	g_free(head);
512 	return r;
513 }
514 
store_body(GMimeObject * object,DbmailMessage * m)515 static int store_body(GMimeObject *object, DbmailMessage *m)
516 {
517 	int r;
518 	char *text = g_mime_object_get_body(object);
519 	if (! text) return 0;
520 	r = store_blob(m, text, 0);
521 	g_free(text);
522 	return r;
523 }
524 
store_mime_text(GMimeObject * object,DbmailMessage * m,gboolean skiphead)525 static gboolean store_mime_text(GMimeObject *object, DbmailMessage *m, gboolean skiphead)
526 {
527 	g_return_val_if_fail(GMIME_IS_OBJECT(object), TRUE);
528 	if (! skiphead && store_head(object, m) < 0) return TRUE;
529 	if(store_body(object, m) < 0) return TRUE;
530 	return FALSE;
531 }
532 
store_mime_multipart(GMimeObject * object,DbmailMessage * m,const GMimeContentType * content_type,gboolean skiphead)533 static gboolean store_mime_multipart(GMimeObject *object, DbmailMessage *m, const GMimeContentType *content_type, gboolean skiphead)
534 {
535 	const char *boundary;
536 	const char *preface = NULL, *postface = NULL;
537 	int n = 0, i, c;
538 
539 	g_return_val_if_fail(GMIME_IS_OBJECT(object), TRUE);
540 
541 	if (! skiphead && store_head(object,m) < 0) return TRUE;
542 
543 	boundary = g_mime_multipart_get_boundary((GMimeMultipart *)object);
544 	preface = g_mime_multipart_get_preface((GMimeMultipart *)object);
545 	postface = g_mime_multipart_get_postface((GMimeMultipart *)object);
546 
547 	if (g_mime_content_type_is_type(GMIME_CONTENT_TYPE(content_type), "multipart", "*") &&
548 			store_blob(m, preface, 0) < 0) return TRUE;
549 
550 	if (boundary) {
551 		m->part_depth++;
552 		n = m->part_order;
553 		m->part_order=0;
554 	}
555 
556 	c = g_mime_multipart_get_count((GMimeMultipart *)object);
557 	for (i=0; i<c; i++) {
558 		GMimeObject *part = g_mime_multipart_get_part((GMimeMultipart *)object, i);
559 		if (store_mime_object(object, part, m)) return TRUE;
560 	}
561 
562 	if (boundary) {
563 		n++;
564 		m->part_depth--;
565 		m->part_order++;
566 	}
567 
568 	if (g_mime_content_type_is_type(GMIME_CONTENT_TYPE(content_type), "multipart", "*") &&
569 			store_blob(m, postface, 0) < 0) return TRUE;
570 
571 
572 	return FALSE;
573 }
574 
store_mime_message(GMimeObject * object,DbmailMessage * m,gboolean skiphead)575 static gboolean store_mime_message(GMimeObject * object, DbmailMessage *m, gboolean skiphead)
576 {
577 	gboolean r;
578 	GMimeMessage *m2;
579 
580 	if (! skiphead && store_head(object, m) < 0) return TRUE;
581 
582 	m2 = g_mime_message_part_get_message(GMIME_MESSAGE_PART(object));
583 
584 	if (GMIME_IS_MESSAGE(m2))
585 		r = store_mime_object(object, GMIME_OBJECT(m2), m);
586 	else // fall-back
587 		r = store_mime_text(object, m, TRUE);
588 
589 	return r;
590 
591 }
592 
store_mime_object(GMimeObject * parent,GMimeObject * object,DbmailMessage * m)593 gboolean store_mime_object(GMimeObject *parent, GMimeObject *object, DbmailMessage *m)
594 {
595 	GMimeContentType *content_type;
596 	GMimeObject *mime_part;
597 	gboolean r = FALSE;
598 	gboolean skiphead = FALSE;
599 
600 	TRACE(TRACE_DEBUG, "parent [%p], object [%p], message->content [%p]", parent, object, m->content);
601 	g_return_val_if_fail(GMIME_IS_OBJECT(object), TRUE);
602 
603 	if (GMIME_IS_MESSAGE(object)) {
604 		dprint("\n<message>\n");
605 
606 		if(store_head(object,m) < 0) return TRUE;
607 
608 		// we need to skip the first (post-rfc822) mime-headers
609 		// of the mime_part because they are already included as
610 		// part of the rfc822 headers
611 		skiphead = TRUE;
612 
613 		g_mime_header_list_set_stream (GMIME_MESSAGE(object)->mime_part->headers, NULL);
614 		mime_part = g_mime_message_get_mime_part((GMimeMessage *)object);
615 	} else
616 		mime_part = object;
617 
618 	content_type = g_mime_object_get_content_type(mime_part);
619 
620 	if (g_mime_content_type_is_type(content_type, "multipart", "*")) {
621 		r = store_mime_multipart((GMimeObject *)mime_part, m, content_type, skiphead);
622 
623 	} else if (g_mime_content_type_is_type(content_type, "message","*")) {
624 		r = store_mime_message((GMimeObject *)mime_part, m, skiphead);
625 
626 	} else if (g_mime_content_type_is_type(content_type, "text","*")) {
627 		if (GMIME_IS_MESSAGE(object)) {
628 			if(store_body(object,m) < 0) r = TRUE;
629 		} else {
630 			r = store_mime_text((GMimeObject *)mime_part, m, skiphead);
631 		}
632 
633 	} else {
634 		r = store_mime_text((GMimeObject *)mime_part, m, skiphead);
635 	}
636 
637 	if (GMIME_IS_MESSAGE(object)) {
638 		dprint("\n</message>\n");
639 	}
640 
641 	return r;
642 }
643 
644 
dm_message_store(DbmailMessage * m)645 gboolean dm_message_store(DbmailMessage *m)
646 {
647 	return store_mime_object(NULL, (GMimeObject *)m->content, m);
648 }
649 
650 
651 /* Useful for debugging. Uncomment if/when needed.
652  *//*
653 static void dump_to_file(const char *filename, const char *buf)
654 {
655 	gint se;
656 	g_assert(filename);
657 	FILE *f = fopen(filename,"a");
658 	if (! f) {
659 		se=errno;
660 		TRACE(TRACE_DEBUG,"opening dumpfile failed [%s]", strerror(se));
661 		errno=se;
662 		return;
663 	}
664 	fprintf(f,"%s",buf);
665 	fclose(f);
666 }
667 */
668 
669 
670 /*  \brief create a new empty DbmailMessage struct
671  *  \return the DbmailMessage
672  */
673 
dbmail_message_new(Mempool_T pool)674 DbmailMessage * dbmail_message_new(Mempool_T pool)
675 {
676 	gboolean freepool = FALSE;
677 	if (! pool) {
678 		pool = mempool_open();
679 		freepool = TRUE;
680 	}
681 
682 	DbmailMessage *self = mempool_pop(pool, sizeof(DbmailMessage));
683 	self->pool = pool;
684 	self->freepool = freepool;
685 
686 	self->internal_date = time(NULL);
687 	self->envelope_recipient = p_string_new(self->pool, "");
688 
689 	/* provide quick case-insensitive header name searches */
690 	self->header_name = g_tree_new((GCompareFunc)g_ascii_strcasecmp);
691 
692 	/* provide quick case-sensitive header value searches */
693 	self->header_value = g_tree_new((GCompareFunc)strcmp);
694 
695 	/* internal cache: header_dict[headername.name] = headername.id */
696 	self->header_dict = g_hash_table_new_full((GHashFunc)g_str_hash,
697 			(GEqualFunc)g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free);
698 
699 	dbmail_message_set_class(self, DBMAIL_MESSAGE);
700 
701 	return self;
702 }
703 
dbmail_message_free(DbmailMessage * self)704 void dbmail_message_free(DbmailMessage *self)
705 {
706 	Mempool_T pool;
707 	gboolean freepool;
708 	if (! self)
709 		return;
710 
711 	if (self->content) {
712 		g_object_unref(self->content);
713 		self->content = NULL;
714 	}
715 
716 	if (self->stream) {
717 		g_object_unref(self->stream);
718 		self->stream = NULL;
719 	}
720 	if (self->crlf) {
721 		p_string_free(self->crlf, TRUE);
722 		self->crlf = NULL;
723 	}
724 
725 	p_string_free(self->envelope_recipient,TRUE);
726 	g_hash_table_destroy(self->header_dict);
727 	g_tree_destroy(self->header_name);
728 	g_tree_destroy(self->header_value);
729 
730 	self->id=0;
731 
732 	pool = self->pool;
733 	freepool = self->freepool;
734 	mempool_push(pool, self, sizeof(DbmailMessage));
735 	if (freepool)
736 		mempool_close(&pool);
737 }
738 
739 
740 /* \brief set the type flag for this DbmailMessage
741  * \param the DbmailMessage on which to set the flag
742  * \param type flag is either DBMAIL_MESSAGE or DBMAIL_MESSAGE_PART
743  * \return non-zero in case of error
744  */
dbmail_message_set_class(DbmailMessage * self,int klass)745 int dbmail_message_set_class(DbmailMessage *self, int klass)
746 {
747 	switch (klass) {
748 		case DBMAIL_MESSAGE:
749 		case DBMAIL_MESSAGE_PART:
750 			self->klass = klass;
751 			break;
752 		default:
753 			return 1;
754 			break;
755 	}
756 	return 0;
757 
758 }
759 
760 /* \brief accessor for the type flag
761  * \return the flag
762  */
dbmail_message_get_class(const DbmailMessage * self)763 int dbmail_message_get_class(const DbmailMessage *self)
764 {
765 	return self->klass;
766 }
767 
768 /* \brief initialize a previously created DbmailMessage using a GString
769  * \param the empty DbmailMessage
770  * \param char *content contains the raw message
771  * \return the filled DbmailMessage
772  */
dbmail_message_init_with_string(DbmailMessage * self,const char * str)773 DbmailMessage * dbmail_message_init_with_string(DbmailMessage *self, const char *str)
774 {
775 	char *buf, *crlf;
776 	GMimeObject *content;
777 	GMimeParser *parser;
778 #define FROMLINE 80
779 	char from[FROMLINE];
780 	size_t buflen = strlen(str);
781 
782 	assert(self->content == NULL);
783 
784 	memset(from, 0, sizeof(from));
785 
786 	if ((strncmp(str, "From ", 5) == 0) || (strncmp(str, " ", 1) == 0)) {
787 		/* don't use gmime's from scanner since body lines may begin with 'From ' */
788 		char *end;
789 		if ((end = g_strstr_len(str, FROMLINE, "\n"))) {
790 			g_strlcpy(from, str, FROMLINE);
791 			TRACE(TRACE_DEBUG, "From_ [%s]", from);
792 
793 			// skip broken first line if it starts with a ' '
794 			// we will still try to decode the contents to a date
795 			if (strncmp(str, " ", 1) == 0) {
796 				str = end+1;
797 			}
798 		}
799 	}
800 
801 	self->stream = g_mime_stream_mem_new();
802 	g_mime_stream_write(self->stream, str, buflen);
803 	g_mime_stream_reset(self->stream);
804 
805 	parser = g_mime_parser_new_with_stream(self->stream);
806 
807 
808 	content = GMIME_OBJECT(g_mime_parser_construct_message(parser));
809 	if (content) {
810 		g_object_unref(parser);
811 		dbmail_message_set_class(self, DBMAIL_MESSAGE);
812 		self->content = content;
813 		if (from[0])
814 			dbmail_message_set_internal_date(self, from);
815 	} else {
816 		content = GMIME_OBJECT(g_mime_parser_construct_part(parser));
817 		g_object_unref(parser);
818 		if (content) {
819 			dbmail_message_set_class(self, DBMAIL_MESSAGE_PART);
820 			self->content = content;
821 		}
822 	}
823 
824 	buf = dbmail_message_to_string(self);
825 	crlf = get_crlf_encoded(buf);
826 	self->crlf = p_string_new(self->pool, crlf);
827 	g_free(crlf);
828 	g_free(buf);
829 
830 	return self;
831 }
832 
dbmail_message_set_physid(DbmailMessage * self,uint64_t id)833 void dbmail_message_set_physid(DbmailMessage *self, uint64_t id)
834 {
835 	self->id = id;
836 }
837 
dbmail_message_get_physid(const DbmailMessage * self)838 uint64_t dbmail_message_get_physid(const DbmailMessage *self)
839 {
840 	return self->id;
841 }
842 
dbmail_message_set_internal_date(DbmailMessage * self,char * internal_date)843 void dbmail_message_set_internal_date(DbmailMessage *self, char *internal_date)
844 {
845 	self->internal_date = time(NULL);
846 	if (internal_date && strlen(internal_date)) {
847 		time_t dt;
848 	        if ((dt = g_mime_utils_header_decode_date(
849 						internal_date,
850 						&(self->internal_date_gmtoff)))) {
851 			self->internal_date = dt;
852 		}
853 		TRACE(TRACE_DEBUG, "internal_date [%s] [%ld] offset [%d]",
854 				internal_date,
855 				self->internal_date,
856 				self->internal_date_gmtoff);
857 	}
858 }
859 
860 /* thisyear is a workaround for some broken gmime version. */
dbmail_message_get_internal_date(const DbmailMessage * self,int thisyear)861 gchar * dbmail_message_get_internal_date(const DbmailMessage *self, int thisyear)
862 {
863 	char *res;
864 	struct tm gmt;
865 	assert(self->internal_date);
866 
867 	memset(&gmt,'\0', sizeof(struct tm));
868 	gmtime_r(&self->internal_date, &gmt);
869 
870 	/* override if the date is not sane */
871 	if (thisyear && ((gmt.tm_year + 1900) > (thisyear + 1)))
872 		gmt.tm_year = thisyear - 1900;
873 
874 	res = g_new0(char, TIMESTRING_SIZE+1);
875 	strftime(res, TIMESTRING_SIZE, "%Y-%m-%d %T", &gmt);
876 
877 	return res;
878 }
879 
dbmail_message_set_envelope_recipient(DbmailMessage * self,const char * envelope_recipient)880 void dbmail_message_set_envelope_recipient(DbmailMessage *self, const char *envelope_recipient)
881 {
882 	if (envelope_recipient)
883 		p_string_printf(self->envelope_recipient, "%s", envelope_recipient);
884 }
885 
dbmail_message_get_envelope_recipient(const DbmailMessage * self)886 const char * dbmail_message_get_envelope_recipient(const DbmailMessage *self)
887 {
888 	if (p_string_len(self->envelope_recipient) > 0)
889 		return p_string_str(self->envelope_recipient);
890 	return NULL;
891 }
892 
dbmail_message_set_header(DbmailMessage * self,const char * header,const char * value)893 void dbmail_message_set_header(DbmailMessage *self, const char *header, const char *value)
894 {
895 	g_mime_object_prepend_header(GMIME_OBJECT(self->content), header, value);
896 }
897 
dbmail_message_get_header(const DbmailMessage * self,const char * header)898 const gchar * dbmail_message_get_header(const DbmailMessage *self, const char *header)
899 {
900 	return g_mime_object_get_header(GMIME_OBJECT(self->content), header);
901 }
902 
903 struct payload {
904 	const DbmailMessage *message;
905 	const char *header;
906 	GList *list;
907 };
908 
_get_header_repeated(const char * name,const char * value,gpointer data)909 void _get_header_repeated(const char *name, const char *value, gpointer data)
910 {
911 	struct payload *load = (struct payload *)data;
912 	if (MATCH(load->header, name))
913 		load->list = g_list_append(load->list, (gpointer)value);
914 }
915 
916 
dbmail_message_get_header_repeated(const DbmailMessage * self,const char * header)917 GList * dbmail_message_get_header_repeated(const DbmailMessage *self, const char *header)
918 {
919 	GMimeHeaderList *headers = g_mime_object_get_header_list(
920 			GMIME_OBJECT(self->content));
921 
922 	struct payload data;
923 	memset(&data, 0, sizeof(struct payload));
924 	data.header = header;
925 	data.list = NULL;
926 
927 	g_mime_header_list_foreach(headers, _get_header_repeated, &data);
928 
929 	return data.list;
930 }
931 
dbmail_message_get_header_addresses(DbmailMessage * message,const char * field_name)932 GList * dbmail_message_get_header_addresses(DbmailMessage *message, const char *field_name)
933 {
934 	InternetAddressList *ialist;
935 	InternetAddress *ia;
936 	GList *result = NULL;
937 	const char *field_value;
938 	int i,j = 0;
939 
940 	if (!message || !field_name) {
941 		TRACE(TRACE_WARNING, "received a NULL argument, this is a bug");
942 		return NULL;
943 	}
944 
945 	if ((field_value = dbmail_message_get_header(message, field_name)) == NULL)
946 		return NULL;
947 
948 	TRACE(TRACE_INFO, "mail address parser looking at field [%s] with value [%s]", field_name, field_value);
949 
950 	if ((ialist = internet_address_list_parse_string(field_value)) == NULL) {
951 		TRACE(TRACE_NOTICE, "mail address parser error parsing header field");
952 		return NULL;
953 	}
954 
955 	i = internet_address_list_length(ialist);
956 	for (j=0; j<i; j++) {
957 		const char *a;
958 		ia = internet_address_list_get_address(ialist, j);
959 		if ((a = internet_address_mailbox_get_addr((InternetAddressMailbox *)ia)) != NULL) {;
960 			TRACE(TRACE_DEBUG, "mail address parser found [%s]", a);
961 			result = g_list_append(result, (gpointer)g_strdup(a));
962 		}
963 	}
964 	g_object_unref(ialist);
965 
966 	TRACE(TRACE_DEBUG, "mail address parser found [%d] email addresses", g_list_length(result));
967 
968 	return result;
969 }
970 
dbmail_message_get_charset(DbmailMessage * self)971 const char * dbmail_message_get_charset(DbmailMessage *self)
972 {
973 	assert(self && self->content);
974 	if (! self->charset)
975 		self->charset = message_get_charset((GMimeMessage *)self->content);
976 	return self->charset;
977 }
978 
979 /* dump message(parts) to char ptrs */
dbmail_message_to_string(const DbmailMessage * self)980 gchar * dbmail_message_to_string(const DbmailMessage *self)
981 {
982 	assert(self && self->content);
983 	return g_mime_object_to_string(GMIME_OBJECT(self->content));
984 }
dbmail_message_body_to_string(const DbmailMessage * self)985 gchar * dbmail_message_body_to_string(const DbmailMessage *self)
986 {
987 	assert(self && self->content);
988 	return g_mime_object_get_body(GMIME_OBJECT(self->content));
989 }
990 
dbmail_message_hdrs_to_string(const DbmailMessage * self)991 gchar * dbmail_message_hdrs_to_string(const DbmailMessage *self)
992 {
993 	char *h;
994 	unsigned offset = 0;
995 
996 	h = dbmail_message_to_string(self);
997 	offset = find_end_of_header(h);
998 	h[offset] = '\0';
999 	return g_realloc(h, offset+1);
1000 }
1001 
dbmail_message_get_size(const DbmailMessage * self,gboolean crlf)1002 size_t dbmail_message_get_size(const DbmailMessage *self, gboolean crlf)
1003 {
1004         return crlf ? (size_t)p_string_len(self->crlf):(size_t)g_mime_stream_length(self->stream);
1005 }
1006 
_retrieve(DbmailMessage * self,const char * query_template)1007 static DbmailMessage * _retrieve(DbmailMessage *self, const char *query_template)
1008 {
1009 	int l, row = 0;
1010 	GString *m;
1011 	INIT_QUERY;
1012 	Connection_T c; ResultSet_T r;
1013 	DbmailMessage *store;
1014 	Field_T frag;
1015 	char *internal_date = NULL;
1016 	gconstpointer blob;
1017 
1018 	assert(dbmail_message_get_physid(self));
1019 
1020 	store = self;
1021 
1022 	if ((self = _mime_retrieve(self)))
1023 		return self;
1024 
1025 	/*
1026 	 * _mime_retrieve failed. Fall back to messageblks
1027 	 * interface
1028 	 */
1029 	TRACE(TRACE_INFO, "[%" PRIu64 "] Deprecation warning. Please migrate the old messageblks using dbmail-util -M",
1030 			dbmail_message_get_physid(store));
1031 	self = store;
1032 
1033 	date2char_str("p.internal_date", &frag);
1034 	snprintf(query, DEF_QUERYSIZE-1, query_template, frag, DBPFX, DBPFX, dbmail_message_get_physid(self));
1035 
1036 	c = db_con_get();
1037 	if (! (r = db_query(c, query))) {
1038 		db_con_close(c);
1039 		return NULL;
1040 	}
1041 
1042 	row = 0;
1043 	m = g_string_new("");
1044 	while (db_result_next(r)) {
1045 		blob = db_result_get_blob(r,0,&l);
1046 		if (row == 0) internal_date = g_strdup(db_result_get(r,2));
1047 		g_string_append_len(m, (const char *)blob, l);
1048 		row++;
1049 	}
1050 	db_con_close(c);
1051 
1052 	if (row == 0) {
1053 		g_string_free(m, TRUE);
1054 		return NULL;
1055 	}
1056 
1057 	self = dbmail_message_init_with_string(self,m->str);
1058 	dbmail_message_set_internal_date(self, internal_date);
1059 
1060 	if (internal_date) g_free(internal_date);
1061 	g_string_free(m,TRUE);
1062 
1063 	return self;
1064 }
1065 
1066 /*
1067  *
1068  * retrieve the full message
1069  *
1070  */
_fetch_full(DbmailMessage * self)1071 static DbmailMessage * _fetch_full(DbmailMessage *self)
1072 {
1073 	const char *query_template = "SELECT b.messageblk, b.is_header, %s "
1074 		"FROM %smessageblks b "
1075 		"JOIN %sphysmessage p ON b.physmessage_id=p.id "
1076 		"WHERE b.physmessage_id = %" PRIu64 " "
1077 		"ORDER BY b.messageblk_idnr";
1078 	return _retrieve(self, query_template);
1079 }
1080 
1081 /* \brief retrieve message
1082  * \param empty DbmailMessage
1083  * \param physmessage_id
1084  * \return filled DbmailMessage
1085  */
dbmail_message_retrieve(DbmailMessage * self,uint64_t physid)1086 DbmailMessage * dbmail_message_retrieve(DbmailMessage *self, uint64_t physid)
1087 {
1088 	assert(physid);
1089 	DbmailMessage *ptr;
1090 
1091 	dbmail_message_set_physid(self, physid);
1092 	ptr = self;
1093 
1094 	self = _fetch_full(self);
1095 
1096 	if ((!self) || (! self->content)) {
1097 		TRACE(TRACE_ERR, "retrieval failed for physid [%" PRIu64 "]", physid);
1098 		dbmail_message_free(ptr);
1099 		return NULL;
1100 	}
1101 
1102 	return self;
1103 }
1104 
1105 
1106 /* \brief store a temporary copy of a message.
1107  * \param 	filled DbmailMessage
1108  * \return
1109  *     - -1 on error
1110  *     -  1 on success
1111  */
_update_message(DbmailMessage * self)1112 static int _update_message(DbmailMessage *self)
1113 {
1114 	uint64_t size    = (uint64_t)dbmail_message_get_size(self,FALSE);
1115 	uint64_t rfcsize = (uint64_t)dbmail_message_get_size(self,TRUE);
1116 
1117 	assert(size);
1118 	assert(rfcsize);
1119 	if (! db_update("UPDATE %sphysmessage SET messagesize = %" PRIu64 ", rfcsize = %" PRIu64 " WHERE id = %" PRIu64 "",
1120 			DBPFX, size, rfcsize, self->id))
1121 		return DM_EQUERY;
1122 
1123 	if (! db_update("UPDATE %smessages SET status = %d WHERE message_idnr = %" PRIu64 "",
1124 			DBPFX, MESSAGE_STATUS_NEW, self->msg_idnr))
1125 		return DM_EQUERY;
1126 
1127 	if (! dm_quota_user_inc(db_get_useridnr(self->msg_idnr), size))
1128 		return DM_EQUERY;
1129 
1130 	return DM_SUCCESS;
1131 }
1132 
1133 
dbmail_message_store(DbmailMessage * self)1134 int dbmail_message_store(DbmailMessage *self)
1135 {
1136 	uint64_t user_idnr;
1137 	char unique_id[UID_SIZE];
1138 	int res = 0, i = 1, retry = 10, delay = 200;
1139 	int step = 0;
1140 
1141 	if (! auth_user_exists(DBMAIL_DELIVERY_USERNAME, &user_idnr)) {
1142 		TRACE(TRACE_ERR, "unable to find user_idnr for user [%s]. Make sure this system user is in the database!", DBMAIL_DELIVERY_USERNAME);
1143 		return DM_EQUERY;
1144 	}
1145 
1146 	create_unique_id(unique_id, user_idnr);
1147 
1148 	while (i++ < retry) {
1149 		if (step == 0) {
1150 			/* create a message record */
1151 			if(_message_insert(self, user_idnr, DBMAIL_TEMPMBOX, unique_id) < 0) {
1152 				usleep(delay*i);
1153 				continue;
1154 			}
1155 			step++;
1156 		}
1157 		if (step == 1) {
1158 			/* update message meta-data and owner quota */
1159 			if ((res = _update_message(self) < 0)) {
1160 				usleep(delay*i);
1161 				continue;
1162 			}
1163 			step++;
1164 		}
1165 
1166 		if (step == 2) {
1167 			/* store the message mime-parts */
1168 			if ((res = dm_message_store(self))) {
1169 				TRACE(TRACE_WARNING,"Failed to store mimeparts");
1170 				usleep(delay*i);
1171 				continue;
1172 			}
1173 			step++;
1174 		}
1175 
1176 		if (step == 3) {
1177 			/* store message headers */
1178 			if ((res = dbmail_message_cache_headers(self)) < 0) {
1179 				usleep(delay*i);
1180 				continue;
1181 			}
1182 
1183 			dbmail_message_cache_envelope(self);
1184 
1185 			step++;
1186 		}
1187 
1188 		/* ready */
1189 		break;
1190 	}
1191 
1192 	return res;
1193 }
1194 
insert_physmessage(DbmailMessage * self,Connection_T c)1195 static void insert_physmessage(DbmailMessage *self, Connection_T c)
1196 {
1197 	ResultSet_T r = NULL;
1198 	char *internal_date = NULL, *frag;
1199 	int thisyear;
1200 	volatile uint64_t id = 0;
1201 	struct timeval tv;
1202 	struct tm gmt;
1203 
1204 	/* get the messages date, but override it if it's from the future */
1205 	gettimeofday(&tv, NULL);
1206 	localtime_r(&tv.tv_sec, &gmt);
1207 	thisyear = gmt.tm_year + 1900;
1208 	internal_date = dbmail_message_get_internal_date(self, thisyear);
1209 
1210 	frag = db_returning("id");
1211 
1212 	if (internal_date != NULL) {
1213 		Field_T to_date_str;
1214 		char2date_str(internal_date, &to_date_str);
1215 		g_free(internal_date);
1216 		if (db_params.db_driver == DM_DRIVER_ORACLE)
1217 			db_exec(c, "INSERT INTO %sphysmessage (internal_date) VALUES (%s) %s",
1218 					DBPFX, &to_date_str, frag);
1219 		else
1220 			r = db_query(c, "INSERT INTO %sphysmessage (internal_date) VALUES (%s) %s",
1221 					DBPFX, &to_date_str, frag);
1222 	} else {
1223 		if (db_params.db_driver == DM_DRIVER_ORACLE)
1224 			db_exec(c, "INSERT INTO %sphysmessage (internal_date) VALUES (%s) %s",
1225 					DBPFX, db_get_sql(SQL_CURRENT_TIMESTAMP), frag);
1226 		else
1227 			r = db_query(c, "INSERT INTO %sphysmessage (internal_date) VALUES (%s) %s",
1228 					DBPFX, db_get_sql(SQL_CURRENT_TIMESTAMP), frag);
1229 	}
1230 
1231 	g_free(frag);
1232 
1233 	if (db_params.db_driver == DM_DRIVER_ORACLE)
1234 		id = db_get_pk(c, "physmessage");
1235 	else if (r)
1236 		id = db_insert_result(c, r);
1237 
1238 	if (! id) {
1239 		TRACE(TRACE_ERR,"no physmessage_id [%" PRIu64 "]", id);
1240 	} else {
1241 		dbmail_message_set_physid(self, id);
1242 		TRACE(TRACE_DEBUG,"new physmessage_id [%" PRIu64 "]", id);
1243 	}
1244 }
1245 
_message_insert(DbmailMessage * self,uint64_t user_idnr,const char * mailbox,const char * unique_id)1246 int _message_insert(DbmailMessage *self,
1247 		uint64_t user_idnr,
1248 		const char *mailbox,
1249 		const char *unique_id)
1250 {
1251 	uint64_t mailboxid;
1252 	char *frag = NULL;
1253 	Connection_T c; ResultSet_T r;
1254 	volatile int t = 0;
1255 
1256 	assert(unique_id);
1257 	assert(mailbox);
1258 
1259 	if (db_find_create_mailbox(mailbox, BOX_DEFAULT, user_idnr, &mailboxid) == -1)
1260 		return -1;
1261 
1262 	if (mailboxid == 0) {
1263 		TRACE(TRACE_ERR, "mailbox [%s] could not be found!", mailbox);
1264 		return -1;
1265 	}
1266 
1267 	/* insert a new physmessage entry */
1268 
1269 	/* now insert an entry into the messages table */
1270 	c = db_con_get();
1271 	TRY
1272 		db_begin_transaction(c);
1273 		insert_physmessage(self, c);
1274 
1275 		if (db_params.db_driver == DM_DRIVER_ORACLE) {
1276 				db_exec(c, "INSERT INTO "
1277 						"%smessages(mailbox_idnr, physmessage_id, unique_id,"
1278 						"recent_flag, status) "
1279 						"VALUES (%" PRIu64 ", %" PRIu64 ", '%s', 1, %d)",
1280 						DBPFX, mailboxid, dbmail_message_get_physid(self), unique_id,
1281 						MESSAGE_STATUS_INSERT);
1282 				self->msg_idnr = db_get_pk(c, "messages");
1283 		} else {
1284 				frag = db_returning("message_idnr");
1285 				r = db_query(c, "INSERT INTO "
1286 						"%smessages(mailbox_idnr, physmessage_id, unique_id,"
1287 						"recent_flag, status) "
1288 						"VALUES (%" PRIu64 ", %" PRIu64 ", '%s', 1, %d) %s",
1289 						DBPFX, mailboxid, dbmail_message_get_physid(self), unique_id,
1290 						MESSAGE_STATUS_INSERT, frag);
1291 				g_free(frag);
1292 				self->msg_idnr = db_insert_result(c, r);
1293 		}
1294 		TRACE(TRACE_DEBUG,"new message_idnr [%" PRIu64 "]", self->msg_idnr);
1295 
1296 		t = DM_SUCCESS;
1297 		db_commit_transaction(c);
1298 	CATCH(SQLException)
1299 		LOG_SQLERROR;
1300 		db_rollback_transaction(c);
1301 		t = DM_EQUERY;
1302 	FINALLY
1303 		db_con_close(c);
1304 	END_TRY;
1305 
1306 	return t;
1307 }
1308 
1309 #define CACHE_WIDTH 255
1310 
_message_cache_envelope_date(const DbmailMessage * self)1311 void _message_cache_envelope_date(const DbmailMessage *self)
1312 {
1313 	time_t date = self->internal_date;
1314 	char *value;
1315 	char datefield[CACHE_WIDTH];
1316 	char sortfield[CACHE_WIDTH];
1317 	uint64_t headervalue_id = 0;
1318 	uint64_t headername_id = 0;
1319 
1320 	value = g_mime_utils_header_format_date(
1321 			self->internal_date,
1322 			self->internal_date_gmtoff);
1323 
1324 	memset(sortfield, 0, sizeof(sortfield));
1325 	strftime(sortfield, CACHE_WIDTH-1, "%Y-%m-%d %H:%M:%S", gmtime(&date));
1326 
1327 	if (self->internal_date_gmtoff)
1328 		date += (self->internal_date_gmtoff * 36);
1329 
1330 	memset(datefield, 0, sizeof(datefield));
1331 	strftime(datefield, 20, "%Y-%m-%d", gmtime(&date));
1332 
1333 	_header_name_get_id(self, "Date", &headername_id);
1334 	if (headername_id)
1335 		_header_value_get_id(value, sortfield, datefield, &headervalue_id);
1336 
1337 	g_free(value);
1338 
1339 	if (headervalue_id && headername_id)
1340 		_header_insert(self->id, headername_id, headervalue_id);
1341 }
1342 
dbmail_message_cache_headers(const DbmailMessage * self)1343 int dbmail_message_cache_headers(const DbmailMessage *self)
1344 {
1345 	assert(self);
1346 	assert(self->id);
1347 	GMimeObject *part;
1348 	GMimeContentType *content_type;
1349 	GMimeContentDisposition *content_disp;
1350 
1351 	if (! GMIME_IS_MESSAGE(self->content)) {
1352 		TRACE(TRACE_ERR,"self->content is not a message");
1353 		return -1;
1354 	}
1355 
1356 	/*
1357 	 * store all headers as-is, plus separate copies for
1358 	 * searching and sorting
1359 	 *
1360 	 * */
1361 	GMimeHeaderList *headers = g_mime_object_get_header_list(
1362 			GMIME_OBJECT(self->content));
1363 	g_mime_header_list_foreach(headers, (GMimeHeaderForeachFunc)_header_cache,
1364 			(gpointer)self);
1365 
1366 	/*
1367 	 * gmime treats content-type and content-disposition differently
1368 	 *
1369 	 */
1370 	part = g_mime_message_get_mime_part(GMIME_MESSAGE(self->content));
1371 	if ((content_type = g_mime_object_get_content_type(part))) {
1372 		char *value = g_mime_content_type_to_string(content_type);
1373 		_header_cache("content-type", (const char *)value, (gpointer)self);
1374 		free(value);
1375 	}
1376 
1377 	if ((content_disp = g_mime_object_get_content_disposition(part))) {
1378 		char *value = g_mime_content_disposition_to_string(
1379 				content_disp, FALSE);
1380 		_header_cache("content-disposition", (const char *)value, (gpointer)self);
1381 		free(value);
1382 	}
1383 
1384 	/*
1385 	 * if there is no Date: header, store the envelope's date
1386 	 *
1387 	 * */
1388 	if (! dbmail_message_get_header(self, "Date"))
1389 		_message_cache_envelope_date(self);
1390 
1391 	/*
1392 	 * not all messages have a references field or a in-reply-to field
1393 	 *
1394 	 * */
1395 	dbmail_message_cache_referencesfield(self);
1396 
1397 	return DM_SUCCESS;
1398 }
1399 
1400 
1401 
_header_name_get_id(const DbmailMessage * self,const char * header,uint64_t * id)1402 static int _header_name_get_id(const DbmailMessage *self, const char *header, uint64_t *id)
1403 {
1404 	uint64_t *tmp = NULL;
1405 	gpointer cacheid;
1406 	gchar *case_header, *safe_header, *frag;
1407 	Connection_T c; ResultSet_T r; PreparedStatement_T s;
1408 	Field_T config;
1409 	volatile gboolean cache_readonly = false;
1410 	volatile int t = FALSE;
1411 
1412 	// rfc822 headernames are case-insensitive
1413 	safe_header = g_ascii_strdown(header,-1);
1414 	if ((cacheid = g_hash_table_lookup(self->header_dict, (gconstpointer)safe_header)) != NULL) {
1415 		*id = *(uint64_t *)cacheid;
1416 		g_free(safe_header);
1417 		return 1;
1418 	}
1419 
1420 	config_get_value("header_cache_readonly", "DBMAIL", config);
1421 	if (strlen(config)) {
1422 		if (SMATCH(config, "true") || SMATCH(config, "yes")) {
1423 			cache_readonly = true;
1424 		}
1425 	}
1426 
1427 	case_header = g_strdup_printf(db_get_sql(SQL_STRCASE),"headername");
1428 	tmp = g_new0(uint64_t,1);
1429 
1430 	c = db_con_get();
1431 
1432 	TRY
1433 		db_begin_transaction(c);
1434 		*tmp = 0;
1435 		s = db_stmt_prepare(c, "SELECT id FROM %sheadername WHERE %s=?", DBPFX, case_header);
1436 		db_stmt_set_str(s,1,safe_header);
1437 		r = db_stmt_query(s);
1438 
1439 		if (db_result_next(r)) {
1440 			*tmp = db_result_get_u64(r,0);
1441 		} else if (cache_readonly) {
1442 			*tmp = 0;
1443 			TRACE(TRACE_DEBUG, "skip: [%s] since headername table is readonly", safe_header);
1444 		} else {
1445 			db_con_clear(c);
1446 
1447 			frag = db_returning("id");
1448 			s = db_stmt_prepare(c, "INSERT %s INTO %sheadername (headername) VALUES (?) %s",
1449 					db_get_sql(SQL_IGNORE), DBPFX, frag);
1450 			g_free(frag);
1451 
1452 			db_stmt_set_str(s,1,safe_header);
1453 
1454 			if (db_params.db_driver == DM_DRIVER_ORACLE) {
1455 				db_stmt_exec(s);
1456 				*tmp = db_get_pk(c, "headername");
1457 			} else {
1458 				r = db_stmt_query(s);
1459 				*tmp = db_insert_result(c, r);
1460 			}
1461 		}
1462 		t = TRUE;
1463 		db_commit_transaction(c);
1464 
1465 	CATCH(SQLException)
1466 		LOG_SQLERROR;
1467 		db_rollback_transaction(c);
1468 		t = DM_EQUERY;
1469 	FINALLY
1470 		db_con_close(c);
1471 	END_TRY;
1472 
1473 	g_free(case_header);
1474 
1475 	if (t == DM_EQUERY) {
1476 		g_free(safe_header);
1477 		g_free(tmp);
1478 		return t;
1479 	}
1480 
1481 	*id = *tmp;
1482 	g_hash_table_insert(self->header_dict, (gpointer)(safe_header), (gpointer)(tmp));
1483 	return 1;
1484 }
1485 
_header_value_exists(Connection_T c,const char * value,const char * hash)1486 static uint64_t _header_value_exists(Connection_T c, const char *value, const char *hash)
1487 {
1488 	ResultSet_T r; PreparedStatement_T s;
1489 	uint64_t id = 0;
1490 	char blob_cmp[DEF_FRAGSIZE];
1491 	memset(blob_cmp, 0, sizeof(blob_cmp));
1492 
1493 	if (db_params.db_driver == DM_DRIVER_ORACLE && strlen(value) > DM_ORA_MAX_BYTES_LOB_CMP) {
1494 		/** Value greater then DM_ORA_MAX_BYTES_LOB_CMP will cause SQL exception */
1495 		return 0;
1496 	}
1497 	db_con_clear(c);
1498 	snprintf(blob_cmp, DEF_FRAGSIZE-1, db_get_sql(SQL_COMPARE_BLOB), "headervalue");
1499 
1500 	s = db_stmt_prepare(c, "SELECT id FROM %sheadervalue WHERE hash=? AND %s", DBPFX, blob_cmp);
1501 	db_stmt_set_str(s, 1, hash);
1502 	db_stmt_set_blob(s, 2, value, strlen(value));
1503 
1504 	r = db_stmt_query(s);
1505 	if (db_result_next(r))
1506 		id = db_result_get_u64(r,0);
1507 
1508 	return id;
1509 
1510 }
1511 
_header_value_insert(Connection_T c,const char * value,const char * sortfield,const char * datefield,const char * hash)1512 static uint64_t _header_value_insert(Connection_T c, const char *value, const char *sortfield, const char *datefield, const char *hash)
1513 {
1514 	ResultSet_T r; PreparedStatement_T s;
1515 	uint64_t id = 0;
1516 	char *frag;
1517 	size_t datesize = 0;
1518 
1519 	if (datefield)
1520 		datesize = strlen(datefield);
1521 
1522 	db_con_clear(c);
1523 
1524 	frag = db_returning("id");
1525 	if (datesize)
1526 		s = db_stmt_prepare(c, "INSERT INTO %sheadervalue (hash, headervalue, sortfield, datefield) VALUES (?,?,?,?) %s", DBPFX, frag);
1527 	else
1528 		s = db_stmt_prepare(c, "INSERT INTO %sheadervalue (hash, headervalue, sortfield) VALUES (?,?,?) %s", DBPFX, frag);
1529 	g_free(frag);
1530 
1531 	db_stmt_set_str(s, 1, hash);
1532 	db_stmt_set_blob(s, 2, value, strlen(value));
1533 	db_stmt_set_str(s, 3, sortfield);
1534 	if (datesize)
1535 		db_stmt_set_str(s, 4, datefield);
1536 
1537 	if (db_params.db_driver == DM_DRIVER_ORACLE) {
1538 		db_stmt_exec(s);
1539 		id = db_get_pk(c, "headervalue");
1540 	} else {
1541 		r = db_stmt_query(s);
1542 		id = db_insert_result(c, r);
1543 	}
1544 	TRACE(TRACE_DATABASE,"new headervalue.id [%" PRIu64 "]", id);
1545 
1546 	return id;
1547 }
1548 
_header_value_get_id(const char * value,const char * sortfield,const char * datefield,uint64_t * id)1549 static int _header_value_get_id(const char *value, const char *sortfield, const char *datefield, uint64_t *id)
1550 {
1551 	uint64_t tmp = 0;
1552 	char hash[FIELDSIZE];
1553 	memset(hash, 0, sizeof(hash));
1554 
1555 	Connection_T c;
1556 	if (dm_get_hash_for_string(value, hash))
1557 		return FALSE;
1558 
1559 	c = db_con_get();
1560 	TRY
1561 		db_begin_transaction(c);
1562 		if ((tmp = _header_value_exists(c, value, (const char *)hash)) != 0)
1563 			*id = tmp;
1564 		else if ((tmp = _header_value_insert(c, value, sortfield, datefield, (const char *)hash)) != 0)
1565 			*id = tmp;
1566 		db_commit_transaction(c);
1567 	CATCH(SQLException)
1568 		LOG_SQLERROR;
1569 		db_rollback_transaction(c);
1570 		*id = 0;
1571 	FINALLY
1572 		db_con_close(c);
1573 	END_TRY;
1574 
1575 	return TRUE;
1576 }
1577 
_header_insert(uint64_t physmessage_id,uint64_t headername_id,uint64_t headervalue_id)1578 static gboolean _header_insert(uint64_t physmessage_id, uint64_t headername_id, uint64_t headervalue_id)
1579 {
1580 
1581 	Connection_T c; PreparedStatement_T s; volatile gboolean t = TRUE;
1582 
1583 	c = db_con_get();
1584 	db_con_clear(c);
1585 	TRY
1586 		db_begin_transaction(c);
1587 		s = db_stmt_prepare(c, "INSERT INTO %sheader (physmessage_id, headername_id, headervalue_id) VALUES (?,?,?)", DBPFX);
1588 		db_stmt_set_u64(s, 1, physmessage_id);
1589 		db_stmt_set_u64(s, 2, headername_id);
1590 		db_stmt_set_u64(s, 3, headervalue_id);
1591 		db_stmt_exec(s);
1592 		db_commit_transaction(c);
1593 	CATCH(SQLException)
1594 		db_rollback_transaction(c);
1595 		t = FALSE;
1596 	FINALLY
1597 		db_con_close(c);
1598 	END_TRY;
1599 
1600 	return t;
1601 }
1602 
_header_addresses(InternetAddressList * ialist)1603 static GString * _header_addresses(InternetAddressList *ialist)
1604 {
1605 	int i,j;
1606 	InternetAddress *ia;
1607 	GString *store = g_string_new("");
1608 
1609 	i = internet_address_list_length(ialist);
1610 	for (j=0; j<i; j++) {
1611 		ia = internet_address_list_get_address(ialist, j);
1612 		if(ia == NULL) break;
1613 
1614 		if (internet_address_group_get_members((InternetAddressGroup *)ia)) {
1615 
1616 			if (j>0) g_string_append(store, " ");
1617 
1618 			GString *group;
1619 			const char *name;
1620 		       	if ((name = internet_address_get_name(ia))) {
1621 				if (strchr(name, ',')) {
1622 					g_string_append_printf(store, "\"%s\":", internet_address_get_name(ia));
1623 				} else {
1624 					g_string_append_printf(store, "%s:", internet_address_get_name(ia));
1625 				}
1626 			}
1627 			group = _header_addresses(internet_address_group_get_members((InternetAddressGroup *)ia));
1628 			if (group->len > 0)
1629 				g_string_append_printf(store, " %s", group->str);
1630 			g_string_free(group, TRUE);
1631 			g_string_append(store, ";");
1632 		} else {
1633 
1634 			if (j>0)
1635 				g_string_append(store, ", ");
1636 
1637 			const char *name = internet_address_get_name(ia);
1638 			const char *addr = internet_address_mailbox_get_addr((InternetAddressMailbox *)ia);
1639 
1640 			if (name) {
1641 				if (strchr(name, ',')) {
1642 					g_string_append_printf(store, "\"%s\" ", name);
1643 				} else {
1644 					g_string_append_printf(store, "%s ", name);
1645 				}
1646 			}
1647 			if (addr)
1648 				g_string_append_printf(store, "%s%s%s",
1649 						name?"<":"",
1650 						addr,
1651 						name?">":"");
1652 		}
1653 	}
1654 	return store;
1655 }
1656 
_header_cache(const char * header,const char * raw,gpointer user_data)1657 static void _header_cache(const char *header, const char *raw, gpointer user_data)
1658 {
1659 	uint64_t headername_id = 0;
1660 	uint64_t headervalue_id;
1661 	DbmailMessage *self = (DbmailMessage *)user_data;
1662 	time_t date;
1663 	volatile gboolean isaddr = 0, isdate = 0, issubject = 0;
1664 	const char *charset = dbmail_message_get_charset(self);
1665 	char datefield[CACHE_WIDTH];
1666 	char sortfield[CACHE_WIDTH*4];
1667 	char *value = NULL;
1668 	InternetAddressList *emaillist;
1669 	InternetAddress *ia;
1670 
1671 	memset(sortfield, 0, sizeof(sortfield));
1672 
1673 	/* skip headernames with spaces like From_ */
1674 	if (strchr(header, ' '))
1675 		return;
1676 
1677 	TRACE(TRACE_DEBUG,"headername [%s]", header);
1678 
1679 	if ((_header_name_get_id(self, header, &headername_id) < 0))
1680 		return;
1681 	if (! headername_id)
1682 		return;
1683 
1684 	if (g_ascii_strcasecmp(header,"From")==0)
1685 		isaddr=1;
1686 	else if (g_ascii_strcasecmp(header,"To")==0)
1687 		isaddr=1;
1688 	else if (g_ascii_strcasecmp(header,"Reply-to")==0)
1689 		isaddr=1;
1690 	else if (g_ascii_strcasecmp(header,"Cc")==0)
1691 		isaddr=1;
1692 	else if (g_ascii_strcasecmp(header,"Bcc")==0)
1693 		isaddr=1;
1694 	else if (g_ascii_strcasecmp(header,"Return-path")==0)
1695 		isaddr=1;
1696 	else if (g_ascii_strcasecmp(header,"Subject")==0)
1697 		issubject=1;
1698 	else if (g_ascii_strcasecmp(header,"Date")==0)
1699 		isdate=1;
1700 
1701 
1702 	value = dbmail_iconv_decode_field(raw, charset, isaddr);
1703 
1704 	if ((! value) || (strlen(value) == 0)) {
1705 		if (value)
1706 			g_free(value);
1707 		return;
1708 	}
1709 
1710 	// Generate additional fields for SORT optimization
1711 	if(isaddr) {
1712 		GString *store;
1713 		int i,j=0;
1714 		emaillist = internet_address_list_parse_string(value);
1715 		store = _header_addresses(emaillist);
1716 
1717 		i = internet_address_list_length(emaillist);
1718 		for (j=0; j<i; j++) {
1719 			ia = internet_address_list_get_address(emaillist, j);
1720 			if(ia == NULL) break;
1721 
1722 
1723 			if(sortfield[0] == '\0') {
1724 				// Only the first email recipient is to be used for sorting - so save it now.
1725 				const char *addr;
1726 
1727 				if (internet_address_group_get_members((InternetAddressGroup *)ia)) {
1728 					addr = internet_address_get_name(ia);
1729 					g_strlcpy(sortfield, addr ? addr : "", CACHE_WIDTH-1);
1730 				} else {
1731 					addr = internet_address_mailbox_get_addr((InternetAddressMailbox *)ia);
1732 					gchar **parts = g_strsplit(addr, "@",2);
1733 					g_strlcpy(sortfield, parts[0] ? parts[0] : "", CACHE_WIDTH-1);
1734 					g_strfreev(parts);
1735 				}
1736 			}
1737 		}
1738 		g_object_unref(emaillist);
1739 		g_free(value);
1740 
1741 		value = store->str;
1742 		g_string_free(store, FALSE);
1743 	}
1744 
1745 	if(issubject) {
1746 		char *s, *t = dm_base_subject(value);
1747 		s = dbmail_iconv_str_to_db(t, charset);
1748 		g_utf8_strncpy(sortfield, s, CACHE_WIDTH-1);
1749 		g_free(s);
1750 		g_free(t);
1751 	}
1752 
1753 	memset(datefield, 0, sizeof(datefield));
1754 	if(isdate) {
1755 		int offset;
1756 		date = g_mime_utils_header_decode_date(value,&offset);
1757 		strftime(sortfield, CACHE_WIDTH-1, "%Y-%m-%d %H:%M:%S", gmtime(&date));
1758 
1759 		date += (offset * 36); // +0200 -> offset 200
1760 		strftime(datefield, 20,"%Y-%m-%d", gmtime(&date));
1761 		TRACE(TRACE_DEBUG,"Date is [%s] offset [%d], datefield [%s]",
1762 				value, offset, datefield);
1763 	}
1764 
1765 	if (sortfield[0] == '\0')
1766 		g_utf8_strncpy(sortfield, value, CACHE_WIDTH-1);
1767 
1768 	/* Fetch header value id if exists, else insert, and return new id */
1769 	_header_value_get_id(value, sortfield, datefield, &headervalue_id);
1770 
1771 	g_free(value);
1772 
1773 	/* Insert relation between physmessage, header name and header value */
1774 	if (headervalue_id)
1775 		_header_insert(self->id, headername_id, headervalue_id);
1776 	else
1777 		TRACE(TRACE_INFO, "error inserting headervalue. skipping.");
1778 
1779 	headervalue_id=0;
1780 
1781 	emaillist=NULL;
1782 	date=0;
1783 }
1784 
insert_field_cache(uint64_t physid,const char * field,const char * value)1785 static void insert_field_cache(uint64_t physid, const char *field, const char *value)
1786 {
1787 	gchar *clean_value;
1788 	Connection_T c; PreparedStatement_T s;
1789 
1790 	g_return_if_fail(value != NULL);
1791 
1792 	/* field values are truncated to 255 bytes */
1793 	clean_value = g_strndup(value,CACHE_WIDTH);
1794 
1795 	c = db_con_get();
1796 	TRY
1797 		db_begin_transaction(c);
1798 		s = db_stmt_prepare(c,"INSERT INTO %s%sfield (physmessage_id, %sfield) VALUES (?,?)", DBPFX, field, field);
1799 		db_stmt_set_u64(s, 1, physid);
1800 		db_stmt_set_str(s, 2, clean_value);
1801 		db_stmt_exec(s);
1802 		db_commit_transaction(c);
1803 	CATCH(SQLException)
1804 		LOG_SQLERROR;
1805 		db_rollback_transaction(c);
1806 		TRACE(TRACE_ERR, "insert %sfield failed [%s]", field, value);
1807 	FINALLY
1808 		db_con_close(c);
1809 	END_TRY;
1810 	g_free(clean_value);
1811 }
1812 
1813 #define DM_ADDRESS_TYPE_TO "To"
1814 #define DM_ADDRESS_TYPE_CC "Cc"
1815 #define DM_ADDRESS_TYPE_FROM "From"
1816 #define DM_ADDRESS_TYPE_REPL "Reply-to"
1817 
dbmail_message_cache_referencesfield(const DbmailMessage * self)1818 void dbmail_message_cache_referencesfield(const DbmailMessage *self)
1819 {
1820 	GMimeReferences *refs, *head;
1821 	GTree *tree;
1822 	const char *referencesfield, *inreplytofield;
1823 	char *field;
1824 
1825 	referencesfield = (char *)dbmail_message_get_header(self,"References");
1826 	inreplytofield = (char *)dbmail_message_get_header(self,"In-Reply-To");
1827 
1828 	// Some clients will put parent in the in-reply-to header only and the grandparents and older in references
1829 	field = g_strconcat(referencesfield, " ", inreplytofield, NULL);
1830 	refs = g_mime_references_decode(field);
1831 	g_free(field);
1832 
1833 	if (! refs) {
1834 		TRACE(TRACE_DEBUG, "reference_decode failed [%" PRIu64 "]", self->id);
1835 		return;
1836 	}
1837 
1838 	head = refs;
1839 	tree = g_tree_new_full((GCompareDataFunc)dm_strcmpdata, NULL, NULL, NULL);
1840 
1841 	while (refs->msgid) {
1842 		if (! g_tree_lookup(tree,refs->msgid)) {
1843 			insert_field_cache(self->id, "references", refs->msgid);
1844 			g_tree_insert(tree,refs->msgid,refs->msgid);
1845 		}
1846 		if (refs->next == NULL)
1847 			break;
1848 		refs = refs->next;
1849 	}
1850 
1851 	g_tree_destroy(tree);
1852 	g_mime_references_clear(&head);
1853 }
1854 
dbmail_message_cache_envelope(const DbmailMessage * self)1855 void dbmail_message_cache_envelope(const DbmailMessage *self)
1856 {
1857 	char *envelope = NULL;
1858 	Connection_T c; PreparedStatement_T s;
1859 
1860 	envelope = imap_get_envelope(GMIME_MESSAGE(self->content));
1861 
1862 	c = db_con_get();
1863 	TRY
1864 		db_begin_transaction(c);
1865 		s = db_stmt_prepare(c, "INSERT INTO %senvelope (physmessage_id, envelope) VALUES (?,?)", DBPFX);
1866 		db_stmt_set_u64(s, 1, self->id);
1867 		db_stmt_set_str(s, 2, envelope);
1868 		db_stmt_exec(s);
1869 		db_commit_transaction(c);
1870 	CATCH(SQLException)
1871 		LOG_SQLERROR;
1872 		db_rollback_transaction(c);
1873 		TRACE(TRACE_ERR, "insert envelope failed [%s]", envelope);
1874 	FINALLY
1875 		db_con_close(c);
1876 	END_TRY;
1877 
1878 	g_free(envelope);
1879 	envelope = NULL;
1880 }
1881 
1882 //
1883 // construct a new message where only sender, recipient, subject and
1884 // a body are known. The body can be any kind of charset. Make sure
1885 // it's not pre-encoded (base64, quopri)
1886 //
1887 // TODO: support text/html
1888 
dbmail_message_construct(DbmailMessage * self,const gchar * to,const gchar * from,const gchar * subject,const gchar * body)1889 DbmailMessage * dbmail_message_construct(DbmailMessage *self,
1890 		const gchar *to, const gchar *from,
1891 		const gchar *subject, const gchar *body)
1892 {
1893 	GMimeMessage *message;
1894 	GMimePart *mime_part;
1895 	GMimeDataWrapper *content;
1896 	GMimeStream *stream, *fstream;
1897 	GMimeContentType *mime_type;
1898 	GMimeContentEncoding encoding = GMIME_CONTENT_ENCODING_DEFAULT;
1899 	GMimeFilter *filter = NULL;
1900 
1901 	// FIXME: this could easily be expanded to allow appending
1902 	// a new sub-part to an existing mime-part. But for now let's
1903 	// require self to be a pristine (empty) DbmailMessage.
1904 	g_return_val_if_fail(self->content==NULL, self);
1905 
1906 	message = g_mime_message_new(TRUE);
1907 
1908 	// determine the optimal encoding type for the body: how would gmime
1909 	// encode this string. This will return either base64 or quopri.
1910 	if (g_mime_utils_text_is_8bit((unsigned char *)body, strlen(body)))
1911 		encoding = g_mime_utils_best_encoding((unsigned char *)body, strlen(body));
1912 
1913 	// set basic headers
1914 	TRACE(TRACE_DEBUG, "from: [%s] to: [%s] subject: [%s] body: [%s]", from, to, subject, body);
1915 	g_mime_message_set_sender(message, from);
1916 	g_mime_message_set_subject(message, subject);
1917 	g_mime_message_add_recipient(message, GMIME_RECIPIENT_TYPE_TO, NULL, to);
1918 
1919 	// construct mime-part
1920 	mime_part = g_mime_part_new();
1921 
1922 	// setup a stream-filter
1923 	stream = g_mime_stream_mem_new();
1924 	fstream = g_mime_stream_filter_new(stream);
1925 
1926 	switch(encoding) {
1927 		case GMIME_CONTENT_ENCODING_BASE64:
1928 			filter = g_mime_filter_basic_new(GMIME_CONTENT_ENCODING_BASE64, TRUE);
1929 			g_mime_stream_filter_add((GMimeStreamFilter *)fstream, filter);
1930 			g_object_unref(filter);
1931 			break;
1932 		case GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE:
1933 			filter = g_mime_filter_basic_new(GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE, TRUE);
1934 			g_mime_stream_filter_add((GMimeStreamFilter *)fstream, filter);
1935 			g_object_unref(filter);
1936 			break;
1937 		default:
1938 			break;
1939 	}
1940 
1941 	// fill the stream and thus the mime-part
1942 	g_mime_stream_write_string(fstream,body);
1943 	g_object_unref(fstream);
1944 
1945 	content = g_mime_data_wrapper_new_with_stream(stream, encoding);
1946 	g_mime_part_set_content_object(mime_part, content);
1947 	g_object_unref(content);
1948 
1949 	// Content-Type
1950 	mime_type = g_mime_content_type_new("text","plain");
1951 	g_mime_object_set_content_type((GMimeObject *)mime_part, mime_type);
1952 	g_object_unref(mime_type);
1953 	// We originally tried to use g_mime_charset_best to pick a charset,
1954 	// but it regularly failed to choose utf-8 when utf-8 data was given to it.
1955 	g_mime_object_set_content_type_parameter((GMimeObject *)mime_part, "charset", "utf-8");
1956 
1957 	// Content-Transfer-Encoding
1958 	switch(encoding) {
1959 		case GMIME_CONTENT_ENCODING_BASE64:
1960 			g_mime_object_set_header(GMIME_OBJECT(mime_part),"Content-Transfer-Encoding", "base64");
1961 			break;
1962 		case GMIME_CONTENT_ENCODING_QUOTEDPRINTABLE:
1963 			g_mime_object_set_header(GMIME_OBJECT(mime_part),"Content-Transfer-Encoding", "quoted-printable");
1964 			break;
1965 		default:
1966 			g_mime_object_set_header(GMIME_OBJECT(mime_part),"Content-Transfer-Encoding", "7bit");
1967 			break;
1968 	}
1969 
1970 	// attach the mime-part to the mime-message
1971 	g_mime_message_set_mime_part(message, (GMimeObject *)mime_part);
1972 	g_object_unref(mime_part);
1973 
1974 	// attach the message to the DbmailMessage struct
1975 	self->content = (GMimeObject *)message;
1976 	self->stream = stream;
1977 
1978 	// cleanup
1979 	return self;
1980 }
1981 
get_mailbox_from_filters(DbmailMessage * message,uint64_t useridnr,const char * mailbox,char * into,size_t into_n)1982 static int get_mailbox_from_filters(DbmailMessage *message, uint64_t useridnr, const char *mailbox, char *into, size_t into_n)
1983 {
1984 	volatile int t = FALSE;
1985 	uint64_t anyone = 0;
1986 	PreparedStatement_T stmt;
1987 	Connection_T c;
1988        	ResultSet_T r;
1989 
1990 	TRACE(TRACE_INFO, "default mailbox [%s]", mailbox);
1991 
1992 	if (mailbox != NULL) return t;
1993 
1994 	if (! auth_user_exists(DBMAIL_ACL_ANYONE_USER, &anyone))
1995 		return t;
1996 
1997 	c = db_con_get();
1998 
1999 	TRY
2000 		stmt = db_stmt_prepare(c,
2001 			       	"SELECT f.mailbox,f.headername,f.headervalue FROM %sfilters f "
2002 				"JOIN %sheadername n ON f.headername=n.headername "
2003 				"JOIN %sheader h ON h.headername_id = n.id "
2004 				"join %sheadervalue v on v.id=h.headervalue_id "
2005 				"WHERE v.headervalue %s f.headervalue "
2006 				"AND h.physmessage_id=? "
2007 				"AND f.user_id in (?,?)",
2008 				DBPFX, DBPFX, DBPFX, DBPFX,
2009 				db_get_sql(SQL_INSENSITIVE_LIKE));
2010 		db_stmt_set_u64(stmt, 1, message->id);
2011 		db_stmt_set_u64(stmt, 2, anyone);
2012 		db_stmt_set_u64(stmt, 3, useridnr);
2013 		r = db_stmt_query(stmt);
2014 
2015 		if (db_result_next(r)) {
2016 			const char *hn, *hv;
2017 			strncpy(into, db_result_get(r,0), into_n);
2018 			hn = db_result_get(r,1);
2019 			hv = db_result_get(r,2);
2020 			TRACE(TRACE_DEBUG, "match [%s: %s] file-into mailbox [%s]", hn, hv, into);
2021 			t = TRUE;
2022 		}
2023 
2024 	CATCH(SQLException)
2025 		LOG_SQLERROR;
2026 	FINALLY
2027 		db_con_close(c);
2028 	END_TRY;
2029 
2030 	return t;
2031 }
2032 
2033 /* Figure out where to deliver the message, then deliver it.
2034  * */
sort_and_deliver(DbmailMessage * message,const char * destination,uint64_t useridnr,const char * mailbox,mailbox_source source)2035 dsn_class_t sort_and_deliver(DbmailMessage *message,
2036 		const char *destination, uint64_t useridnr,
2037 		const char *mailbox, mailbox_source source)
2038 {
2039 	int cancelkeep = 0;
2040 	int reject = 0;
2041 	dsn_class_t ret;
2042 	Field_T val;
2043 	char *subaddress = NULL;
2044 	char into[1024];
2045 
2046 	/* Catch the brute force delivery right away.
2047 	 * We skip the Sieve scripts, and down the call
2048 	 * chain we don't check permissions on the mailbox. */
2049 	if (source == BOX_BRUTEFORCE) {
2050 		TRACE(TRACE_NOTICE, "Beginning brute force delivery for user [%" PRIu64 "] to mailbox [%s].",
2051 				useridnr, mailbox);
2052 		return sort_deliver_to_mailbox(message, useridnr, mailbox, source, NULL, NULL);
2053 	}
2054 
2055 	/* This is the only condition when called from pipe.c, actually. */
2056 	if (! mailbox) {
2057 		memset(into,0,sizeof(into));
2058 
2059 		if (! (get_mailbox_from_filters(message, useridnr, mailbox, into, sizeof(into)-1))) {
2060 			mailbox = "INBOX";
2061 			source = BOX_DEFAULT;
2062 		} else {
2063 			mailbox = into;
2064 		}
2065 	}
2066 
2067 	TRACE(TRACE_INFO, "Destination [%s] useridnr [%" PRIu64 "], mailbox [%s], source [%d]",
2068 			destination, useridnr, mailbox, source);
2069 
2070 	/* Subaddress. */
2071 	config_get_value("SUBADDRESS", "DELIVERY", val);
2072 	if (strcasecmp(val, "yes") == 0) {
2073 		int res;
2074 		size_t sublen, subpos;
2075 		res = find_bounded((char *)destination, '+', '@', &subaddress, &sublen, &subpos);
2076 		if (res > 0 && sublen > 0) {
2077 			/* We'll free this towards the end of the function. */
2078 			mailbox = subaddress;
2079 			source = BOX_ADDRESSPART;
2080 			TRACE(TRACE_INFO, "Setting BOX_ADDRESSPART mailbox to [%s]", mailbox);
2081 		}
2082 	}
2083 
2084 	/* Give Sieve access to the envelope recipient. */
2085 	dbmail_message_set_envelope_recipient(message, destination);
2086 
2087 	/* Sieve. */
2088 	config_get_value("SIEVE", "DELIVERY", val);
2089 	if (strcasecmp(val, "yes") == 0 && dm_sievescript_isactive(useridnr)) {
2090 		TRACE(TRACE_INFO, "Calling for a Sieve sort");
2091 		SortResult_T *sort_result = sort_process(useridnr, message, mailbox);
2092 		if (sort_result) {
2093 			cancelkeep = sort_get_cancelkeep(sort_result);
2094 			reject = sort_get_reject(sort_result);
2095 			sort_free_result(sort_result);
2096 		}
2097 	}
2098 
2099 	/* Sieve actions:
2100 	 * (m = must implement, s = should implement, e = extension)
2101 	 * m Keep - implicit default action.
2102 	 * m Discard - requires us to skip the default action.
2103 	 * m Redirect - add to the forwarding list.
2104 	 * s Fileinto - change the destination mailbox.
2105 	 * s Reject - nope, sorry. we killed bounce().
2106 	 * e Vacation - share with the auto reply code.
2107 	 */
2108 
2109 	if (cancelkeep) {
2110 		// The implicit keep has been cancelled.
2111 		// This may necessarily imply that the message
2112 		// is being discarded -- dropped flat on the floor.
2113 		ret = DSN_CLASS_OK;
2114 		TRACE(TRACE_INFO, "Keep was cancelled. Message may be discarded.");
2115 	} else {
2116 		ret = sort_deliver_to_mailbox(message, useridnr, mailbox, source, NULL, NULL);
2117 		TRACE(TRACE_INFO, "Keep was not cancelled. Message will be delivered by default.");
2118 	}
2119 
2120 	/* Might have been allocated by the subaddress calculation. NULL otherwise. */
2121 	g_free(subaddress);
2122 
2123 	/* Reject probably implies cancelkeep,
2124 	 * but we'll not assume that and instead
2125 	 * just test this as a separate block. */
2126 	if (reject) {
2127 		TRACE(TRACE_INFO, "Message will be rejected.");
2128 		ret = DSN_CLASS_FAIL;
2129 	}
2130 
2131 	return ret;
2132 }
2133 
sort_deliver_to_mailbox(DbmailMessage * message,uint64_t useridnr,const char * mailbox,mailbox_source source,int * msgflags,GList * keywords)2134 dsn_class_t sort_deliver_to_mailbox(DbmailMessage *message,
2135 		uint64_t useridnr, const char *mailbox, mailbox_source source,
2136 		int *msgflags, GList *keywords)
2137 {
2138 	uint64_t mboxidnr = 0, newmsgidnr = 0;
2139 	Field_T val;
2140 	size_t msgsize = (uint64_t)dbmail_message_get_size(message, FALSE);
2141 
2142 	if (db_find_create_mailbox(mailbox, source, useridnr, &mboxidnr) != 0) {
2143 		TRACE(TRACE_ERR, "mailbox [%s] not found", mailbox);
2144 		return DSN_CLASS_FAIL;
2145 	}
2146 
2147 	if (source == BOX_BRUTEFORCE) {
2148 		TRACE(TRACE_INFO, "Brute force delivery; skipping ACL checks on mailbox.");
2149 	} else {
2150 		// Check ACL's on the mailbox. It must be read-write,
2151 		// it must not be no_select, and it may require an ACL for
2152 		// the user whose Sieve script this is, since it's possible that
2153 		// we've looked up a #Public or a #Users mailbox.
2154 		int permission;
2155 		TRACE(TRACE_DEBUG, "Checking if we have the right to post incoming messages");
2156 
2157 		// don't load the full mailbox state
2158 		MailboxState_T S = MailboxState_new(NULL, 0);
2159 		MailboxState_setId(S, mboxidnr);
2160 		permission = acl_has_right(S, useridnr, ACL_RIGHT_POST);
2161 		MailboxState_free(&S);
2162 
2163 		switch (permission) {
2164 		case -1:
2165 			TRACE(TRACE_NOTICE, "error retrieving right for [%" PRIu64 "] to deliver mail to [%s]",
2166 					useridnr, mailbox);
2167 			return DSN_CLASS_TEMP;
2168 		case 0:
2169 			// No right.
2170 			TRACE(TRACE_NOTICE, "user [%" PRIu64 "] does not have right to deliver mail to [%s]",
2171 					useridnr, mailbox);
2172 			// Switch to INBOX.
2173 			if (strcmp(mailbox, "INBOX") == 0) {
2174 				// Except if we've already been down this path.
2175 				TRACE(TRACE_NOTICE, "already tried to deliver to INBOX");
2176 				return DSN_CLASS_FAIL;
2177 			}
2178 			return sort_deliver_to_mailbox(message, useridnr, "INBOX", BOX_DEFAULT, msgflags, keywords);
2179 		case 1:
2180 			// Has right.
2181 			TRACE(TRACE_INFO, "user [%" PRIu64 "] has right to deliver mail to [%s]",
2182 					useridnr, mailbox);
2183 			break;
2184 		default:
2185 			TRACE(TRACE_ERR, "invalid return value from acl_has_right");
2186 			return DSN_CLASS_FAIL;
2187 		}
2188 	}
2189 
2190 	// if the mailbox already holds this message we're done
2191 	GETCONFIGVALUE("suppress_duplicates", "DELIVERY", val);
2192 	if (strcasecmp(val,"yes") == 0) {
2193 		const char *messageid = dbmail_message_get_header(message, "message-id");
2194 		if ( messageid && ((db_mailbox_has_message_id(mboxidnr, messageid)) > 0) ) {
2195 			TRACE(TRACE_INFO, "suppress_duplicate: [%s]", messageid);
2196 			return DSN_CLASS_OK;
2197 		}
2198 	}
2199 
2200 	// Ok, we have the ACL right, time to deliver the message.
2201 	switch (db_copymsg(message->msg_idnr, mboxidnr, useridnr, &newmsgidnr, TRUE)) {
2202 	case -2:
2203 		TRACE(TRACE_ERR, "error copying message to user [%" PRIu64 "],"
2204 				"maxmail exceeded", useridnr);
2205 		return DSN_CLASS_QUOTA;
2206 	case -1:
2207 		TRACE(TRACE_ERR, "error copying message to user [%" PRIu64 "]",
2208 				useridnr);
2209 		return DSN_CLASS_TEMP;
2210 	default:
2211 		TRACE(TRACE_NOTICE, "useridnr [%" PRIu64 "] mailbox [%" PRIu64 "] message [%" PRIu64 "] size [%zd] is inserted",
2212 				useridnr, mboxidnr, newmsgidnr, msgsize);
2213 		if (msgflags || keywords) {
2214 			TRACE(TRACE_NOTICE, "message id=%" PRIu64 ", setting imap flags",
2215 				newmsgidnr);
2216 
2217 			if (db_set_msgflag(newmsgidnr, msgflags, keywords, IMAPFA_ADD, 0, NULL))
2218 				db_mailbox_seq_update(mboxidnr, newmsgidnr);
2219 		}
2220 		message->msg_idnr = newmsgidnr;
2221 		return DSN_CLASS_OK;
2222 	}
2223 }
2224 
parse_and_escape(const char * in,char ** out)2225 static int parse_and_escape(const char *in, char **out)
2226 {
2227 	InternetAddressList *ialist;
2228 	InternetAddress *ia;
2229 	const char *addr;
2230 
2231 	TRACE(TRACE_DEBUG, "parsing address [%s]", in);
2232 	ialist = internet_address_list_parse_string(in);
2233 	if (!ialist) {
2234                 TRACE(TRACE_NOTICE, "unable to parse email address [%s]", in);
2235                 return -1;
2236 	}
2237 
2238         ia = internet_address_list_get_address(ialist,0);
2239 	addr = internet_address_mailbox_get_addr((InternetAddressMailbox *)ia);
2240         if (!ia || !addr) {
2241 		TRACE(TRACE_NOTICE, "unable to parse email address [%s]", in);
2242 		return -1;
2243 	}
2244 
2245 	if (! (*out = dm_shellesc(addr))) {
2246 		TRACE(TRACE_ERR, "out of memory calling dm_shellesc");
2247 		return -1;
2248 	}
2249 
2250 	return 0;
2251 }
2252 /* Sends a message. */
send_mail(DbmailMessage * message,const char * to,const char * from,const char * preoutput,enum sendwhat sendwhat,char * sendmail_external)2253 int send_mail(DbmailMessage *message,
2254 		const char *to, const char *from,
2255 		const char *preoutput,
2256 		enum sendwhat sendwhat, char *sendmail_external)
2257 {
2258 	FILE *mailpipe = NULL;
2259 	char *escaped_to = NULL;
2260 	char *escaped_from = NULL;
2261 	char *sendmail_command = NULL;
2262 	Field_T sendmail, postmaster;
2263 	int result;
2264 	char *buf;
2265 
2266 	if (!from || strlen(from) < 1) {
2267 		if (config_get_value("POSTMASTER", "DBMAIL", postmaster) < 0) {
2268 			TRACE(TRACE_NOTICE, "no config value for POSTMASTER");
2269 		}
2270 		if (strlen(postmaster))
2271 			from = postmaster;
2272 		else
2273 			from = DEFAULT_POSTMASTER;
2274 	}
2275 
2276 	if (config_get_value("SENDMAIL", "DBMAIL", sendmail) < 0) {
2277 		TRACE(TRACE_ERR, "error getting value for SENDMAIL in DBMAIL section of dbmail.conf.");
2278 		return -1;
2279 	}
2280 
2281 	if (strlen(sendmail) < 1) {
2282 		TRACE(TRACE_ERR, "SENDMAIL not set in DBMAIL section of dbmail.conf.");
2283 		return -1;
2284 	}
2285 
2286 	if (!sendmail_external) {
2287 		if (parse_and_escape(to, &escaped_to) < 0) {
2288 			TRACE(TRACE_NOTICE, "could not prepare 'to' address.");
2289 			return 1;
2290 		}
2291 		if (parse_and_escape(from, &escaped_from) < 0) {
2292 			g_free(escaped_to);
2293 			TRACE(TRACE_NOTICE, "could not prepare 'from' address.");
2294 			return 1;
2295 		}
2296 		sendmail_command = g_strconcat(sendmail, " -i -f ", escaped_from, " ", escaped_to, NULL);
2297 		g_free(escaped_to);
2298 		g_free(escaped_from);
2299 		if (!sendmail_command) {
2300 			TRACE(TRACE_ERR, "out of memory calling g_strconcat");
2301 			return -1;
2302 		}
2303 	} else {
2304 		sendmail_command = sendmail_external;
2305 	}
2306 
2307 	TRACE(TRACE_INFO, "opening pipe to [%s]", sendmail_command);
2308 
2309 	if (!(mailpipe = popen(sendmail_command, "w"))) {
2310 		TRACE(TRACE_ERR, "could not open pipe to sendmail");
2311 		g_free(sendmail_command);
2312 		return 1;
2313 	}
2314 
2315 	TRACE(TRACE_DEBUG, "pipe opened");
2316 
2317 	switch (sendwhat) {
2318 	case SENDRAW:
2319 		// This is a hack so forwards can give a From line.
2320 		if (preoutput)
2321 			fprintf(mailpipe, "%s\n", preoutput);
2322 		// fall-through
2323 	case SENDMESSAGE:
2324 		buf = dbmail_message_to_string(message);
2325 		fprintf(mailpipe, "%s", buf);
2326 		g_free(buf);
2327 		break;
2328 	default:
2329 		TRACE(TRACE_ERR, "invalid sendwhat in call to send_mail: [%d]", sendwhat);
2330 		break;
2331 	}
2332 
2333 	result = pclose(mailpipe);
2334 	TRACE(TRACE_DEBUG, "pipe closed");
2335 
2336 	/* Adapted from the Linux waitpid 2 man page. */
2337 	if (WIFEXITED(result)) {
2338 		result = WEXITSTATUS(result);
2339 		TRACE(TRACE_INFO, "sendmail exited normally");
2340 	} else if (WIFSIGNALED(result)) {
2341 		result = WTERMSIG(result);
2342 		TRACE(TRACE_INFO, "sendmail was terminated by signal");
2343 	} else if (WIFSTOPPED(result)) {
2344 		result = WSTOPSIG(result);
2345 		TRACE(TRACE_INFO, "sendmail was stopped by signal");
2346 	}
2347 
2348 	if (result != 0) {
2349 		TRACE(TRACE_ERR, "sendmail error return value was [%d]", result);
2350 
2351 		if (!sendmail_external)
2352 			g_free(sendmail_command);
2353 		return 1;
2354 	}
2355 
2356 	if (!sendmail_external)
2357 		g_free(sendmail_command);
2358 	return 0;
2359 }
2360 
valid_sender(const char * addr)2361 static int valid_sender(const char *addr)
2362 {
2363 	int ret = 1;
2364 	char *testaddr;
2365 	testaddr = g_ascii_strdown(addr, -1);
2366 	if (strstr(testaddr, "mailer-daemon@"))
2367 		ret = 0;
2368 	if (strstr(testaddr, "daemon@"))
2369 		ret = 0;
2370 	if (strstr(testaddr, "postmaster@"))
2371 		ret = 0;
2372 	g_free(testaddr);
2373 	return ret;
2374 }
2375 
2376 #define REPLY_DAYS 7
2377 
check_destination(DbmailMessage * message,GList * aliases)2378 static int check_destination(DbmailMessage *message, GList *aliases)
2379 {
2380 	GList *to, *cc, *recipients = NULL;
2381 	to = dbmail_message_get_header_addresses(message, "To");
2382 	cc = dbmail_message_get_header_addresses(message, "Cc");
2383 
2384 	recipients = g_list_concat(to, cc);
2385 
2386 	while (recipients) {
2387 		char *addr = (char *)recipients->data;
2388 
2389 		if (addr) {
2390 			aliases = g_list_first(aliases);
2391 			while (aliases) {
2392 				char *alias = (char *)aliases->data;
2393 				if (MATCH(alias, addr)) {
2394 					TRACE(TRACE_DEBUG, "valid alias found as recipient [%s]", alias);
2395 					return TRUE;
2396 				}
2397 				if (! g_list_next(aliases)) break;
2398 				aliases = g_list_next(aliases);
2399 			}
2400 		}
2401 		if (! g_list_next(recipients)) break;
2402 		recipients = g_list_next(recipients);
2403 	}
2404 
2405 	g_list_free(g_list_first(recipients));
2406 	return FALSE;
2407 }
2408 
2409 
send_reply(DbmailMessage * message,const char * body,GList * aliases)2410 static int send_reply(DbmailMessage *message, const char *body, GList *aliases)
2411 {
2412 	const char *from, *to, *subject;
2413 	const char *x_dbmail_reply;
2414 	const char *precedence;
2415 	char *usubject;
2416 	char *newsubject;
2417 	char *unewsubject;
2418 	char handle[FIELDSIZE];
2419 	int result;
2420 
2421 	x_dbmail_reply = dbmail_message_get_header(message, "X-Dbmail-Reply");
2422 	if (x_dbmail_reply) {
2423 		TRACE(TRACE_INFO, "reply loop detected [%s]", x_dbmail_reply);
2424 		return 0;
2425 	}
2426 
2427 	precedence = dbmail_message_get_header(message, "Precedence");
2428 	if (precedence && (MATCH(precedence, "bulk") || MATCH(precedence, "list")))
2429 		return 0;
2430 
2431 	if (! check_destination(message, aliases)) {
2432 		TRACE(TRACE_INFO, "no valid destination ");
2433 		return 0;
2434 	}
2435 
2436 	subject = dbmail_message_get_header(message, "Subject");
2437 	from = dbmail_message_get_header(message, "Delivered-To");
2438 
2439 	if (!from)
2440 		from = p_string_str(message->envelope_recipient);
2441 	if (!from)
2442 		from = ""; // send_mail will change this to DEFAULT_POSTMASTER
2443 
2444 	to = dbmail_message_get_header(message, "Reply-To");
2445 	if (!to)
2446 		to = dbmail_message_get_header(message, "Return-Path");
2447 	if (!to) {
2448 		TRACE(TRACE_ERR, "no address to send to");
2449 		return 0;
2450 	}
2451 	if (!valid_sender(to)) {
2452 		TRACE(TRACE_DEBUG, "sender invalid. skip auto-reply.");
2453 		return 0;
2454 	}
2455 
2456 	memset(handle, 0, sizeof(handle));
2457 	if (dm_md5((const char * const)body, handle))
2458 		return 0;
2459 
2460 	if (db_replycache_validate(to, from, handle, REPLY_DAYS) != DM_SUCCESS) {
2461 		TRACE(TRACE_DEBUG, "skip auto-reply");
2462 		return 0;
2463 	}
2464 
2465 	usubject = dbmail_iconv_decode_text(subject);
2466        	unewsubject = g_strconcat("Re: ", usubject, NULL);
2467 	newsubject = g_mime_utils_header_encode_text(unewsubject);
2468 	g_free(usubject);
2469 	g_free(unewsubject);
2470 
2471 	DbmailMessage *new_message = dbmail_message_new(message->pool);
2472 	new_message = dbmail_message_construct(new_message, to, from, newsubject, body);
2473 	dbmail_message_set_header(new_message, "X-DBMail-Reply", from);
2474 	dbmail_message_set_header(new_message, "Precedence", "bulk");
2475 	dbmail_message_set_header(new_message, "Auto-Submitted", "auto-replied");
2476 
2477 	result = send_mail(new_message, to, from, NULL, SENDMESSAGE, SENDMAIL);
2478 
2479 	if (result == 0) {
2480 		db_replycache_register(to, from, handle);
2481 	}
2482 
2483 	g_free(newsubject);
2484 	dbmail_message_free(new_message);
2485 
2486 	return result;
2487 }
2488 
2489 /*
2490  *           * Send an automatic notification.
2491  *            */
send_notification(DbmailMessage * message,const char * to)2492 static int send_notification(DbmailMessage *message, const char *to)
2493 {
2494 	Field_T from = "";
2495 	Field_T subject = "";
2496 	int result;
2497 
2498 	if (config_get_value("POSTMASTER", "DBMAIL", from) < 0) {
2499 		TRACE(TRACE_INFO, "no config value for POSTMASTER");
2500 	}
2501 
2502 	if (config_get_value("AUTO_NOTIFY_SENDER", "DELIVERY", from) < 0) {
2503 		TRACE(TRACE_INFO, "no config value for AUTO_NOTIFY_SENDER");
2504 	}
2505 
2506 	if (config_get_value("AUTO_NOTIFY_SUBJECT", "DELIVERY", subject) < 0) {
2507 		TRACE(TRACE_INFO, "no config value for AUTO_NOTIFY_SUBJECT");
2508 	}
2509 
2510 	if (strlen(from) < 1)
2511 		g_strlcpy(from, AUTO_NOTIFY_SENDER, FIELDSIZE);
2512 
2513 	if (strlen(subject) < 1)
2514 		g_strlcpy(subject, AUTO_NOTIFY_SUBJECT, FIELDSIZE);
2515 
2516 	DbmailMessage *new_message = dbmail_message_new(message->pool);
2517 	new_message = dbmail_message_construct(new_message, to, from, subject, "");
2518 
2519 	result = send_mail(new_message, to, from, NULL, SENDMESSAGE, SENDMAIL);
2520 
2521 	dbmail_message_free(new_message);
2522 
2523 	return result;
2524 }
2525 
2526 
2527 /* Yeah, RAN. That's Reply And Notify ;-) */
execute_auto_ran(DbmailMessage * message,uint64_t useridnr)2528 static int execute_auto_ran(DbmailMessage *message, uint64_t useridnr)
2529 {
2530 	Field_T val;
2531 	int do_auto_notify = 0, do_auto_reply = 0;
2532 	char *reply_body = NULL;
2533 	char *notify_address = NULL;
2534 
2535 	/* message has been successfully inserted, perform auto-notification & auto-reply */
2536 	if (config_get_value("AUTO_NOTIFY", "DELIVERY", val) < 0) {
2537 		TRACE(TRACE_ERR, "error getting config value for AUTO_NOTIFY");
2538 		return -1;
2539 	}
2540 
2541 	if (strcasecmp(val, "yes") == 0)
2542 		do_auto_notify = 1;
2543 
2544 	if (config_get_value("AUTO_REPLY", "DELIVERY", val) < 0) {
2545 		TRACE(TRACE_ERR, "error getting config value for AUTO_REPLY");
2546 		return -1;
2547 	}
2548 
2549 	if (strcasecmp(val, "yes") == 0)
2550 		do_auto_reply = 1;
2551 
2552 	if (do_auto_notify) {
2553 		TRACE(TRACE_DEBUG, "starting auto-notification procedure");
2554 
2555 		if (db_get_notify_address(useridnr, &notify_address) != 0)
2556 			TRACE(TRACE_ERR, "error fetching notification address");
2557 		else {
2558 			if (notify_address == NULL)
2559 				TRACE(TRACE_DEBUG, "no notification address specified, skipping");
2560 			else {
2561 				TRACE(TRACE_DEBUG, "sending notification to [%s]", notify_address);
2562 				if (send_notification(message, notify_address) < 0) {
2563 					TRACE(TRACE_ERR, "error in call to send_notification.");
2564 					g_free(notify_address);
2565 					return -1;
2566 				}
2567 				g_free(notify_address);
2568 			}
2569 		}
2570 	}
2571 
2572 	if (do_auto_reply) {
2573 		TRACE(TRACE_DEBUG, "starting auto-reply procedure");
2574 
2575 		if (db_get_reply_body(useridnr, &reply_body) != 0)
2576 			TRACE(TRACE_DEBUG, "no reply body found");
2577 		else {
2578 			if (reply_body == NULL || reply_body[0] == '\0')
2579 				TRACE(TRACE_DEBUG, "no reply body specified, skipping");
2580 			else {
2581 				GList *aliases = auth_get_user_aliases(useridnr);
2582 				if (send_reply(message, reply_body, aliases) < 0) {
2583 					TRACE(TRACE_ERR, "error in call to send_reply");
2584 					g_free(reply_body);
2585 					return -1;
2586 				}
2587 				g_free(reply_body);
2588 
2589 			}
2590 		}
2591 	}
2592 
2593 	return 0;
2594 }
2595 
2596 
2597 
send_forward_list(DbmailMessage * message,GList * targets,const char * from)2598 int send_forward_list(DbmailMessage *message, GList *targets, const char *from)
2599 {
2600 	int result = 0;
2601 	Field_T postmaster;
2602 
2603 	if (!from) {
2604 		if (config_get_value("POSTMASTER", "DBMAIL", postmaster) < 0)
2605 			TRACE(TRACE_NOTICE, "no config value for POSTMASTER");
2606 		if (strlen(postmaster))
2607 			from = postmaster;
2608 		else
2609 			from = DEFAULT_POSTMASTER;
2610 	}
2611 	targets = g_list_first(targets);
2612 	TRACE(TRACE_INFO, "delivering to [%u] external addresses", g_list_length(targets));
2613 	while (targets) {
2614 		char *to = (char *)targets->data;
2615 
2616 		if (!to || strlen(to) < 1) {
2617 			TRACE(TRACE_ERR, "forwarding address is zero length, message not forwarded.");
2618 		} else {
2619 			if (to[0] == '!') {
2620 				// The forward is a command to execute.
2621 				// Prepend an mbox From line.
2622 				char timestr[50];
2623 				time_t td;
2624 				struct tm tm;
2625 				char *fromline;
2626 
2627 				time(&td);		/* get time */
2628 				tm = *localtime(&td);	/* get components */
2629 				strftime(timestr, sizeof(timestr), "%a %b %e %H:%M:%S %Y", &tm);
2630 
2631 				TRACE(TRACE_DEBUG, "prepending mbox style From header to pipe returnpath: %s", from);
2632 
2633 				/* Format: From<space>address<space><space>Date */
2634 				fromline = g_strconcat("From ", from, "  ", timestr, NULL);
2635 
2636 				result |= send_mail(message, "", "", fromline, SENDRAW, to+1);
2637 				g_free(fromline);
2638 			} else if (to[0] == '|') {
2639 				// The forward is a command to execute.
2640 				result |= send_mail(message, "", "", NULL, SENDRAW, to+1);
2641 
2642 			} else {
2643 				// The forward is an email address.
2644 				result |= send_mail(message, to, from, NULL, SENDRAW, SENDMAIL);
2645 			}
2646 		}
2647 		if (! g_list_next(targets))
2648 			break;
2649 		targets = g_list_next(targets);
2650 
2651 	}
2652 
2653 	return result;
2654 }
2655 
2656 
2657 /* Here's the real *meat* of this source file!
2658  *
2659  * Function: insert_messages()
2660  * What we get:
2661  *   - The message
2662  *   - A list of destinations
2663  *
2664  * What we do:
2665  *   - Store the message
2666  *   - Process the destination addresses into lists:
2667  *     - Local useridnr's
2668  *     - External forwards
2669  *     - No such user bounces
2670  *   - Store the local useridnr's
2671  *     - Run the message through each user's sorting rules
2672  *     - Potentially alter the delivery:
2673  *       - Different mailbox
2674  *       - Bounce
2675  *       - Reply with vacation message
2676  *       - Forward to another address
2677  *     - Check the user's quota before delivering
2678  *       - Do this *after* their sorting rules, since the
2679  *         sorting rules might not store the message anyways
2680  *   - Send out the no such user bounces
2681  *   - Send out the external forwards
2682  *   - Delete the temporary message from the database
2683  * What we return:
2684  *   - 0 on success
2685  *   - -1 on full failure
2686  */
2687 
insert_messages(DbmailMessage * message,List_T dsnusers)2688 int insert_messages(DbmailMessage *message, List_T dsnusers)
2689 {
2690 	uint64_t tmpid;
2691 	int result=0;
2692 	Field_T val;
2693 	gboolean quota_softfail = FALSE;
2694 
2695  	delivery_status_t final_dsn;
2696 
2697 	if ((result = dbmail_message_store(message)) == DM_EQUERY) {
2698 		TRACE(TRACE_ERR,"storing message failed");
2699 		return result;
2700 	}
2701 
2702 	TRACE(TRACE_DEBUG, "temporary msgidnr is [%" PRIu64 "]", message->msg_idnr);
2703 
2704 	config_get_value("QUOTA_FAILURE", "DELIVERY", val);
2705 	if (SMATCH(val, "soft"))
2706 		quota_softfail = TRUE;
2707 	else if (SMATCH(val, "hard"))
2708 		quota_softfail = FALSE;
2709 	else
2710 		TRACE(TRACE_INFO, "Using default hard bounce for quota failure");
2711 
2712 
2713 	tmpid = message->msg_idnr; // for later removal
2714 
2715 	// TODO: Run a Sieve script associated with the internal delivery user.
2716 	// Code would go here, after we've stored the message
2717 	// before we've started delivering it
2718 
2719 	/* Loop through the users list. */
2720 	dsnusers = p_list_first(dsnusers);
2721 	while (dsnusers) {
2722 
2723 		GList *userids;
2724 
2725 		int ok = 0, temp = 0, fail = 0, fail_quota = 0;
2726 
2727 		Delivery_T *delivery = (Delivery_T *)p_list_data(dsnusers);
2728 
2729 		/* Each user may have a list of user_idnr's for local
2730 		 * delivery. */
2731 		userids = g_list_first(delivery->userids);
2732 		while (userids) {
2733 			uint64_t *useridnr = (uint64_t *) userids->data;
2734 
2735 			TRACE(TRACE_DEBUG, "calling sort_and_deliver for useridnr [%" PRIu64 "]", *useridnr);
2736 			switch (sort_and_deliver(message, delivery->address, *useridnr, delivery->mailbox, delivery->source)) {
2737 			case DSN_CLASS_OK:
2738 				TRACE(TRACE_INFO, "successful sort_and_deliver for useridnr [%" PRIu64 "]", *useridnr);
2739 				ok = 1;
2740 				break;
2741 			case DSN_CLASS_FAIL:
2742 				TRACE(TRACE_ERR, "permanent failure sort_and_deliver for useridnr [%" PRIu64 "]", *useridnr);
2743 				fail = 1;
2744 				break;
2745 			case DSN_CLASS_QUOTA:
2746 				TRACE(TRACE_NOTICE, "mailbox over quota, message rejected for useridnr [%" PRIu64 "]", *useridnr);
2747 				fail_quota = 1;
2748 				break;
2749 			case DSN_CLASS_TEMP:
2750 			default:
2751 				TRACE(TRACE_ERR, "unknown temporary failure in sort_and_deliver for useridnr [%" PRIu64 "]", *useridnr);
2752 				temp = 1;
2753 				break;
2754 			}
2755 
2756 			/* Automatic reply and notification */
2757 			if (execute_auto_ran(message, *useridnr) < 0) {
2758 				TRACE(TRACE_ERR, "error in execute_auto_ran(), but continuing delivery normally.");
2759 			}
2760 
2761 			if (! g_list_next(userids))
2762 				break;
2763 			userids = g_list_next(userids);
2764 
2765 		}
2766 
2767 		final_dsn.class = dsnuser_worstcase_int(ok, temp, fail, fail_quota);
2768 		switch (final_dsn.class) {
2769 		case DSN_CLASS_OK:
2770 			/* Success. Address related. Valid. */
2771 			set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
2772 			break;
2773 		case DSN_CLASS_TEMP:
2774 			/* sort_and_deliver returns TEMP is useridnr is 0, aka,
2775 			 * if nothing was delivered at all, or for any other failures. */
2776 
2777 			/* If there's a problem with the delivery address, but
2778 			 * there are proper forwarding addresses, we're OK. */
2779 			if (g_list_length(delivery->forwards) > 0) {
2780 				/* Success. Address related. Valid. */
2781 				set_dsn(&delivery->dsn, DSN_CLASS_OK, 1, 5);
2782 				break;
2783 			}
2784 			/* Fall through to FAIL. */
2785 		case DSN_CLASS_FAIL:
2786 			/* Permanent failure. Address related. Does not exist. */
2787 			set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 1, 1);
2788 			break;
2789 		case DSN_CLASS_QUOTA:
2790 			/* Failure. Mailbox related. Over quota limit. */
2791 			if (quota_softfail)
2792 				set_dsn(&delivery->dsn, DSN_CLASS_TEMP, 2, 2);
2793 			else
2794 				set_dsn(&delivery->dsn, DSN_CLASS_FAIL, 2, 2);
2795 			break;
2796 		case DSN_CLASS_NONE:
2797 			/* Leave the DSN status at whatever dsnuser_resolve set it at. */
2798 			break;
2799 		}
2800 
2801 		TRACE(TRACE_DEBUG, "deliver [%u] messages to external addresses", g_list_length(delivery->forwards));
2802 
2803 		/* Each user may also have a list of external forwarding addresses. */
2804 		if (g_list_length(delivery->forwards) > 0) {
2805 
2806 			TRACE(TRACE_DEBUG, "delivering to external addresses");
2807 			const char *from = dbmail_message_get_header(message, "Return-Path");
2808 
2809 			/* Forward using the temporary stored message. */
2810 			if (send_forward_list(message, delivery->forwards, from)) {
2811 				/* If forward fails, tell the sender that we're
2812 				 * having a transient error. They'll resend. */
2813 				TRACE(TRACE_NOTICE, "forwaring failed, reporting transient error.");
2814 				set_dsn(&delivery->dsn, DSN_CLASS_TEMP, 1, 1);
2815 			}
2816 		}
2817 		if (! p_list_next(dsnusers))
2818 			break;
2819 		dsnusers = p_list_next(dsnusers);
2820 
2821 	}
2822 
2823 	/* Always delete the temporary message, even if the delivery failed.
2824 	 * It is the MTA's job to requeue or bounce the message,
2825 	 * and our job to keep a tidy database ;-) */
2826 	if (! db_delete_message(tmpid))
2827 		TRACE(TRACE_ERR, "failed to delete temporary message [%" PRIu64 "]", tmpid);
2828 	TRACE(TRACE_DEBUG, "temporary message deleted from database. Done.");
2829 
2830 	return 0;
2831 }
2832 
2833