1 /* test.c -- test and example for sieve version 2 api
2  * Aaron Stone
3  * $Id$
4  *
5  * usage: "test message script"
6  */
7 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
8  *                                                                       *
9  * As an exception to the LGPL license which applies to the libSieve     *
10  * library as a whole, this file is released under the "MIT License"     *
11  * so as to promote use of this work as a generic template for both      *
12  * free and proprietary software which make use of the libSieve library. *
13  *                                                                       *
14  * Copyright (c) 2005 Aaron Stone                                        *
15  *                                                                       *
16  * Permission is hereby granted, free of charge, to any person obtaining *
17  * a copy of this software and associated documentation files (the       *
18  * "Software"), to deal in the Software without restriction, including   *
19  * without limitation the rights to use, copy, modify, merge, publish,   *
20  * distribute, sublicense, and/or sell copies of the Software, and to    *
21  * permit persons to whom the Software is furnished to do so, subject    *
22  * to the following conditions:                                          *
23  *                                                                       *
24  * The above copyright notice and this permission notice shall be        *
25  * included in all copies or substantial portions of the Software.       *
26  *                                                                       *
27  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
28  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
29  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
30  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  *
31  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  *
32  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     *
33  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                *
34  *                                                                       *
35  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
36 
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif
40 
41 #include <stdio.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <fcntl.h>
46 #include <ctype.h>
47 #include <string.h>
48 #include <stdlib.h>
49 
50 #include "sieve2.h"
51 #include "sieve2_error.h"
52 
53 struct freelist {
54 	void *free;
55 	void *next;
56 };
57 
58 struct my_context {
59 	const int m_size;
60 	char *m_buf;
61 	char *s_buf;
62 	char *scriptfile;
63 	int error_runtime;
64 	int error_parse;
65 	int actiontaken;
66 	struct freelist *freelist;
67 };
68 
69 static int read_file(char *filename, char **ret_buf,
70 	int (* terminator)(char *buf, int pos) );
71 static int end_of_nothing(char *buf, int pos);
72 static int end_of_header(char *buf, int pos);
73 
74 static int debug = 0;
my_debug(sieve2_context_t * s,void * my)75 int my_debug(sieve2_context_t *s, void *my)
76 {
77 	if (debug) {
78 		printf("Debug: level [%d] module [%s] file [%s] function [%s] message [%s]\n",
79 			sieve2_getvalue_int(s, "level"),
80 			sieve2_getvalue_string(s, "module"),
81 			sieve2_getvalue_string(s, "file"),
82 			sieve2_getvalue_string(s, "function"),
83 			sieve2_getvalue_string(s, "message"));
84 	}
85 	return SIEVE2_OK;
86 }
87 
my_notify(sieve2_context_t * s,void * my)88 int my_notify(sieve2_context_t *s, void *my)
89 {
90 	struct my_context *m = (struct my_context *)my;
91 	char ** options;
92 	int i;
93 
94 	printf( "Action is NOTIFY: \n" );
95 	printf( "  ID \"%s\" is %s\n",
96 		sieve2_getvalue_string(s, "id"),
97 		sieve2_getvalue_string(s, "active"));
98 	printf( "    Method is %s\n",
99 		sieve2_getvalue_string(s, "method"));
100 	printf( "    Priority is %s\n",
101 		sieve2_getvalue_string(s, "priority"));
102 	printf( "    Message is %s\n",
103 		sieve2_getvalue_string(s, "message"));
104 
105 	options = sieve2_getvalue_stringlist(s, "options");
106 	if (!options)
107 		return SIEVE2_ERROR_BADARGS;
108 	for (i = 0; options[i] != NULL; i++) {
109 		printf( "    Options are %s\n", options[i] );
110 	}
111 
112 	m->actiontaken = 1;
113 	return SIEVE2_OK;
114 }
115 
my_vacation(sieve2_context_t * s,void * my)116 int my_vacation(sieve2_context_t *s, void *my)
117 {
118 	struct my_context *m = (struct my_context *)my;
119 	int yn;
120 
121 	/* Ask for the message hash, the days parameters, etc. */
122 	fprintf(stderr, "Have I already responded to '%s' in the past %d days?\n",
123 		sieve2_getvalue_string(s, "hash"),
124 		sieve2_getvalue_int(s, "days") );
125 
126 	yn = getchar();
127 
128 	/* Check in our 'database' to see if there's a match. */
129 	if (yn == 'y' || yn == 'Y') {
130 		printf( "Ok, not sending a vacation response.\n" );
131 	}
132 
133 	/* If so, do nothing. If not, send a vacation and log it. */
134 	printf("echo '%s' | mail -s '%s' '%s' for message '%s'\n",
135 		sieve2_getvalue_string(s, "message"),
136 		sieve2_getvalue_string(s, "subject"),
137 		sieve2_getvalue_string(s, "address"),
138 		sieve2_getvalue_string(s, "name") );
139 
140 	m->actiontaken = 1;
141 	return SIEVE2_OK;
142 }
143 
my_redirect(sieve2_context_t * s,void * my)144 int my_redirect(sieve2_context_t *s, void *my)
145 {
146 	struct my_context *m = (struct my_context *)my;
147 
148 	printf( "Action is REDIRECT: \n" );
149 	printf( "  Destination is [%s]\n",
150 		sieve2_getvalue_string(s, "address"));
151 
152 	m->actiontaken = 1;
153 	return SIEVE2_OK;
154 }
155 
my_reject(sieve2_context_t * s,void * my)156 int my_reject(sieve2_context_t *s, void *my)
157 {
158 	struct my_context *m = (struct my_context *)my;
159 
160 	printf( "Action is REJECT: \n" );
161 	printf( "  Message is [%s]\n",
162 		sieve2_getvalue_string(s, "message"));
163 
164 	m->actiontaken = 1;
165 	return SIEVE2_OK;
166 }
167 
my_discard(sieve2_context_t * s,void * my)168 int my_discard(sieve2_context_t *s, void *my)
169 {
170 	struct my_context *m = (struct my_context *)my;
171 
172 	printf( "Action is DISCARD\n" );
173 
174 	m->actiontaken = 1;
175 	return SIEVE2_OK;
176 }
177 
my_fileinto(sieve2_context_t * s,void * my)178 int my_fileinto(sieve2_context_t *s, void *my)
179 {
180 	struct my_context *m = (struct my_context *)my;
181 	char ** flags;
182 	int i;
183 
184 	printf( "Action is KEEP or FILEINTO: \n" );
185 	printf( "  Destination is %s\n",
186 		sieve2_getvalue_string(s, "mailbox"));
187 	flags = sieve2_getvalue_stringlist(s, "flags");
188 	if (flags) {
189 		printf( "  Flags are:");
190 		for (i = 0; flags[i]; i++)
191 			printf( " %s", flags[i]);
192 		printf( ".\n");
193 	} else {
194 			printf( "  No flags specified.\n");
195 	}
196 
197 	m->actiontaken = 1;
198 	return SIEVE2_OK;
199 }
200 
my_keep(sieve2_context_t * s,void * my)201 int my_keep(sieve2_context_t *s, void *my)
202 {
203 	struct my_context *m = (struct my_context *)my;
204 
205 	printf( "Action is KEEP\n" );
206 
207 	m->actiontaken = 1;
208 	return SIEVE2_OK;
209 }
210 
my_errparse(sieve2_context_t * s,void * my)211 int my_errparse(sieve2_context_t *s, void *my)
212 {
213 	struct my_context *m = (struct my_context *)my;
214 
215 	printf( "Error is SCRIPT PARSE: " );
216 	printf( "  Line is %d\n",
217 		sieve2_getvalue_int(s, "lineno"));
218 	printf( "  Message is %s\n",
219 		sieve2_getvalue_string(s, "message"));
220 
221 	m->error_parse = 1;
222 	return SIEVE2_OK;
223 }
224 
my_erraddress(sieve2_context_t * s,void * my)225 int my_erraddress(sieve2_context_t *s, void *my)
226 {
227 //	struct my_context *m = (struct my_context *)my;
228 
229 	printf( "Error is ADDRESS PARSE: " );
230 	printf( "  Message is %s\n",
231 		sieve2_getvalue_string(s, "message"));
232 
233 //	m->error_parse = 1;
234 	return SIEVE2_OK;
235 }
236 
my_errheader(sieve2_context_t * s,void * my)237 int my_errheader(sieve2_context_t *s, void *my)
238 {
239 	struct my_context *m = (struct my_context *)my;
240 
241 	printf( "Error is HEADER PARSE: " );
242 	printf( "  Line is %d\n",
243 		sieve2_getvalue_int(s, "lineno"));
244 	printf( "  Message is %s\n",
245 		sieve2_getvalue_string(s, "message"));
246 
247 	m->error_parse = 1;
248 	return SIEVE2_OK;
249 }
250 
my_errexec(sieve2_context_t * s,void * my)251 int my_errexec(sieve2_context_t *s, void *my)
252 {
253 	struct my_context *m = (struct my_context *)my;
254 
255 	printf( "Error is EXEC: " );
256 	printf( "  Message is %s\n",
257 		sieve2_getvalue_string(s, "message"));
258 
259 	m->error_runtime = 1;
260 	return SIEVE2_OK;
261 }
262 
my_getscript(sieve2_context_t * s,void * my)263 int my_getscript(sieve2_context_t *s, void *my)
264 {
265 	struct my_context *m = (struct my_context *)my;
266 	const char * path, * name;
267 	int res;
268 
269 	/* Path could be :general, :personal, or empty. */
270 	path = sieve2_getvalue_string(s, "path");
271 
272 	/* If no file is named, we're looking for the main file. */
273 	name = sieve2_getvalue_string(s, "name");
274 
275 	if (path == NULL || name == NULL)
276 		return SIEVE2_ERROR_BADARGS;
277 
278 	if (strlen(path) && strlen(name)) {
279 		printf("Include requested from '%s' named '%s'\n",
280 			path, name);
281 	} else
282 	if (!strlen(path) && !strlen(name)) {
283 		/* If we're being called again, free what was here before. */
284 		if (m->s_buf) free(m->s_buf);
285 
286 		/* Read the script file given as an argument. */
287 		res = read_file(m->scriptfile, &m->s_buf, end_of_nothing);
288 		if (res != SIEVE2_OK) {
289 			printf("my_getscript: read_file() returns %d\n", res);
290 			return SIEVE2_ERROR_FAIL;
291 		}
292 		sieve2_setvalue_string(s, "script", m->s_buf);
293 	} else {
294 		return SIEVE2_ERROR_BADARGS;
295 	}
296 
297 	return SIEVE2_OK;
298 }
299 
my_getheaders(sieve2_context_t * s,void * my)300 int my_getheaders(sieve2_context_t *s, void *my)
301 {
302 	struct my_context *m = (struct my_context *)my;
303 
304 	sieve2_setvalue_string(s, "allheaders", m->m_buf);
305 
306 	return SIEVE2_OK;
307 }
308 
my_getheader(sieve2_context_t * s,void * my)309 int my_getheader(sieve2_context_t *s, void *my)
310 {
311 	// struct my_context *m = (struct my_context *)my;
312 
313 	printf( "Requested header [%s], returning NULL\n",
314 			sieve2_getvalue_string(s, "header") );
315 
316 	char * null[] = { NULL, NULL };
317 	sieve2_setvalue_stringlist(s, "body", null);
318 
319 	return SIEVE2_OK;
320 }
321 
322 /* Feed back null values as a crash test. */
my_getenvelope(sieve2_context_t * s,void * my)323 int my_getenvelope(sieve2_context_t *s, void *my)
324 {
325 	sieve2_setvalue_string(s, "to", "foo+AllowedBox@bar");
326 	sieve2_setvalue_string(s, "from", "from@nothing" );
327 
328 	return SIEVE2_OK;
329 //	return SIEVE2_ERROR_UNSUPPORTED;
330 }
331 
my_getbody(sieve2_context_t * s,void * my)332 int my_getbody(sieve2_context_t *s, void *my)
333 {
334 	return SIEVE2_ERROR_UNSUPPORTED;
335 }
336 
my_getsize(sieve2_context_t * s,void * my)337 int my_getsize(sieve2_context_t *s, void *my)
338 {
339 	struct my_context *m = (struct my_context *)my;
340 
341 	sieve2_setvalue_int(s, "size", m->m_size);
342 
343 	return SIEVE2_OK;
344 }
345 
346 // Calculate the address according to the mail system's specs.
my_getsubaddress(sieve2_context_t * s,void * my)347 int my_getsubaddress(sieve2_context_t *s, void *my)
348 {
349 	struct my_context *m = (struct my_context *)my;
350 	const char *address;
351 	char *user = NULL, *detail = NULL,
352 	     *localpart = NULL, *domain = NULL;
353 
354 	address = sieve2_getvalue_string(s, "address");
355 
356 	// In a real system, you have to watch this memory;
357 	// the client app owns it, not libSieve! You may
358 	// not permute the address parameter, either!
359 
360 	localpart = strdup(address);
361 	domain = strchr(localpart, '@');
362 	if (domain) {
363 		*domain = '\0';
364 		domain++;
365 	} else {
366 		// Malformed address.
367 	}
368 
369 	user = strdup(localpart);
370 	detail = strchr(user, '+');
371 	if (detail) {
372 		*detail = '\0';
373 		detail++;
374 	} else {
375 		// No detail present.
376 	}
377 
378 	sieve2_setvalue_string(s, "user", user);
379 	sieve2_setvalue_string(s, "detail", detail);
380 	sieve2_setvalue_string(s, "localpart", localpart);
381 	sieve2_setvalue_string(s, "domain", domain);
382 
383 	struct freelist *tmp = malloc(sizeof(struct freelist));
384 	tmp->next = m->freelist;
385 	tmp->free = user;
386 	m->freelist = tmp;
387 
388 	tmp = malloc(sizeof(struct freelist));
389 	tmp->next = m->freelist;
390 	tmp->free = localpart;
391 	m->freelist = tmp;
392 
393 	return SIEVE2_OK;
394 }
395 
396 /* END OF EXAMPLE SIEVE CALLBACKS */
397 
398 /* little function to check for end of a RFC 822 header. */
end_of_header(char * buf,int pos)399 static int end_of_header(char *buf, int pos)
400 {
401 	return ( (pos < 2 ? 0 :
402 			(buf[pos-1] == '\n' && buf[pos-2] == '\n'))
403 		|| (pos < 4 ? 0 :
404 			(buf[pos-1] == '\n' && buf[pos-2] == '\r'
405 			&& buf[pos-3] == '\n' && buf[pos-4] == '\r'))
406 	);
407 }
408 
409 /* little function to do not much of anything. */
end_of_nothing(char * buf,int pos)410 static int end_of_nothing(char *buf, int pos)
411 {
412 	return 0;
413 }
414 
read_file(char * filename,char ** ret_buf,int (* terminator)(char * buf,int pos))415 static int read_file(char *filename, char **ret_buf,
416 	int (* terminator)(char *buf, int pos) )
417 {
418 #define GROW_AMOUNT 200
419 	size_t f_len=0;
420 	size_t f_pos=0;
421 	char *tmp_buf;
422 	char *f_buf = NULL; // This one needs to initialize NULL!
423 	FILE *f;
424 
425 	if (!filename) {
426 		*ret_buf = f_buf;
427 		return SIEVE2_ERROR_FAIL;
428 	}
429 
430    	f = fopen(filename, "r");
431 		if (!f) {
432 		printf("Could not open file '%s'\n", filename);
433 		return 1;
434 		}
435 
436 	while(!feof(f) && !terminator(f_buf, f_pos)) {
437 		if( f_pos + 1 >= f_len ) {
438 			tmp_buf = realloc(f_buf,
439 				sizeof(char) * (f_len+=GROW_AMOUNT));
440 		if( tmp_buf != NULL )
441 			f_buf = tmp_buf;
442 		else
443 			return 1;
444 		}
445 		f_buf[f_pos] = fgetc(f);
446 		f_pos++;
447 	}
448 
449 	if(f_pos)
450 		f_buf[f_pos] = '\0';
451 
452 	fclose(f);
453 
454 	*ret_buf = f_buf;
455 	return SIEVE2_OK;
456 }
457 
458 /* END OF EXAMPLE FILE PROCESSING FUNCTIONS */
459 
460 /* CALLBACK REGISTRATION TABLE */
461 
462 sieve2_callback_t my_callbacks[] = {
463 { SIEVE2_DEBUG_TRACE,           my_debug         },
464 { SIEVE2_ERRCALL_PARSE,         my_errparse      },
465 { SIEVE2_ERRCALL_RUNTIME,       my_errexec       },
466 { SIEVE2_ERRCALL_ADDRESS,       my_erraddress    },
467 { SIEVE2_ERRCALL_HEADER,        my_errheader     },
468 { SIEVE2_ACTION_FILEINTO,       my_fileinto      },
469 { SIEVE2_ACTION_DISCARD,        my_discard       },
470 { SIEVE2_ACTION_REDIRECT,       my_redirect      },
471 { SIEVE2_ACTION_REJECT,         my_reject        },
472 { SIEVE2_ACTION_NOTIFY,         my_notify        },
473 { SIEVE2_ACTION_VACATION,       my_vacation      },
474 /* KEEP is essentially the default case of FILEINTO "INBOX". */
475 { SIEVE2_ACTION_KEEP,           my_fileinto      },
476 { SIEVE2_SCRIPT_GETSCRIPT,      my_getscript     },
477 /* We don't support one header at a time in this example. */
478 { SIEVE2_MESSAGE_GETHEADER,     NULL             },
479 //{ SIEVE2_MESSAGE_GETHEADER,     my_getheader   },
480 /* libSieve can parse headers itself, so we'll use that. */
481 { SIEVE2_MESSAGE_GETALLHEADERS, my_getheaders    },
482 { SIEVE2_MESSAGE_GETSUBADDRESS, my_getsubaddress },
483 { SIEVE2_MESSAGE_GETENVELOPE,   my_getenvelope   },
484 { SIEVE2_MESSAGE_GETBODY,       my_getbody       },
485 { SIEVE2_MESSAGE_GETSIZE,       my_getsize       },
486 { 0 } };
487 
main(int argc,char * argv[])488 int main(int argc, char *argv[])
489 {
490 	int usage_error = 0;
491 	int res, exitcode = 0, s, m;
492 	struct my_context *my_context;
493 	sieve2_context_t *sieve2_context;
494 	char *message = NULL, *script = NULL;
495 
496 	if (argc < 2) {
497 		usage_error = 1;
498 	} else {
499 		s = 1, m = 2; // Where to look in argv
500 		if (strcmp(argv[1], "-l") == 0) {
501 			printf("%s", sieve2_license());
502 			exitcode = 0;
503 			goto endnofree;
504 		} else if (strcmp(argv[1], "-c") == 0) {
505 			printf("%s", sieve2_credits());
506 			exitcode = 0;
507 			goto endnofree;
508 		} else {
509 			if (strcmp(argv[1], "-d") == 0) {
510 				debug = 1;
511 				s++, m++;
512 			}
513 			if (argc >= m) {
514 				script = argv[s];
515 				message = argv[m];
516 			} else if (argc >= s) {
517 				script = argv[s];
518 			} else {
519 				usage_error = 1;
520 			}
521 		}
522 	}
523 
524 	if (usage_error) {
525 		printf("Usage:\n");
526 		printf("%s script\n", argv[0]);
527 		printf("%s script message\n", argv[0]);
528 		exitcode = 1;
529 		goto endnofree;
530 	}
531 
532 	/* This is the locally-defined structure that will be
533 	 * passed as the user context into the sieve calls.
534 	 * It will be passed by libSieve into each callback.*/
535 	my_context = malloc(sizeof(struct my_context));
536 	if (!my_context) {
537 		exitcode = 1;
538 		goto endnofree;
539 	}
540 	memset(my_context, 0, sizeof(struct my_context));
541 
542 	if (script) {
543 		my_context->scriptfile = script;
544 	}
545 
546 	if (message) {
547 		res = read_file(message, &my_context->m_buf, end_of_header);
548 		if (res != SIEVE2_OK) {
549 			printf("Message: read_file() returns %d\n", res);
550 			exitcode = 1;
551 			goto freecontext;
552 		}
553 	}
554 
555 	res = sieve2_alloc(&sieve2_context);
556 	if (res != SIEVE2_OK) {
557 		printf("Error %d when calling sieve2_alloc: %s\n",
558 			res, sieve2_errstr(res));
559 		exitcode = 1;
560 		goto freesieve;
561 	}
562 
563 	res = sieve2_callbacks(sieve2_context, my_callbacks);
564 	if (res != SIEVE2_OK) {
565 		printf("Error %d when calling sieve2_callbacks: %s\n",
566 			res, sieve2_errstr(res));
567 		exitcode = 1;
568 		goto freesieve;
569 	}
570 
571 	printf("Validating script...");
572 	res = sieve2_validate(sieve2_context, my_context);
573 	if (res != SIEVE2_OK) {
574 		printf("Error %d when calling sieve2_validate: %s\n",
575 			res, sieve2_errstr(res));
576 		exitcode = 1;
577 		goto freesieve;
578 	}
579 	if (!my_context->error_parse)
580 		printf(" valid.\n");
581 	else
582 		printf(" not valid.\n");
583 
584 	if (message) {
585 		printf("Executing script...\n");
586 		res = sieve2_execute(sieve2_context, my_context);
587 		if (res != SIEVE2_OK) {
588 			printf("Error %d when calling sieve2_execute: %s\n",
589 				res, sieve2_errstr(res));
590 			exitcode = 1;
591 			goto freesieve;
592 		}
593 		if (!my_context->actiontaken) {
594 			printf("  no actions taken; keeping message.\n");
595 			my_keep(NULL, my_context);
596 		} else {
597 			printf("  actions taken; keep is cancelled.\n");
598 		}
599 	}
600 
601 	/* At this point the callbacks are called from within libSieve. */
602 
603 	exitcode |= my_context->error_parse;
604 	exitcode |= my_context->error_runtime;
605 
606 freesieve:
607 	res = sieve2_free(&sieve2_context);
608 	if (res != SIEVE2_OK) {
609 		printf("Error %d when calling sieve2_free: %s\n",
610 			res, sieve2_errstr(res));
611 		exitcode = 1;
612 	}
613 
614 freecontext:
615 	if (my_context->m_buf) free(my_context->m_buf);
616 	if (my_context->s_buf) free(my_context->s_buf);
617 
618 	while (my_context->freelist) {
619 		struct freelist *tmpnext = my_context->freelist->next;
620 		free(my_context->freelist->free);
621 		free(my_context->freelist);
622 		my_context->freelist = tmpnext;
623 	}
624 
625 	if (my_context) free(my_context);
626 
627 endnofree:
628 	return exitcode;
629 }
630 
631 /* END OF THE EXAMPLE */
632 
633