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, ¬ify_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