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