1 /* spmfilter - mail filtering framework
2 * Copyright (C) 2009-2013 Sebastian Jaekel, Axel Steiner and SpaceNet AG
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #define _GNU_SOURCE
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <dlfcn.h>
29
30 #include "smf_modules.h"
31 #include "smf_header.h"
32 #include "smf_envelope.h"
33 #include "smf_message.h"
34 #include "smf_nexthop.h"
35 #include "smf_trace.h"
36 #include "smf_internal.h"
37 #include "smf_dict.h"
38 #include "smf_smtp.h"
39
40 #define THIS_MODULE "modules"
41
message_file_mtime(SMFSession_T * session)42 static time_t message_file_mtime(SMFSession_T *session) {
43 struct stat fstat;
44
45 if (stat(session->message_file, &fstat) != 0) {
46 STRACE(TRACE_ERR, session->id, "%s: %s", session->message_file, strerror(errno));
47 return 0; /* Ignore... */
48 }
49
50 return fstat.st_mtime;
51 }
52
_header_destroy(void * data)53 void _header_destroy(void *data) {
54 SMFHeader_T *h = (SMFHeader_T *)data;
55 smf_header_free(h);
56 }
57
_copy_header(SMFListElem_T * elem)58 void *_copy_header(SMFListElem_T *elem) {
59 SMFHeader_T *o = (SMFHeader_T *)smf_list_data(elem);
60 SMFHeader_T *n = smf_header_new();
61 char *s = NULL;
62 int i;
63
64 s = smf_header_get_name(o);
65 smf_header_set_name(n, s);
66
67 for(i=0; i < smf_header_get_count(o); i++) {
68 s = smf_header_get_value(o, i);
69 smf_header_set_value(n, s, 0);
70 }
71
72 return n;
73 }
74
smf_modules_engine_load(SMFSettings_T * settings)75 int smf_modules_engine_load(SMFSettings_T *settings) {
76 void *module = NULL;
77 LoadEngine load_engine = NULL;
78 char *engine_path = NULL;
79 char *error = NULL;
80 int ret;
81
82 /* check if engine module starts with lib */
83 if (settings->lib_dir != NULL)
84 engine_path = smf_internal_build_module_path(settings->lib_dir, settings->engine);
85 else
86 engine_path = smf_internal_build_module_path(LIB_DIR, settings->engine);
87
88 if ((module = dlopen(engine_path, RTLD_LAZY)) == NULL) {
89 TRACE(TRACE_ERR,"failed to load library [%s]: %s", engine_path,dlerror());
90 free(engine_path);
91 return -1;
92 }
93 dlerror();
94
95 load_engine = dlsym(module,"load");
96 if ((error = dlerror()) != NULL) {
97 TRACE(TRACE_ERR,"library error: %s", error);
98 free(error);
99 free(engine_path);
100 return -1;
101 }
102
103 ret = load_engine(settings);
104
105 if (dlclose(module) != 0) {
106 TRACE(TRACE_ERR,"failed to unload module [%s]",engine_path);
107 }
108
109 free(engine_path);
110
111 return ret;
112 }
113
114 /* initialize the processing queue */
smf_modules_pqueue_init(int (* loaderr)(SMFSettings_T * settings,SMFSession_T * session),int (* processerr)(SMFSettings_T * settings,SMFSession_T * session,int retval),int (* nhoperr)(SMFSettings_T * settings,SMFSession_T * session))115 SMFProcessQueue_T *smf_modules_pqueue_init(
116 int (*loaderr)(SMFSettings_T *settings, SMFSession_T *session),
117 int (*processerr)(SMFSettings_T *settings, SMFSession_T *session, int retval),
118 int (*nhoperr)(SMFSettings_T *settings, SMFSession_T *session)) {
119 SMFProcessQueue_T *q;
120
121 q = (SMFProcessQueue_T *)calloc(1, sizeof(SMFProcessQueue_T));
122 if(q == NULL) {
123 TRACE(TRACE_ERR, "failed to allocate memory for process queue!");
124 return(NULL);
125 }
126
127 q->load_error = loaderr;
128 q->processing_error = processerr;
129 q->nexthop_error = nhoperr;
130 return q;
131 }
132
133 /* build full filename to modules states dir */
smf_modules_stf_path(SMFSettings_T * settings,SMFSession_T * session)134 static char *smf_modules_stf_path(SMFSettings_T *settings, SMFSession_T *session) {
135 char *hex = NULL;
136 char *mid = NULL;
137 char *buf = NULL;
138 SMFMessage_T *msg = NULL;
139 msg = smf_envelope_get_message(session->envelope);
140
141 mid = smf_message_get_message_id(msg);
142 hex = smf_core_md5sum(mid);
143 asprintf(&buf,"%s/%s.%s.modules", settings->queue_dir, session->id, hex);
144 free(hex);
145
146 return(buf);
147 }
148
149 /* load the list of processed modules from file */
smf_modules_stf_processed_modules(FILE * fh)150 static SMFDict_T *smf_modules_stf_processed_modules(FILE *fh) {
151 SMFDict_T *d;
152 char *buf = NULL;
153 size_t n;
154 char **parts = NULL;
155 int nparts;
156
157 d = smf_dict_new();
158 fseek(fh, 0, SEEK_SET); /* rewind the file */
159
160 while(getline(&buf,&n,fh) >= 0) {
161 parts = smf_core_strsplit(buf, ":", &nparts);
162 assert(nparts == 2);
163 smf_dict_set(d, parts[0], parts[1]);
164 }
165
166 free(buf);
167 if (parts != NULL) {
168 free(parts[0]);
169 free(parts[1]);
170 free(parts[2]);
171 free(parts);
172 }
173 return d;
174 }
175
176 /* write an entry to the state file */
smf_modules_stf_write_entry(FILE * fh,char * mod)177 static int smf_modules_stf_write_entry(FILE *fh, char *mod) {
178 fprintf(fh, "%s:ok\n", mod);
179 return(0);
180 }
181
smf_module_create(const char * name)182 SMFModule_T *smf_module_create(const char *name) {
183 return smf_module_create_callback(name, NULL);
184 }
185
smf_module_create_handle(const char * name)186 static void *smf_module_create_handle(const char *name) {
187 struct stat fstat;
188 void *handle;
189 char *path;
190
191 if (stat(name, &fstat) == 0 && S_ISREG(fstat.st_mode)) {
192 path = strdup(name);
193 } else if ((path = smf_internal_build_module_path(LIB_DIR, name)) == NULL) {
194 TRACE(TRACE_ERR, "failed to build module path for [%s]", name);
195 return NULL;
196 }
197
198 if ((handle = dlopen(path, RTLD_LAZY)) == NULL) {
199 TRACE(TRACE_ERR, "failed to load module [%s]: %s", name, dlerror());
200 free(path);
201 return NULL;
202 }
203
204 free(path);
205
206 return handle;
207 }
208
smf_module_create_callback(const char * name,ModuleLoadFunction callback)209 SMFModule_T *smf_module_create_callback(const char *name, ModuleLoadFunction callback) {
210 SMFModule_T *module;
211
212 assert(name);
213
214 if ((module = malloc(sizeof(SMFModule_T))) == NULL) {
215 return NULL;
216 }
217
218 module->name = strdup(name);
219
220 if (callback == NULL) {
221 module->type = 0;
222 module->u.handle = smf_module_create_handle(name);
223 } else {
224 module->type = 1;
225 module->u.callback = callback;
226 }
227
228 TRACE(TRACE_DEBUG, "module %s loaded", name);
229
230 return module;
231 }
232
smf_module_destroy(SMFModule_T * module)233 int smf_module_destroy(SMFModule_T *module) {
234 int result = 0;
235
236 assert(module);
237
238 if (module->type == 0) {
239 if (module->u.handle != NULL && dlclose(module->u.handle) != 0) {
240 TRACE(TRACE_ERR, "failed to unload module [%s]", module->name);
241 result = -1;
242 }
243 }
244
245 free(module->name);
246 free(module);
247
248 return result;
249 }
250
smf_module_invoke(SMFSettings_T * settings,SMFModule_T * module,SMFSession_T * session)251 int smf_module_invoke(SMFSettings_T *settings, SMFModule_T *module, SMFSession_T *session) {
252 ModuleLoadFunction runner;
253 time_t mtime_before, mtime_after;
254 int result;
255
256 assert(module);
257 assert(session);
258
259 if (module->type == 0) {
260 dlerror(); // Clear any errors
261 if ((runner = dlsym(module->u.handle, "load")) == NULL) {
262 TRACE(TRACE_ERR, "failed to locate 'load'-symbol in module '%s': %s",
263 module->name, dlerror());
264 return -1;
265 }
266 } else {
267 runner = module->u.callback;
268 }
269
270 if (session->message_file != NULL)
271 mtime_before = message_file_mtime(session);
272
273 result = runner(settings,session);
274
275 if (result == 0 && session->message_file != NULL) {
276 mtime_after = message_file_mtime(session);
277
278 if (mtime_after > mtime_before) {
279 // Spoolfile has change. Reload the message inside the session
280 SMFMessage_T *message_new = smf_message_new();
281 result = smf_message_from_file(&message_new, session->message_file, 0);
282
283 if (result == 0) {
284 smf_message_free(session->envelope->message);
285 session->envelope->message = message_new;
286 }
287 }
288 }
289
290 return result;
291 }
292
smf_modules_process(SMFProcessQueue_T * q,SMFSession_T * session,SMFSettings_T * settings)293 int smf_modules_process(
294 SMFProcessQueue_T *q, SMFSession_T *session, SMFSettings_T *settings) {
295 FILE *stfh = NULL;
296 char *stf_filename = NULL;
297 SMFDict_T *modlist;
298 SMFMessage_T *msg = NULL;
299 SMFList_T *initial_headers = NULL;
300 SMFListElem_T *elem = NULL;
301 SMFModule_T *curmod;
302 int ret = 0;
303 int mod_count;
304 char *header = NULL;
305 NexthopFunction nexthop;
306
307 /* initialize message file and load processed modules */
308 stf_filename = smf_modules_stf_path(settings,session);
309
310 stfh = fopen(stf_filename, "a+");
311 if(stfh == NULL) {
312 STRACE(TRACE_ERR, session->id, "failed to open message state file %s: %s (%d)", stf_filename, strerror(errno),errno);
313
314 if(stf_filename != NULL)
315 free(stf_filename);
316
317 return -1;
318 }
319
320 if (smf_list_new(&initial_headers,_header_destroy) != 0) {
321 STRACE(TRACE_ERR,session->id, "failed to create header list");
322 free(stf_filename);
323 fclose(stfh);
324 return -1;
325 }
326
327 msg = smf_envelope_get_message(session->envelope);
328 elem = smf_list_head(msg->headers);
329 while(elem != NULL) {
330 smf_list_append(initial_headers,_copy_header(elem));
331 elem = elem->next;
332 }
333
334 if (settings->add_header == 1)
335 asprintf(&header,"X-Spmfilter: ");
336
337 /* fetch user data */
338 if (smf_internal_fetch_user_data(settings,session) != 0)
339 STRACE(TRACE_ERR, session->id, "failed to load local user data");
340
341 mod_count = 0;
342 modlist = smf_modules_stf_processed_modules(stfh);
343 elem = smf_list_head(settings->modules);
344 while(elem != NULL) {
345 curmod = (SMFModule_T *)smf_list_data(elem);
346 elem = elem->next;
347
348 /* check if the module is in our modlist, if yes, the module has
349 * already been processed and can be skipped */
350 if(smf_dict_get(modlist, curmod->name) != NULL) {
351 STRACE(TRACE_INFO, session->id, "skipping module [%s]", curmod->name);
352 continue;
353 }
354
355 STRACE(TRACE_DEBUG,session->id,"invoke module [%s]", curmod->name);
356 ret = smf_module_invoke(settings, curmod, session);
357
358 if(ret != 0) {
359 ret = q->processing_error(settings,session,ret);
360
361 if(ret == 0) {
362 STRACE(TRACE_ERR, session->id, "module [%s] failed, stopping processing!", curmod->name);
363 smf_dict_free(modlist);
364 fclose(stfh);
365 free(stf_filename);
366 free(header);
367 smf_list_free(initial_headers);
368 return -1;
369 } else if(ret == 1) {
370 STRACE(TRACE_WARNING, session->id, "module [%s] stopped processing!", curmod->name);
371 smf_dict_free(modlist);
372 fclose(stfh);
373 if(unlink(stf_filename) != 0)
374 STRACE(TRACE_ERR,session->id,"Failed to unlink state file [%s]", stf_filename);
375 free(stf_filename);
376 free(header);
377 smf_list_free(initial_headers);
378 return 1;
379 } else if(ret == 2) {
380 STRACE(TRACE_DEBUG,session->id,"module [%s] stopped processing, turning to nexthop processing!",curmod->name);
381 break;
382 }
383 } else {
384 STRACE(TRACE_DEBUG, session->id, "module [%s] finished successfully", curmod->name);
385 smf_modules_stf_write_entry(stfh, curmod->name);
386 }
387
388 mod_count++;
389 if (settings->add_header == 1) {
390 if (mod_count == smf_list_size(settings->modules))
391 smf_core_strcat_printf(&header, "%s", curmod->name);
392 else
393 smf_core_strcat_printf(&header, "%s, ", curmod->name);
394 }
395 }
396
397 /* close file, cleanup modlist and remove state file */
398 STRACE(TRACE_DEBUG, session->id,"module processing finished successfully.");
399 fclose(stfh);
400 smf_dict_free(modlist);
401
402 if(unlink(stf_filename) != 0) {
403 STRACE(TRACE_ERR,session->id,"failed to unlink state file [%s]: %s (%d)", stf_filename,strerror(errno),errno);
404 }
405 free(stf_filename);
406
407 if ((ret == 0) || (ret == 2)) {
408 if (settings->add_header == 1) {
409 smf_message_set_header(msg, header);
410 free(header);
411 }
412
413 if ((ret = smf_modules_flush_dirty(settings,session,initial_headers)) != 0)
414 STRACE(TRACE_ERR,session->id,"message flush failed");
415
416 /* queue is done, if we're still here check for next hop and
417 * deliver
418 */
419 if (ret == 0 && (nexthop = smf_nexthop_find(settings)) != NULL) {
420 if ((ret = nexthop(settings, session)) != 0)
421 q->nexthop_error(settings, session);
422 }
423 }
424 smf_list_free(initial_headers);
425
426 return ret;
427 }
428
429
430 /** Flush modified message headers to queue file */
smf_modules_flush_dirty(SMFSettings_T * settings,SMFSession_T * session,SMFList_T * initial_headers)431 int smf_modules_flush_dirty(SMFSettings_T *settings, SMFSession_T *session, SMFList_T *initial_headers) {
432 SMFHeader_T *h_init = NULL;
433 SMFHeader_T *h_msg = NULL;
434 SMFListElem_T *elem_init = NULL;
435 SMFListElem_T *elem_msg = NULL;
436 SMFMessage_T *msg = NULL;
437 int dirty = 0;
438 int found = 0;
439 int i = 0;
440 char *value1 = NULL;
441 char *value2 = NULL;
442
443 STRACE(TRACE_DEBUG,session->id,"flushing header information to filesystem");
444
445 msg = smf_envelope_get_message(session->envelope);
446
447 elem_msg = smf_list_head(msg->headers);
448
449 /* check if message headers have changed during session */
450 if (msg->headers->size != initial_headers->size) {
451 dirty = 1;
452 } else {
453 while(elem_msg != NULL) {
454 h_msg = (SMFHeader_T *)smf_list_data(elem_msg);
455 found = 0;
456 elem_init = smf_list_head(initial_headers);
457 while (elem_init != NULL) {
458 h_init = (SMFHeader_T *)smf_list_data(elem_init);
459 /* ok, we found the header name in the initial list */
460 if (strcmp(h_msg->name,h_init->name)==0) {
461 found = 1;
462 /* lets check if the values are the same */
463 if (smf_header_get_count(h_msg) != smf_header_get_count(h_init)) {
464 dirty = 1;
465 } else {
466 for (i=0;i<smf_header_get_count(h_msg);i++) {
467 value1 = smf_header_get_value(h_msg,i);
468 value2 = smf_header_get_value(h_init,i);
469 if ((value1 != NULL) && (value2 !=NULL) && (strcmp(value1,value2) != 0)) {
470 dirty = 1;
471 }
472 }
473 }
474 break;
475 }
476 elem_init = elem_init->next;
477 }
478
479 if (found == 0) {
480 dirty = 1;
481 break;
482 }
483 elem_msg = elem_msg->next;
484 }
485 }
486
487 /* merge new headers with message content to new queue file */
488 if (dirty == 1) {
489 char tmpname[PATH_MAX];
490 int fd;
491 FILE *new = NULL;
492 FILE *old = NULL;
493
494 snprintf(tmpname, sizeof(tmpname), "%s/XXXXXX", settings->queue_dir);
495
496 if ((fd = mkstemp(tmpname)) == -1) {
497 STRACE(TRACE_ERR,session->id,"failed to create temporary file: %s (%d)",strerror(errno),errno);
498 return -1;
499 }
500
501 if (smf_message_to_fd(msg, fd) == -1) {
502 STRACE(TRACE_ERR,session->id,"unable to write temporary file [%s]: %s",tmpname, strerror(errno));
503 return -1;
504 }
505
506 close(fd);
507
508 if((new = fopen(tmpname, "a"))==NULL) {
509 STRACE(TRACE_ERR,session->id,"unable to open temporary file: %s (%d)",strerror(errno), errno);
510 return -1;
511 }
512
513 if((old = fopen(session->message_file, "r"))==NULL) {
514 STRACE(TRACE_ERR,session->id,"unable to open queue file: %s (%d)",strerror(errno), errno);
515 fclose(new);
516 return -1;
517 }
518
519 if (smf_message_write_skip_header(old, new) == -1) {
520 fclose(old);
521 fclose(new);
522 return -1;
523 }
524
525 fclose(old);
526 fclose(new);
527
528 if (unlink(session->message_file)!=0) {
529 STRACE(TRACE_ERR,session->id,"failed to remove queue file: %s (%d)",strerror(errno),errno);
530 return -1;
531 }
532
533 if (rename(tmpname,session->message_file)!=0) {
534 STRACE(TRACE_ERR,session->id,"failed to rename queue file: %s (%d)",strerror(errno),errno);
535 return -1;
536 }
537 }
538
539 return 0;
540 }
541