1 /*
2 
3  Copyright (C) 1999-2004 Aaron Stone aaron at serendipity dot cx
4 
5  This program is free software; you can redistribute it and/or
6  modify it under the terms of the GNU General Public License
7  as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later
9  version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, write to the Free Software
18  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 
20  *
21  * Functions for running user defined sorting rules
22  * on a message in the temporary store, usually
23  * just delivering the message to the user's INBOX
24  * ...unless they have fancy rules defined, that is :-)
25  *
26  */
27 
28 #include "dbmail.h"
29 
30 #define THIS_MODULE "sort"
31 
32 /* Used by us to keep track of libSieve. */
33 struct sort_context {
34 	char *s_buf;
35 	char *script;
36 	uint64_t user_idnr;
37 	DbmailMessage *message;
38 	struct sort_result *result;
39 	GList *freelist;
40 };
41 
42 /* Returned opaquely as type SortResult_T. */
43 struct sort_result {
44 	int cancelkeep;
45 	const char *mailbox;
46 	int reject;
47 	GString *rejectmsg;
48 	int error_runtime;
49 	int error_parse;
50 	GString *errormsg;
51 };
52 
53 /* [DELIVERY] SIEVE_* settings in dbmail.conf */
54 struct sort_sieve_config {
55 	int vacation;
56 	int notify;
57 	int debug;
58 };
59 
sort_sieve_get_config(struct sort_sieve_config * sieve_config)60 static void sort_sieve_get_config(struct sort_sieve_config *sieve_config)
61 {
62 	Field_T val;
63 
64 	assert(sieve_config != NULL);
65 
66 	sieve_config->vacation = 0;
67 	sieve_config->notify = 0;
68 	sieve_config->debug = 0;
69 
70 	config_get_value("SIEVE_VACATION", "DELIVERY", val);
71 	if (strcasecmp(val, "yes") == 0) {
72 		sieve_config->vacation = 1;
73 	}
74 
75 	config_get_value("SIEVE_NOTIFY", "DELIVERY", val);
76 	if (strcasecmp(val, "yes") == 0) {
77 		sieve_config->notify= 1;
78 	}
79 
80 	config_get_value("SIEVE_DEBUG", "DELIVERY", val);
81 	if (strcasecmp(val, "yes") == 0) {
82 		sieve_config->debug = 1;
83 	}
84 }
85 
86 /*
87  * Send a vacation message. FIXME: this should provide
88  * MIME support, to comply with the Sieve-Vacation spec.
89  */
send_vacation(DbmailMessage * message,const char * to,const char * from,const char * subject,const char * body,const char * handle)90 static int send_vacation(DbmailMessage *message,
91 		const char *to, const char *from,
92 		const char *subject, const char *body, const char *handle)
93 {
94 	int result;
95 	const char *x_dbmail_vacation = dbmail_message_get_header(message, "X-Dbmail-Vacation");
96 
97 	if (x_dbmail_vacation) {
98 		TRACE(TRACE_NOTICE, "vacation loop detected [%s]", x_dbmail_vacation);
99 		return 0;
100 	}
101 
102 	DbmailMessage *new_message = dbmail_message_new(message->pool);
103 	new_message = dbmail_message_construct(new_message, to, from, subject, body);
104 	dbmail_message_set_header(new_message, "X-DBMail-Vacation", handle);
105 
106 	result = send_mail(new_message, to, from, NULL, SENDMESSAGE, SENDMAIL);
107 
108 	dbmail_message_free(new_message);
109 
110 	return result;
111 }
112 
send_redirect(DbmailMessage * message,const char * to,const char * from)113 static int send_redirect(DbmailMessage *message, const char *to, const char *from)
114 {
115 	if (!to || !from) {
116 		TRACE(TRACE_ERR, "both To and From addresses must be specified");
117 		return -1;
118 	}
119 
120 	return send_mail(message, to, from, NULL, SENDRAW, SENDMAIL);
121 }
122 
send_alert(uint64_t user_idnr,char * subject,char * body)123 int send_alert(uint64_t user_idnr, char *subject, char *body)
124 {
125 	DbmailMessage *new_message;
126 	Field_T postmaster;
127 	char *from;
128 	int msgflags[IMAP_NFLAGS];
129 
130 	// Only send each unique alert once a day.
131 	char *tmp = g_strconcat(subject, body, NULL);
132 	char *userchar = g_strdup_printf("%" PRIu64 "", user_idnr);
133 	char handle[FIELDSIZE];
134 
135 	memset(handle, 0, sizeof(handle));
136        	dm_md5(tmp, handle);
137 	if (db_replycache_validate(userchar, "send_alert", handle, 1) != DM_SUCCESS) {
138 		TRACE(TRACE_INFO, "Already sent alert [%s] to user [%" PRIu64 "] today", subject, user_idnr);
139 		g_free(userchar);
140 		g_free(tmp);
141 		return 0;
142 	} else {
143 		TRACE(TRACE_INFO, "Sending alert [%s] to user [%" PRIu64 "]", subject, user_idnr);
144 		db_replycache_register(userchar, "send_alert", handle);
145 		g_free(userchar);
146 		g_free(tmp);
147 	}
148 
149 	// From the Postmaster.
150 	if (config_get_value("POSTMASTER", "DBMAIL", postmaster) < 0) {
151 		TRACE(TRACE_NOTICE, "no config value for POSTMASTER");
152 	}
153 	if (strlen(postmaster))
154 		from = postmaster;
155 	else
156 		from = DEFAULT_POSTMASTER;
157 
158 	// Set the \Flagged flag.
159 	memset(msgflags, 0, sizeof(msgflags));
160 	msgflags[IMAP_FLAG_FLAGGED] = 1;
161 
162 	// Get the user's login name.
163 	char *to = auth_get_userid(user_idnr);
164 
165 	new_message = dbmail_message_new(NULL);
166 	new_message = dbmail_message_construct(new_message, to, from, subject, body);
167 
168 	// Pre-insert the message and get a new_message->id
169 	dbmail_message_store(new_message);
170 	uint64_t tmpid = new_message->id;
171 
172 	if (sort_deliver_to_mailbox(new_message, user_idnr,
173 			"INBOX", BOX_BRUTEFORCE, msgflags, NULL) != DSN_CLASS_OK) {
174 		TRACE(TRACE_ERR, "Unable to deliver alert [%s] to user [%" PRIu64 "]", subject, user_idnr);
175 	}
176 
177 	g_free(to);
178 	db_delete_message(tmpid);
179 	dbmail_message_free(new_message);
180 
181 	return 0;
182 }
183 
184 
185 /* SIEVE CALLBACKS */
186 
187 /*
188 From http://www.ietf.org/internet-drafts/draft-ietf-sieve-vacation-06.txt
189 
190    Usage:   vacation [":days" number] [":subject" string]
191                      [":from" string] [":addresses" string-list]
192                      [":mime"] [":handle" string] <reason: string>
193 
194    The parameters that an implementation needs to know about are:
195    days, subject, from, mime, handle, reason. Addresses is used
196    internally by libSieve implementation to comply with RFCs.
197 
198 We need to make sure to respect the implementation requirements.
199 */
sort_vacation(sieve2_context_t * s,void * my)200 int sort_vacation(sieve2_context_t *s, void *my)
201 {
202 	struct sort_context *m = (struct sort_context *)my;
203 	const char *message, *subject, *fromaddr, *handle;
204 	const char *rc_to, *rc_from;
205 	char rc_handle[FIELDSIZE];
206 	int days;
207 	//int mime;
208 
209 	days = sieve2_getvalue_int(s, "days");
210 	//mime = sieve2_getvalue_int(s, "mime"); // mime: 1 if message is mime coded. FIXME.
211 	message = sieve2_getvalue_string(s, "message");
212 	subject = sieve2_getvalue_string(s, "subject");
213 	fromaddr = sieve2_getvalue_string(s, "fromaddr"); // From: specified by the script.
214 	handle = sieve2_getvalue_string(s, "hash");
215 
216 	/* Default to a week, upper limit of a month.
217 	 * This is our only loop prevention mechanism! The value must be
218 	 * greater than 0, else the replycache code will always indicate
219 	 * that we haven't seen anything since 0 days ago... */
220 	if (days == 0) days = 7;
221 	if (days < 1) days = 1;
222 	if (days > 30) days = 30;
223 
224 	memset(rc_handle, 0, sizeof(rc_handle));
225 	dm_md5((char * const) handle, rc_handle);
226 
227 	// FIXME: should be validated as a user might try
228 	// to forge an address from their script.
229 	rc_from = fromaddr;
230 	if (!rc_from)
231 		rc_from = dbmail_message_get_header(m->message, "Delivered-To");
232 	if (!rc_from)
233 		rc_from = p_string_str(m->message->envelope_recipient);
234 
235 	rc_to = dbmail_message_get_header(m->message, "Reply-To");
236 	if (!rc_to)
237 		rc_to = dbmail_message_get_header(m->message, "Return-Path");
238 
239 	if (db_replycache_validate(rc_to, rc_from, rc_handle, days) == DM_SUCCESS) {
240 		if (send_vacation(m->message, rc_to, rc_from, subject, message, rc_handle) == 0)
241 			db_replycache_register(rc_to, rc_from, rc_handle);
242 		TRACE(TRACE_INFO, "Sending vacation to [%s] from [%s] handle [%s] repeat days [%d]",
243 			rc_to, rc_from, rc_handle, days);
244 	} else {
245 		TRACE(TRACE_INFO, "Vacation suppressed to [%s] from [%s] handle [%s] repeat days [%d]",
246 			rc_to, rc_from, rc_handle, days);
247 	}
248 
249 	m->result->cancelkeep = 0;
250 	return SIEVE2_OK;
251 }
252 
253 /*
254 From http://www.ietf.org/internet-drafts/draft-ietf-sieve-notify-07.txt
255 
256    Usage:  notify [":from" string] [":importance" <"1" / "2" / "3">]
257                   [":options" string-list] [":message" string] <method: string>
258 */
sort_notify(sieve2_context_t * s,void * my)259 int sort_notify(sieve2_context_t *s, void *my)
260 {
261 	struct sort_context *m = (struct sort_context *)my;
262 	const char *fromaddr;
263 	//const char *message;
264 	//const char *method;
265 	const char *rc_to, *rc_from;
266 	//int importance;
267 	//char * const * options;
268 
269 	fromaddr = sieve2_getvalue_string(s, "fromaddr");
270 	//method = sieve2_getvalue_string(s, "method");
271 	//message = sieve2_getvalue_string(s, "message");
272 	//importance = sieve2_getvalue_int(s, "importance");
273 	//options = sieve2_getvalue_stringlist(s, "options");
274 
275 	// FIXME: should be validated as a user might try
276 	// to forge an address from their script.
277 	rc_from = fromaddr;
278 	if (!rc_from)
279 		rc_from = dbmail_message_get_header(m->message, "Delivered-To");
280 	if (!rc_from)
281 		rc_from = p_string_str(m->message->envelope_recipient);
282 
283 	rc_to = dbmail_message_get_header(m->message, "Reply-To");
284 	if (!rc_to)
285 		rc_to = dbmail_message_get_header(m->message, "Return-Path");
286 
287 //	send_notification(m->message, rc_to, rc_from, method, message);
288 
289 	TRACE(TRACE_INFO, "Notification from [%s] to [%s] was not sent as notify is not supported in this release.", rc_from, rc_to);
290 
291 	return SIEVE2_OK;
292 }
293 
sort_redirect(sieve2_context_t * s,void * my)294 int sort_redirect(sieve2_context_t *s, void *my)
295 {
296 	struct sort_context *m = (struct sort_context *)my;
297 	const char *to;
298 	const char *from;
299 
300 	to = sieve2_getvalue_string(s, "address");
301 
302 	TRACE(TRACE_INFO, "Action is REDIRECT: REDIRECT destination is [%s].",
303 		to);
304 
305 	/* According to a clarification from the ietf-mta-filter mailing list,
306 	 * the redirect is supposed to be absolutely transparent: the envelope
307 	 * sender is the original envelope sender, with only the envelope
308 	 * recipient changed. As a fallback, we'll use the redirecting user. */
309 	from = dbmail_message_get_header(m->message, "Return-Path");
310 	if (!from)
311 		from = p_string_str(m->message->envelope_recipient);
312 
313 	if (send_redirect(m->message, to, from) != 0) {
314 		return SIEVE2_ERROR_FAIL;
315 	}
316 
317 	m->result->cancelkeep = 1;
318 	return SIEVE2_OK;
319 }
320 
sort_reject(sieve2_context_t * s,void * my)321 int sort_reject(sieve2_context_t *s, void *my)
322 {
323 	struct sort_context *m = (struct sort_context *)my;
324 	const char *message;
325 
326 	message = sieve2_getvalue_string(s, "message");
327 
328 	TRACE(TRACE_INFO, "Action is REJECT: REJECT message is [%s].", message);
329 
330 	m->result->rejectmsg = g_string_new(message);
331 
332 	/* Reject also discards. */
333 	m->result->cancelkeep = 1;
334 	m->result->reject = 1;
335 	return SIEVE2_OK;
336 }
337 
sort_discard(sieve2_context_t * s UNUSED,void * my)338 int sort_discard(sieve2_context_t *s UNUSED, void *my)
339 {
340 	struct sort_context *m = (struct sort_context *)my;
341 
342 	TRACE(TRACE_INFO, "Action is DISCARD.");
343 
344 	m->result->cancelkeep = 1;
345 	return SIEVE2_OK;
346 }
347 
sort_fileinto(sieve2_context_t * s,void * my)348 int sort_fileinto(sieve2_context_t *s, void *my)
349 {
350 	struct sort_context *m = (struct sort_context *)my;
351 	extern const char * imap_flag_desc[];
352 	char * const * flaglist;
353 	const char * mailbox;
354 	int msgflags[IMAP_NFLAGS];
355 	int *has_msgflags = NULL;
356 	GList *keywords = NULL;
357 	char *allflags = NULL;
358 	char **flags = NULL;
359 
360 	mailbox = sieve2_getvalue_string(s, "mailbox");
361 	flaglist = sieve2_getvalue_stringlist(s, "flags");
362 	allflags = g_strjoinv(" ", (char **)flaglist);
363 	flags = g_strsplit(allflags, " ", 0);
364 
365 	/* This condition exists for the KEEP callback. */
366 	if (! mailbox) {
367 		mailbox = "INBOX";
368 	}
369 
370 	TRACE(TRACE_INFO, "Action is FILEINTO: mailbox is [%s] flags are [%s]",
371 			mailbox, allflags);
372 
373 	/* If there were any imapflags, set them. */
374 	if (flags) {
375 		int i, j;
376 		memset(msgflags, 0, sizeof(msgflags));
377 
378 		// Loop through all script/user-specified flags.
379 		for (i = 0; flags[i]; i++) {
380 			// Find the ones we support.
381 			int baseflag = FALSE;
382 			char *flag = strrchr(flags[i], '\\');
383 			if (flag)
384 				flag++;
385 			else
386 				flag = flags[i];
387 
388 			for (j = 0; imap_flag_desc[j] && j < IMAP_NFLAGS; j++) {
389 				if (g_strcasestr(imap_flag_desc[j], flag)) {
390 					TRACE(TRACE_DEBUG, "set baseflag [%s]", flag);
391 					// Flag 'em.
392 					msgflags[j] = 1;
393 					baseflag = TRUE;
394 					// Only pass msgflags if we found something.
395 					has_msgflags = msgflags;
396 				}
397 			}
398 			if (! baseflag) {
399 				TRACE(TRACE_DEBUG, "set keyword [%s]", flag);
400 				keywords = g_list_append(keywords, g_strdup(flag));
401 			}
402 
403 		}
404 		g_strfreev(flags);
405 	}
406 	g_free(allflags);
407 
408 
409 	/* Don't cancel the keep if there's a problem storing the message. */
410 	if (sort_deliver_to_mailbox(m->message, m->user_idnr,
411 			mailbox, BOX_SORTING, has_msgflags, keywords) != DSN_CLASS_OK) {
412 		TRACE(TRACE_ERR, "Could not file message into mailbox; not cancelling keep.");
413 		m->result->cancelkeep = 0;
414 	} else {
415 		m->result->cancelkeep = 1;
416 	}
417 
418 	if (keywords)
419 		g_list_destroy(keywords);
420 
421 	return SIEVE2_OK;
422 }
423 
424 /* This should only happen if the user has uploaded an invalid script.
425  * Possible causes are a homebrew script uploader that does not do proper
426  * validation, libSieve's removal of a deprecated Sieve language feature,
427  * or perhaps some bugginess elsewhere. */
sort_errparse(sieve2_context_t * s,void * my)428 int sort_errparse(sieve2_context_t *s, void *my)
429 {
430 	struct sort_context *m = (struct sort_context *)my;
431 	const char *message;
432 	int lineno;
433 
434 	lineno = sieve2_getvalue_int(s, "lineno");
435 	message = sieve2_getvalue_string(s, "message");
436 
437 	TRACE(TRACE_INFO, "Error is PARSE: Line is [%d], Message is [%s]", lineno, message);
438 
439 	g_string_append_printf(m->result->errormsg, "Parse error on line [%d]: %s", lineno, message);
440 
441 	if (m->message) {
442 		char *alertbody = g_strdup_printf(
443 			"Your Sieve script [%s] failed to parse correctly.\n"
444 			"Messages will be delivered to your INBOX for now.\n"
445 			"The error message is:\n"
446 			"%s\n",
447 			m->script, message);
448 		send_alert(m->user_idnr, "Sieve script parse error", alertbody);
449 		g_free(alertbody);
450 	}
451 
452 	m->result->error_parse = 1;
453 	return SIEVE2_OK;
454 }
455 
sort_errexec(sieve2_context_t * s,void * my)456 int sort_errexec(sieve2_context_t *s, void *my)
457 {
458 	struct sort_context *m = (struct sort_context *)my;
459 	const char *message;
460 
461 	message = sieve2_getvalue_string(s, "message");
462 
463 	TRACE(TRACE_INFO, "Error is EXEC: Message is [%s]", message);
464 
465 	/* This turns out to be incredibly annoying, as libSieve
466 	 * throws execution errors on malformed addresses coming
467 	 * from the wild. As you might guess, that happens with
468 	 * greater than trivial frequency.
469 
470 	g_string_append_printf(m->result->errormsg, "Execution error: %s", message);
471 
472 	if (m->message) {
473 		char *alertbody = g_strdup_printf(
474 			"Your Sieve script [%s] failed to run correctly.\n"
475 			"Messages will be delivered to your INBOX for now.\n"
476 			"The error message is:\n"
477 			"%s\n",
478 			m->script, message);
479 		send_alert(m->user_idnr, "Sieve script run error", alertbody);
480 		g_free(alertbody);
481 	}
482 	*/
483 
484 	m->result->error_runtime = 1;
485 	return SIEVE2_OK;
486 }
487 
sort_getscript(sieve2_context_t * s,void * my)488 int sort_getscript(sieve2_context_t *s, void *my)
489 {
490 	struct sort_context *m = (struct sort_context *)my;
491 	const char * path, * name;
492 	int res;
493 
494 	/* Path could be :general, :personal, or empty. */
495 	path = sieve2_getvalue_string(s, "path");
496 
497 	/* If no file is named, we're looking for the main file. */
498 	name = sieve2_getvalue_string(s, "name");
499 
500 	if (path == NULL || name == NULL)
501 		return SIEVE2_ERROR_BADARGS;
502 
503 	if (strlen(path) && strlen(name)) {
504 		/* TODO: handle included files. */
505 		TRACE(TRACE_INFO, "Include requested from [%s] named [%s]", path, name);
506 	} else
507 	if (!strlen(path) && !strlen(name)) {
508 		/* Read the script file given as an argument. */
509 		TRACE(TRACE_INFO, "Getting default script named [%s]", m->script);
510 		res = dm_sievescript_getbyname(m->user_idnr, m->script, &m->s_buf);
511 		if (res != SIEVE2_OK) {
512 			TRACE(TRACE_ERR, "sort_getscript: read_file() returns %d\n", res);
513 			return SIEVE2_ERROR_FAIL;
514 		}
515 		sieve2_setvalue_string(s, "script", m->s_buf);
516 	} else {
517 		return SIEVE2_ERROR_BADARGS;
518 	}
519 
520 	return SIEVE2_OK;
521 }
522 
sort_getheader(sieve2_context_t * s,void * my)523 int sort_getheader(sieve2_context_t *s, void *my)
524 {
525 	struct sort_context *m = (struct sort_context *)my;
526 	char *header;
527 	char **bodylist;
528 	GList *headers;
529 	unsigned i;
530 
531 	header = (char *)sieve2_getvalue_string(s, "header");
532 
533 	headers = dbmail_message_get_header_repeated(m->message, header);
534 
535 	bodylist = g_new0(char *,g_list_length(headers)+1);
536 	i = 0;
537 	while (headers) {
538 		char *decoded = dbmail_iconv_decode_text((char *)headers->data);
539 		bodylist[i++] = decoded;
540 		/* queue the decoded value for freeing later on */
541 		m->freelist = g_list_prepend(m->freelist, decoded);
542 
543 		if (! g_list_next(headers))
544 			break;
545 		headers = g_list_next(headers);
546 	}
547 	g_list_free(g_list_first(headers));
548 
549 	/* We have to free the header array. */
550 	m->freelist = g_list_prepend(m->freelist, bodylist);
551 
552 	for (i = 0; bodylist[i] != NULL; i++) {
553 		TRACE(TRACE_INFO, "Getting header [%s] returning value [%s]",
554 			header, bodylist[i]);
555 	}
556 
557 	sieve2_setvalue_stringlist(s, "body", bodylist);
558 
559 	return SIEVE2_OK;
560 }
561 
562 /* Return both the to and from headers. */
sort_getenvelope(sieve2_context_t * s,void * my)563 int sort_getenvelope(sieve2_context_t *s, void *my)
564 {
565 	struct sort_context *m = (struct sort_context *)my;
566 	const char *to, *from;
567 
568 	to = dbmail_message_get_envelope_recipient(m->message);
569 	from = dbmail_message_get_header(m->message, "Return-Path");
570 
571 	TRACE(TRACE_DEBUG, "from [%s], to [%s]", from, to);
572 
573 	sieve2_setvalue_string(s, "to", (char *)to);
574 	sieve2_setvalue_string(s, "from", (char *)from);
575 
576 	return SIEVE2_OK;
577 }
578 
sort_getbody(sieve2_context_t * s UNUSED,void * my UNUSED)579 int sort_getbody(sieve2_context_t *s UNUSED, void *my UNUSED)
580 {
581 	return SIEVE2_ERROR_UNSUPPORTED;
582 }
583 
sort_getsize(sieve2_context_t * s,void * my)584 int sort_getsize(sieve2_context_t *s, void *my)
585 {
586 	struct sort_context *m = (struct sort_context *)my;
587 	int rfcsize;
588 
589 	rfcsize = dbmail_message_get_size(m->message, TRUE);
590 
591 	TRACE(TRACE_INFO, "Getting message size [%d]", rfcsize);
592 
593 	sieve2_setvalue_int(s, "size", rfcsize);
594 
595 	return SIEVE2_OK;
596 }
597 
sort_getsubaddress(sieve2_context_t * s,void * my)598 int sort_getsubaddress(sieve2_context_t *s, void *my)
599 {
600 	struct sort_context *m = (struct sort_context *)my;
601 	const char *address;
602 	char *user = NULL, *detail = NULL,
603 	     *localpart = NULL, *domain = NULL;
604 
605 	address = sieve2_getvalue_string(s, "address");
606 
607 	/* Simple address parsing. libSieve only shows us
608 	 * the localpart@domain portion, so we don't need
609 	 * to handle anything exciting or exotic here. */
610 	// TODO: Unify with the delivery chain subaddress code.
611 
612 	localpart = strdup(address);
613 	domain = strchr(localpart, '@');
614 	if (domain) {
615 		*domain = '\0';
616 		domain++;
617 	} else {
618 		// Malformed address.
619 	}
620 
621 	user = strdup(localpart);
622 	detail = strchr(user, '+');
623 	if (detail) {
624 		*detail = '\0';
625 		detail++;
626 	} else {
627 		// No detail present.
628 	}
629 
630 	sieve2_setvalue_string(s, "user", user);
631 	sieve2_setvalue_string(s, "detail", detail);
632 	sieve2_setvalue_string(s, "localpart", localpart);
633 	sieve2_setvalue_string(s, "domain", domain);
634 
635 	m->freelist = g_list_prepend(m->freelist, user);
636 	m->freelist = g_list_prepend(m->freelist, localpart);
637 
638 	return SIEVE2_OK;
639 }
640 
sort_debugtrace(sieve2_context_t * s,void * my UNUSED)641 int sort_debugtrace(sieve2_context_t *s, void *my UNUSED)
642 {
643 	int trace_level;
644 
645 	switch (sieve2_getvalue_int(s, "level")) {
646 		case 0:
647 		case 1:
648 		case 2:
649 			trace_level = TRACE_INFO;
650 			break;
651 		case 3:
652 		case 4:
653 		case 5:
654 		default:
655 			trace_level = TRACE_DEBUG;
656 			break;
657 	}
658 
659 	TRACE(trace_level, "sieve: [%s,%s,%s: [%s]\n",
660 			sieve2_getvalue_string(s, "module"),
661 			sieve2_getvalue_string(s, "file"),
662 			sieve2_getvalue_string(s, "function"),
663 			sieve2_getvalue_string(s, "message"));
664 
665 	return SIEVE2_OK;
666 }
667 
668 /* END OF CALLBACKS */
669 
670 
671 sieve2_callback_t vacation_callbacks[] = {
672 	{ SIEVE2_ACTION_VACATION,       sort_vacation      },
673 	{ 0, 0 } };
674 
675 sieve2_callback_t notify_callbacks[] = {
676 	{ SIEVE2_ACTION_NOTIFY,         sort_notify        },
677 	{ 0, 0 } };
678 
679 sieve2_callback_t debug_callbacks[] = {
680 	{ SIEVE2_DEBUG_TRACE,           sort_debugtrace    },
681 	{ 0, 0 } };
682 
683 sieve2_callback_t sort_callbacks[] = {
684 	{ SIEVE2_ERRCALL_RUNTIME,       sort_errexec       },
685 	{ SIEVE2_ERRCALL_PARSE,         sort_errparse      },
686 
687 	{ SIEVE2_ACTION_REDIRECT,       sort_redirect      },
688 	{ SIEVE2_ACTION_DISCARD,        sort_discard       },
689 	{ SIEVE2_ACTION_REJECT,         sort_reject        },
690 	{ SIEVE2_ACTION_FILEINTO,       sort_fileinto      },
691 	{ SIEVE2_ACTION_KEEP,           sort_fileinto      },
692 
693 	{ SIEVE2_SCRIPT_GETSCRIPT,      sort_getscript     },
694 	{ SIEVE2_MESSAGE_GETHEADER,     sort_getheader     },
695 	{ SIEVE2_MESSAGE_GETENVELOPE,   sort_getenvelope   },
696 	{ SIEVE2_MESSAGE_GETBODY,       sort_getbody       },
697 	{ SIEVE2_MESSAGE_GETSIZE,       sort_getsize       },
698 	{ SIEVE2_MESSAGE_GETSUBADDRESS, sort_getsubaddress },
699 	{ 0, 0 } };
700 
701 
sort_teardown(sieve2_context_t ** s2c,struct sort_context ** sc)702 static int sort_teardown(sieve2_context_t **s2c,
703 		struct sort_context **sc)
704 {
705 	assert(s2c != NULL);
706 	assert(sc != NULL);
707 
708 	sieve2_context_t *sieve2_context = *s2c;
709 	struct sort_context *sort_context = *sc;
710 	int res;
711 
712 	g_list_destroy(sort_context->freelist);
713 
714 	if (sort_context) {
715 		g_free(sort_context);
716 	}
717 
718 	res = sieve2_free(&sieve2_context);
719 	if (res != SIEVE2_OK) {
720 		TRACE(TRACE_ERR, "Error [%d] when calling sieve2_free: [%s]",
721 			res, sieve2_errstr(res));
722 		return DM_EGENERAL;
723 	}
724 
725 	*s2c = NULL;
726 	*sc = NULL;
727 
728 	return DM_SUCCESS;
729 }
730 
sort_startup(sieve2_context_t ** s2c,struct sort_context ** sc)731 static int sort_startup(sieve2_context_t **s2c,
732 		struct sort_context **sc)
733 {
734 	assert(s2c != NULL);
735 	assert(sc != NULL);
736 
737 	sieve2_context_t *sieve2_context = NULL;
738 	struct sort_context *sort_context = NULL;
739 	struct sort_sieve_config sieve_config;
740 	int res;
741 
742 	res = sieve2_alloc(&sieve2_context);
743 	if (res != SIEVE2_OK) {
744 		TRACE(TRACE_ERR, "Error [%d] when calling sieve2_alloc: [%s]",
745 			res, sieve2_errstr(res));
746 		return DM_EGENERAL;
747 	}
748 
749 	sort_sieve_get_config(&sieve_config);
750 
751 	res = sieve2_callbacks(sieve2_context, sort_callbacks);
752 	if (res != SIEVE2_OK) {
753 		TRACE(TRACE_ERR, "Error [%d] when calling sieve2_callbacks: [%s]",
754 			res, sieve2_errstr(res));
755 		sort_teardown(&sieve2_context, &sort_context);
756 		return DM_EGENERAL;
757 	}
758 	if (sieve_config.vacation) {
759 		TRACE(TRACE_DEBUG, "Sieve vacation enabled.");
760 		res = sieve2_callbacks(sieve2_context, vacation_callbacks);
761 		if (res != SIEVE2_OK) {
762 			TRACE(TRACE_ERR, "Error [%d] when calling sieve2_callbacks: [%s]",
763 				res, sieve2_errstr(res));
764 			sort_teardown(&sieve2_context, &sort_context);
765 			return DM_EGENERAL;
766 		}
767 	}
768 	if (sieve_config.notify) {
769 		TRACE(TRACE_INFO, "Sieve notify is not supported in this release.");
770 		res = sieve2_callbacks(sieve2_context, notify_callbacks);
771 		if (res != SIEVE2_OK) {
772 			TRACE(TRACE_ERR, "Error [%d] when calling sieve2_callbacks: [%s]",
773 				res, sieve2_errstr(res));
774 			sort_teardown(&sieve2_context, &sort_context);
775 			return DM_EGENERAL;
776 		}
777 	}
778 	if (sieve_config.debug) {
779 		TRACE(TRACE_DEBUG, "Sieve debugging enabled.");
780 		res = sieve2_callbacks(sieve2_context, debug_callbacks);
781 		if (res != SIEVE2_OK) {
782 			TRACE(TRACE_ERR, "Error [%d] when calling sieve2_callbacks: [%s]",
783 				res, sieve2_errstr(res));
784 			sort_teardown(&sieve2_context, &sort_context);
785 			return DM_EGENERAL;
786 		}
787 	}
788 
789 	sort_context = g_new0(struct sort_context, 1);
790 	if (!sort_context) {
791 		sort_teardown(&sieve2_context, &sort_context);
792 		return DM_EGENERAL;
793 	}
794 	memset(sort_context, 0, sizeof(struct sort_context));
795 
796 	sort_context->freelist = NULL;
797 
798 	*s2c = sieve2_context;
799 	*sc = sort_context;
800 
801 	return DM_SUCCESS;
802 }
803 
804 /* The caller is responsible for freeing memory here. */
sort_listextensions(void)805 const char * sort_listextensions(void)
806 {
807 	sieve2_context_t *sieve2_context;
808 	const char * extensions;
809 	struct sort_sieve_config sieve_config;
810 
811 	if (sieve2_alloc(&sieve2_context) != SIEVE2_OK)
812 		return NULL;
813 
814 	if (sieve2_callbacks(sieve2_context, sort_callbacks))
815 		return NULL;
816 
817 	sort_sieve_get_config(&sieve_config);
818 
819 	if (sieve_config.vacation) {
820 		TRACE(TRACE_DEBUG, "Sieve vacation enabled.");
821 		sieve2_callbacks(sieve2_context, vacation_callbacks);
822 	}
823 	if (sieve_config.notify) {
824 		TRACE(TRACE_ERR, "Sieve notify is not supported in this release.");
825 		sieve2_callbacks(sieve2_context, notify_callbacks);
826 	}
827 	if (sieve_config.debug) {
828 		TRACE(TRACE_DEBUG, "Sieve debugging enabled.");
829 		sieve2_callbacks(sieve2_context, debug_callbacks);
830 	}
831 
832 	/* This will be freed by sieve2_free. */
833 	extensions = sieve2_listextensions(sieve2_context);
834 
835 	/* So we'll make our own copy. */
836 	if (extensions)
837 		extensions = g_strstrip(g_strdup(extensions));
838 
839 	/* If this fails, then we don't care about the
840 	 * memory leak, because the program has to bomb out.
841 	 * It will not be possible to start libSieve up again
842 	 * if it does not free properly. */
843 	if (sieve2_free(&sieve2_context) != SIEVE2_OK)
844 		return NULL;
845 
846 	return extensions;
847 }
848 
849 /* Return 0 on script OK, 1 on script error, 2 on misc error. */
sort_validate(uint64_t user_idnr,char * scriptname)850 SortResult_T *sort_validate(uint64_t user_idnr, char *scriptname)
851 {
852 	int res, exitnull = 0;
853 	struct sort_result *result = NULL;
854 	sieve2_context_t *sieve2_context;
855 	struct sort_context *sort_context;
856 
857 	/* The contents of this function are taken from
858 	 * the libSieve distribution, sv_test/example.c,
859 	 * and are provided under an "MIT style" license.
860 	 * */
861 
862 	if (sort_startup(&sieve2_context, &sort_context) != DM_SUCCESS) {
863 		return NULL;
864 	}
865 
866 	sort_context->script = scriptname;
867 	sort_context->user_idnr = user_idnr;
868 	sort_context->result = g_new0(struct sort_result, 1);
869 	if (! sort_context->result) {
870 		return NULL;
871 	}
872 	sort_context->result->errormsg = g_string_new("");
873 
874 	res = sieve2_validate(sieve2_context, sort_context);
875 	if (res != SIEVE2_OK) {
876 		TRACE(TRACE_ERR, "Error %d when calling sieve2_validate: %s",
877 			res, sieve2_errstr(res));
878 		exitnull = 1;
879 		goto freesieve;
880 	}
881 
882 	/* At this point the callbacks are called from within libSieve. */
883 
884 freesieve:
885 	if (sort_context->s_buf)
886 		g_free(sort_context->s_buf);
887 
888 	if (exitnull)
889 		result = NULL;
890 	else
891 		result = sort_context->result;
892 
893 	sort_teardown(&sieve2_context, &sort_context);
894 
895 	return result;
896 }
897 
898 /* Pull up the relevant sieve scripts for this
899  * user and begin running them against the header
900  * and possibly the body of the message.
901  *
902  * Returns 0 on success, -1 on failure,
903  * and +1 on success but with memory leaking.
904  * In the +1 case, if called from a daemon
905  * such as dbmail-lmtpd, the daemon should
906  * finish storing the message and restart.
907  * */
sort_process(uint64_t user_idnr,DbmailMessage * message,const char * mailbox)908 SortResult_T *sort_process(uint64_t user_idnr, DbmailMessage *message, const char *mailbox)
909 {
910 	int res, exitnull = 0;
911 	struct sort_result *result = NULL;
912 	sieve2_context_t *sieve2_context;
913 	struct sort_context *sort_context;
914 
915 	/* The contents of this function are taken from
916 	 * the libSieve distribution, sv_test/example.c,
917 	 * and are provided under an "MIT style" license.
918 	 * */
919 
920 	if (sort_startup(&sieve2_context, &sort_context) != DM_SUCCESS) {
921 		return NULL;
922 	}
923 
924 	sort_context->message = message;
925 	sort_context->user_idnr = user_idnr;
926 	sort_context->result = g_new0(struct sort_result, 1);
927 	if (! sort_context->result) {
928 		exitnull = 1;
929 		goto freesieve;
930 	}
931 	sort_context->result->errormsg = g_string_new("");
932 	if (mailbox)
933 		sort_context->result->mailbox = mailbox;
934 
935 	res = dm_sievescript_get(user_idnr, &sort_context->script);
936 	if (res != 0) {
937 		TRACE(TRACE_ERR, "Error [%d] when calling db_getactive_sievescript", res);
938 		exitnull = 1;
939 		goto freesieve;
940 	}
941 	if (sort_context->script == NULL) {
942 		TRACE(TRACE_INFO, "User doesn't have any active sieve scripts.");
943 		exitnull = 1;
944 		goto freesieve;
945 	}
946 
947 	res = sieve2_execute(sieve2_context, sort_context);
948 	if (res != SIEVE2_OK) {
949 		TRACE(TRACE_ERR, "Error [%d] when calling sieve2_execute: [%s]",
950 			res, sieve2_errstr(res));
951 		exitnull = 1;
952 	}
953 	if (! sort_context->result->cancelkeep) {
954 		TRACE(TRACE_INFO, "No actions taken; message must be kept.");
955 	}
956 
957 	/* At this point the callbacks are called from within libSieve. */
958 
959 freesieve:
960 	if (sort_context->s_buf)
961 		g_free(sort_context->s_buf);
962 	if (sort_context->script)
963 		g_free(sort_context->script);
964 
965 	if (exitnull)
966 		result = NULL;
967 	else
968 		result = sort_context->result;
969 
970 	sort_teardown(&sieve2_context, &sort_context);
971 
972 	return result;
973 }
974 
975 /* SORT RESULT INTERFACE */
976 
sort_free_result(SortResult_T * result)977 void sort_free_result(SortResult_T *result)
978 {
979 	if (result == NULL) return;
980 	if (result->errormsg != NULL)
981 		g_string_free(result->errormsg, TRUE);
982 	if (result->rejectmsg != NULL)
983 		g_string_free(result->rejectmsg, TRUE);
984 	g_free(result);
985 }
986 
sort_get_cancelkeep(SortResult_T * result)987 int sort_get_cancelkeep(SortResult_T *result)
988 {
989 	if (result == NULL) return 0;
990 	return result->cancelkeep;
991 }
992 
sort_get_mailbox(SortResult_T * result)993 const char * sort_get_mailbox(SortResult_T *result)
994 {
995 	if (result == NULL) return NULL;
996 	return result->mailbox;
997 }
998 
sort_get_reject(SortResult_T * result)999 int sort_get_reject(SortResult_T *result)
1000 {
1001 	if (result == NULL) return 0;
1002 	return result->reject;
1003 }
1004 
sort_get_rejectmsg(SortResult_T * result)1005 const char *sort_get_rejectmsg(SortResult_T *result)
1006 {
1007 	if (result == NULL) return NULL;
1008 	return result->rejectmsg->str;
1009 }
1010 
sort_get_error(SortResult_T * result)1011 int sort_get_error(SortResult_T *result)
1012 {
1013 	if (result == NULL) return 0;
1014 	return result->errormsg->len;
1015 }
1016 
sort_get_errormsg(SortResult_T * result)1017 const char * sort_get_errormsg(SortResult_T *result)
1018 {
1019 	if (result == NULL) return NULL;
1020 	return result->errormsg->str;
1021 }
1022 
1023