Merge branch 'collectd-3.11'
[collectd.git] / src / email.c
1 /**
2  * collectd - src/email.c
3  * Copyright (C) 2006,2007  Sebastian Harl
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Author:
19  *   Sebastian Harl <sh at tokkee.org>
20  **/
21
22 /*
23  * This plugin communicates with a spam filter, a virus scanner or similar
24  * software using a UNIX socket and a very simple protocol:
25  *
26  * e-mail type (e.g. ham, spam, virus, ...) and size
27  * e:<type>:<bytes>
28  *
29  * spam score
30  * s:<value>
31  *
32  * successful spam checks
33  * c:<type1>[,<type2>,...]
34  */
35
36 #include "collectd.h"
37 #include "common.h"
38 #include "plugin.h"
39
40 #include "configfile.h"
41
42 #if HAVE_LIBPTHREAD
43 # include <pthread.h>
44 #endif
45
46 #include <sys/socket.h>
47 #include <sys/un.h>
48 #include <sys/select.h>
49
50 /* some systems (e.g. Darwin) seem to not define UNIX_PATH_MAX at all */
51 #ifndef UNIX_PATH_MAX
52 # define UNIX_PATH_MAX sizeof (((struct sockaddr_un *)0)->sun_path)
53 #endif /* UNIX_PATH_MAX */
54
55 #if HAVE_GRP_H
56 #       include <grp.h>
57 #endif /* HAVE_GRP_H */
58
59 #define MODULE_NAME "email"
60
61 /* 256 bytes ought to be enough for anybody ;-) */
62 #define BUFSIZE 256
63
64 #ifndef COLLECTD_SOCKET_PREFIX
65 # define COLLECTD_SOCKET_PREFIX "/tmp/.collectd-"
66 #endif /* COLLECTD_SOCKET_PREFIX */
67
68 #define SOCK_PATH COLLECTD_SOCKET_PREFIX"email"
69 #define MAX_CONNS 5
70 #define MAX_CONNS_LIMIT 16384
71
72 #define log_err(...) ERROR (MODULE_NAME": "__VA_ARGS__)
73 #define log_warn(...) WARNING (MODULE_NAME": "__VA_ARGS__)
74
75 /*
76  * Private data structures
77  */
78 /* linked list of email and check types */
79 typedef struct type {
80         char        *name;
81         int         value;
82         struct type *next;
83 } type_t;
84
85 typedef struct {
86         type_t *head;
87         type_t *tail;
88 } type_list_t;
89
90 /* collector thread control information */
91 typedef struct collector {
92         pthread_t thread;
93
94         /* socket descriptor of the current/last connection */
95         int socket;
96 } collector_t;
97
98 /* linked list of pending connections */
99 typedef struct conn {
100         /* socket to read data from */
101         int socket;
102
103         /* buffer to read data to */
104         char *buffer;
105         int  idx; /* current write position in buffer */
106         int  length; /* length of the current line, i.e. index of '\0' */
107
108         struct conn *next;
109 } conn_t;
110
111 typedef struct {
112         conn_t *head;
113         conn_t *tail;
114 } conn_list_t;
115
116 /*
117  * Private variables
118  */
119 /* valid configuration file keys */
120 static const char *config_keys[] =
121 {
122         "SocketGroup",
123         "SocketPerms",
124         "MaxConns"
125 };
126 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);
127
128 /* socket configuration */
129 static char *sock_group = COLLECTD_GRP_NAME;
130 static int  sock_perms  = S_IRWXU | S_IRWXG;
131 static int  max_conns   = MAX_CONNS;
132
133 /* state of the plugin */
134 static int disabled = 0;
135
136 /* thread managing "client" connections */
137 static pthread_t connector = (pthread_t) 0;
138 static int connector_socket = -1;
139
140 /* tell the collector threads that a new connection is available */
141 static pthread_cond_t conn_available = PTHREAD_COND_INITIALIZER;
142
143 /* connections that are waiting to be processed */
144 static pthread_mutex_t conns_mutex = PTHREAD_MUTEX_INITIALIZER;
145 static conn_list_t conns;
146
147 /* tell the connector thread that a collector is available */
148 static pthread_cond_t collector_available = PTHREAD_COND_INITIALIZER;
149
150 /* collector threads */
151 static collector_t **collectors = NULL;
152
153 static pthread_mutex_t available_mutex = PTHREAD_MUTEX_INITIALIZER;
154 static int available_collectors;
155
156 static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;
157 static type_list_t count;
158
159 static pthread_mutex_t size_mutex = PTHREAD_MUTEX_INITIALIZER;
160 static type_list_t size;
161
162 static pthread_mutex_t score_mutex = PTHREAD_MUTEX_INITIALIZER;
163 static double score;
164 static int score_count;
165
166 static pthread_mutex_t check_mutex = PTHREAD_MUTEX_INITIALIZER;
167 static type_list_t check;
168
169 /*
170  * Private functions
171  */
172 static int email_config (const char *key, const char *value)
173 {
174         if (0 == strcasecmp (key, "SocketGroup")) {
175                 sock_group = sstrdup (value);
176         }
177         else if (0 == strcasecmp (key, "SocketPerms")) {
178                 /* the user is responsible for providing reasonable values */
179                 sock_perms = (int)strtol (value, NULL, 8);
180         }
181         else if (0 == strcasecmp (key, "MaxConns")) {
182                 long int tmp = strtol (value, NULL, 0);
183
184                 if (tmp < 1) {
185                         fprintf (stderr, "email plugin: `MaxConns' was set to invalid "
186                                         "value %li, will use default %i.\n",
187                                         tmp, MAX_CONNS);
188                         max_conns = MAX_CONNS;
189                 }
190                 else if (tmp > MAX_CONNS_LIMIT) {
191                         fprintf (stderr, "email plugin: `MaxConns' was set to invalid "
192                                         "value %li, will use hardcoded limit %i.\n",
193                                         tmp, MAX_CONNS_LIMIT);
194                         max_conns = MAX_CONNS_LIMIT;
195                 }
196                 else {
197                         max_conns = (int)tmp;
198                 }
199         }
200         else {
201                 return -1;
202         }
203         return 0;
204 } /* static int email_config (char *, char *) */
205
206 /* Increment the value of the given name in the given list by incr. */
207 static void type_list_incr (type_list_t *list, char *name, int incr)
208 {
209         if (NULL == list->head) {
210                 list->head = (type_t *)smalloc (sizeof (type_t));
211
212                 list->head->name  = sstrdup (name);
213                 list->head->value = incr;
214                 list->head->next  = NULL;
215
216                 list->tail = list->head;
217         }
218         else {
219                 type_t *ptr;
220
221                 for (ptr = list->head; NULL != ptr; ptr = ptr->next) {
222                         if (0 == strcmp (name, ptr->name))
223                                 break;
224                 }
225
226                 if (NULL == ptr) {
227                         list->tail->next = (type_t *)smalloc (sizeof (type_t));
228                         list->tail = list->tail->next;
229
230                         list->tail->name  = sstrdup (name);
231                         list->tail->value = incr;
232                         list->tail->next  = NULL;
233                 }
234                 else {
235                         ptr->value += incr;
236                 }
237         }
238         return;
239 } /* static void type_list_incr (type_list_t *, char *) */
240
241 /* Read a single character from the socket. If an error occurs or end-of-file
242  * is reached return '\0'. */
243 static char read_char (conn_t *src)
244 {
245         char ret = '\0';
246
247         fd_set fdset;
248
249         FD_ZERO (&fdset);
250         FD_SET (src->socket, &fdset);
251
252         if (-1 == select (src->socket + 1, &fdset, NULL, NULL, NULL)) {
253                 char errbuf[1024];
254                 log_err ("select() failed: %s",
255                                 sstrerror (errno, errbuf, sizeof (errbuf)));
256                 return '\0';
257         }
258
259         assert (FD_ISSET (src->socket, &fdset));
260
261         do {
262                 ssize_t len = 0;
263
264                 errno = 0;
265                 if (0 > (len = read (src->socket, (void *)&ret, 1))) {
266                         if (EINTR != errno) {
267                                 char errbuf[1024];
268                                 log_err ("read() failed: %s",
269                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
270                                 return '\0';
271                         }
272                 }
273
274                 if (0 == len)
275                         return '\0';
276         } while (EINTR == errno);
277         return ret;
278 } /* static char read_char (conn_t *) */
279
280 /* Read a single line (terminated by '\n') from the the socket.
281  *
282  * The return value is zero terminated and does not contain any newline
283  * characters.
284  *
285  * If an error occurs or end-of-file is reached return NULL.
286  *
287  * IMPORTANT NOTE: If there is no newline character found in BUFSIZE
288  * characters of the input stream, the line will will be ignored! By
289  * definition we should not get any longer input lines, thus this is
290  * acceptable in this case ;-) */
291 static char *read_line (conn_t *src)
292 {
293         int i = 0;
294
295         assert ((BUFSIZE >= src->idx) && (src->idx >= 0));
296         assert ((src->idx > src->length) || (src->length == 0));
297
298         if (src->length > 0) { /* remove old line */
299                 src->idx -= (src->length + 1);
300                 memmove (src->buffer, src->buffer + src->length + 1, src->idx);
301                 src->length = 0;
302         }
303
304         for (i = 0; i < src->idx; ++i) {
305                 if ('\n' == src->buffer[i])
306                         break;
307         }
308
309         if (i == src->idx) {
310                 fd_set fdset;
311
312                 ssize_t len = 0;
313
314                 FD_ZERO (&fdset);
315                 FD_SET (src->socket, &fdset);
316
317                 if (-1 == select (src->socket + 1, &fdset, NULL, NULL, NULL)) {
318                         char errbuf[1024];
319                         log_err ("select() failed: %s",
320                                         sstrerror (errno, errbuf, sizeof (errbuf)));
321                         return NULL;
322                 }
323
324                 assert (FD_ISSET (src->socket, &fdset));
325
326                 do {
327                         errno = 0;
328                         if (0 > (len = read (src->socket,
329                                                         (void *)(src->buffer + src->idx),
330                                                         BUFSIZE - src->idx))) {
331                                 if (EINTR != errno) {
332                                         char errbuf[1024];
333                                         log_err ("read() failed: %s",
334                                                         sstrerror (errno, errbuf, sizeof (errbuf)));
335                                         return NULL;
336                                 }
337                         }
338
339                         if (0 == len)
340                                 return NULL;
341                 } while (EINTR == errno);
342
343                 src->idx += len;
344
345                 for (i = src->idx - len; i < src->idx; ++i) {
346                         if ('\n' == src->buffer[i])
347                                 break;
348                 }
349
350                 if (i == src->idx) {
351                         src->length = 0;
352
353                         if (BUFSIZE == src->idx) { /* no space left in buffer */
354                                 while ('\n' != read_char (src))
355                                         /* ignore complete line */;
356
357                                 src->idx = 0;
358                         }
359                         return read_line (src);
360                 }
361         }
362
363         src->buffer[i] = '\0';
364         src->length    = i;
365
366         return src->buffer;
367 } /* static char *read_line (conn_t *) */
368
369 static void *collect (void *arg)
370 {
371         collector_t *this = (collector_t *)arg;
372
373         char *buffer = (char *)smalloc (BUFSIZE);
374
375         while (1) {
376                 int loop = 1;
377
378                 conn_t *connection;
379
380                 pthread_mutex_lock (&conns_mutex);
381
382                 while (NULL == conns.head) {
383                         pthread_cond_wait (&conn_available, &conns_mutex);
384                 }
385
386                 connection = conns.head;
387                 conns.head = conns.head->next;
388
389                 if (NULL == conns.head) {
390                         conns.tail = NULL;
391                 }
392
393                 this->socket = connection->socket;
394
395                 pthread_mutex_unlock (&conns_mutex);
396
397                 connection->buffer = buffer;
398                 connection->idx    = 0;
399                 connection->length = 0;
400
401                 { /* put the socket in non-blocking mode */
402                         int flags = 0;
403
404                         errno = 0;
405                         if (-1 == fcntl (connection->socket, F_GETFL, &flags)) {
406                                 char errbuf[1024];
407                                 log_err ("fcntl() failed: %s",
408                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
409                                 loop = 0;
410                         }
411
412                         errno = 0;
413                         if (-1 == fcntl (connection->socket, F_SETFL, flags | O_NONBLOCK)) {
414                                 char errbuf[1024];
415                                 log_err ("fcntl() failed: %s",
416                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
417                                 loop = 0;
418                         }
419                 }
420
421                 while (loop) {
422                         char *line = read_line (connection);
423
424                         if (NULL == line) {
425                                 loop = 0;
426                                 break;
427                         }
428
429                         if (':' != line[1]) {
430                                 log_err ("syntax error in line '%s'", line);
431                                 continue;
432                         }
433
434                         if ('e' == line[0]) { /* e:<type>:<bytes> */
435                                 char *ptr  = NULL;
436                                 char *type = strtok_r (line + 2, ":", &ptr);
437                                 char *tmp  = strtok_r (NULL, ":", &ptr);
438                                 int  bytes = 0;
439
440                                 if (NULL == tmp) {
441                                         log_err ("syntax error in line '%s'", line);
442                                         continue;
443                                 }
444
445                                 bytes = atoi (tmp);
446
447                                 pthread_mutex_lock (&count_mutex);
448                                 type_list_incr (&count, type, 1);
449                                 pthread_mutex_unlock (&count_mutex);
450
451                                 if (bytes > 0) {
452                                         pthread_mutex_lock (&size_mutex);
453                                         type_list_incr (&size, type, bytes);
454                                         pthread_mutex_unlock (&size_mutex);
455                                 }
456                         }
457                         else if ('s' == line[0]) { /* s:<value> */
458                                 pthread_mutex_lock (&score_mutex);
459                                 score = (score * (double)score_count + atof (line + 2))
460                                                 / (double)(score_count + 1);
461                                 ++score_count;
462                                 pthread_mutex_unlock (&score_mutex);
463                         }
464                         else if ('c' == line[0]) { /* c:<type1>[,<type2>,...] */
465                                 char *ptr  = NULL;
466                                 char *type = strtok_r (line + 2, ",", &ptr);
467
468                                 do {
469                                         pthread_mutex_lock (&check_mutex);
470                                         type_list_incr (&check, type, 1);
471                                         pthread_mutex_unlock (&check_mutex);
472                                 } while (NULL != (type = strtok_r (NULL, ",", &ptr)));
473                         }
474                         else {
475                                 log_err ("unknown type '%c'", line[0]);
476                         }
477                 } /* while (loop) */
478
479                 close (connection->socket);
480                 free (connection);
481
482                 this->socket = -1;
483
484                 pthread_mutex_lock (&available_mutex);
485                 ++available_collectors;
486                 pthread_mutex_unlock (&available_mutex);
487
488                 pthread_cond_signal (&collector_available);
489         } /* while (1) */
490
491         free (buffer);
492         pthread_exit ((void *)0);
493 } /* static void *collect (void *) */
494
495 static void *open_connection (void *arg)
496 {
497         struct sockaddr_un addr;
498
499         /* create UNIX socket */
500         errno = 0;
501         if (-1 == (connector_socket = socket (PF_UNIX, SOCK_STREAM, 0))) {
502                 char errbuf[1024];
503                 disabled = 1;
504                 log_err ("socket() failed: %s",
505                                 sstrerror (errno, errbuf, sizeof (errbuf)));
506                 pthread_exit ((void *)1);
507         }
508
509         addr.sun_family = AF_UNIX;
510
511         strncpy (addr.sun_path, SOCK_PATH, (size_t)(UNIX_PATH_MAX - 1));
512         addr.sun_path[UNIX_PATH_MAX - 1] = '\0';
513         unlink (addr.sun_path);
514
515         errno = 0;
516         if (-1 == bind (connector_socket, (struct sockaddr *)&addr,
517                                 offsetof (struct sockaddr_un, sun_path)
518                                         + strlen(addr.sun_path))) {
519                 char errbuf[1024];
520                 disabled = 1;
521                 connector_socket = -1; /* TODO: close? */
522                 log_err ("bind() failed: %s",
523                                 sstrerror (errno, errbuf, sizeof (errbuf)));
524                 pthread_exit ((void *)1);
525         }
526
527         errno = 0;
528         if (-1 == listen (connector_socket, 5)) {
529                 char errbuf[1024];
530                 disabled = 1;
531                 connector_socket = -1; /* TODO: close? */
532                 log_err ("listen() failed: %s",
533                                 sstrerror (errno, errbuf, sizeof (errbuf)));
534                 pthread_exit ((void *)1);
535         }
536
537         if ((uid_t) 0 == geteuid ())
538         {
539                 struct group sg;
540                 struct group *grp;
541                 char grbuf[2048];
542                 int status;
543
544                 grp = NULL;
545                 status = getgrnam_r (sock_group, &sg, grbuf, sizeof (grbuf), &grp);
546                 if (status != 0)
547                 {
548                         char errbuf[1024];
549                         log_warn ("getgrnam_r (%s) failed: %s", sock_group,
550                                         sstrerror (errno, errbuf, sizeof (errbuf)));
551                 }
552                 else if (grp == NULL)
553                 {
554                         log_warn ("No such group: `%s'", sock_group);
555                 }
556                 else
557                 {
558                         status = chown (SOCK_PATH, (uid_t) -1, grp->gr_gid);
559                         if (status != 0)
560                         {
561                                 char errbuf[1024];
562                                 log_warn ("chown (%s, -1, %i) failed: %s",
563                                                 SOCK_PATH, (int) grp->gr_gid,
564                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
565                         }
566                 }
567         }
568         else /* geteuid != 0 */
569         {
570                 log_warn ("not running as root");
571         }
572
573         errno = 0;
574         if (0 != chmod (SOCK_PATH, sock_perms)) {
575                 char errbuf[1024];
576                 log_warn ("chmod() failed: %s",
577                                 sstrerror (errno, errbuf, sizeof (errbuf)));
578         }
579
580         { /* initialize collector threads */
581                 int i   = 0;
582                 int err = 0;
583
584                 pthread_attr_t ptattr;
585
586                 conns.head = NULL;
587                 conns.tail = NULL;
588
589                 pthread_attr_init (&ptattr);
590                 pthread_attr_setdetachstate (&ptattr, PTHREAD_CREATE_DETACHED);
591
592                 available_collectors = max_conns;
593
594                 collectors =
595                         (collector_t **)smalloc (max_conns * sizeof (collector_t *));
596
597                 for (i = 0; i < max_conns; ++i) {
598                         collectors[i] = (collector_t *)smalloc (sizeof (collector_t));
599                         collectors[i]->socket = -1;
600
601                         if (0 != (err = pthread_create (&collectors[i]->thread, &ptattr,
602                                                         collect, collectors[i]))) {
603                                 char errbuf[1024];
604                                 log_err ("pthread_create() failed: %s",
605                                                 sstrerror (errno, errbuf, sizeof (errbuf)));
606                                 collectors[i]->thread = (pthread_t) 0;
607                         }
608                 }
609
610                 pthread_attr_destroy (&ptattr);
611         }
612
613         while (1) {
614                 int remote = 0;
615
616                 conn_t *connection;
617
618                 pthread_mutex_lock (&available_mutex);
619
620                 while (0 == available_collectors) {
621                         pthread_cond_wait (&collector_available, &available_mutex);
622                 }
623
624                 --available_collectors;
625
626                 pthread_mutex_unlock (&available_mutex);
627
628                 do {
629                         errno = 0;
630                         if (-1 == (remote = accept (connector_socket, NULL, NULL))) {
631                                 if (EINTR != errno) {
632                                         char errbuf[1024];
633                                         disabled = 1;
634                                         connector_socket = -1; /* TODO: close? */
635                                         log_err ("accept() failed: %s",
636                                                         sstrerror (errno, errbuf, sizeof (errbuf)));
637                                         pthread_exit ((void *)1);
638                                 }
639                         }
640                 } while (EINTR == errno);
641
642                 connection = (conn_t *)smalloc (sizeof (conn_t));
643
644                 connection->socket = remote;
645                 connection->next   = NULL;
646
647                 pthread_mutex_lock (&conns_mutex);
648
649                 if (NULL == conns.head) {
650                         conns.head = connection;
651                         conns.tail = connection;
652                 }
653                 else {
654                         conns.tail->next = connection;
655                         conns.tail = conns.tail->next;
656                 }
657
658                 pthread_mutex_unlock (&conns_mutex);
659
660                 pthread_cond_signal (&conn_available);
661         }
662         pthread_exit ((void *)0);
663 } /* static void *open_connection (void *) */
664
665 static int email_init (void)
666 {
667         int err = 0;
668
669         if (0 != (err = pthread_create (&connector, NULL,
670                                 open_connection, NULL))) {
671                 char errbuf[1024];
672                 disabled = 1;
673                 log_err ("pthread_create() failed: %s",
674                                 sstrerror (errno, errbuf, sizeof (errbuf)));
675                 return (-1);
676         }
677
678         return (0);
679 } /* int email_init */
680
681 static int email_shutdown (void)
682 {
683         int i = 0;
684
685         if (connector != ((pthread_t) 0)) {
686                 pthread_kill (connector, SIGTERM);
687                 connector = (pthread_t) 0;
688         }
689
690         if (connector_socket >= 0) {
691                 close (connector_socket);
692                 connector_socket = -1;
693         }
694
695         /* don't allow any more connections to be processed */
696         pthread_mutex_lock (&conns_mutex);
697
698         if (collectors != NULL) {
699                 for (i = 0; i < max_conns; ++i) {
700                         if (collectors[i] == NULL)
701                                 continue;
702
703                         if (collectors[i]->thread != ((pthread_t) 0)) {
704                                 pthread_kill (collectors[i]->thread, SIGTERM);
705                                 collectors[i]->thread = (pthread_t) 0;
706                         }
707
708                         if (collectors[i]->socket >= 0) {
709                                 close (collectors[i]->socket);
710                                 collectors[i]->socket = -1;
711                         }
712                 }
713         } /* if (collectors != NULL) */
714
715         pthread_mutex_unlock (&conns_mutex);
716
717         unlink (SOCK_PATH);
718         errno = 0;
719
720         return (0);
721 } /* static void email_shutdown (void) */
722
723 static void email_submit (const char *type, const char *type_instance, gauge_t value)
724 {
725         value_t values[1];
726         value_list_t vl = VALUE_LIST_INIT;
727
728         values[0].gauge = value;
729
730         vl.values = values;
731         vl.values_len = 1;
732         vl.time = time (NULL);
733         strcpy (vl.host, hostname_g);
734         strcpy (vl.plugin, "email");
735         strncpy (vl.type_instance, type_instance, sizeof (vl.type_instance));
736
737         plugin_dispatch_values (type, &vl);
738 } /* void email_submit */
739
740 /* Copy list l1 to list l2. l2 may partly exist already, but it is assumed
741  * that neither the order nor the name of any element of either list is
742  * changed and no elements are deleted. The values of l1 are reset to zero
743  * after they have been copied to l2. */
744 static void copy_type_list (type_list_t *l1, type_list_t *l2)
745 {
746         type_t *ptr1;
747         type_t *ptr2;
748
749         type_t *last = NULL;
750
751         for (ptr1 = l1->head, ptr2 = l2->head; NULL != ptr1;
752                         ptr1 = ptr1->next, last = ptr2, ptr2 = ptr2->next) {
753                 if (NULL == ptr2) {
754                         ptr2 = (type_t *)smalloc (sizeof (type_t));
755                         ptr2->name = NULL;
756                         ptr2->next = NULL;
757
758                         if (NULL == last) {
759                                 l2->head = ptr2;
760                         }
761                         else {
762                                 last->next = ptr2;
763                         }
764
765                         l2->tail = ptr2;
766                 }
767
768                 if (NULL == ptr2->name) {
769                         ptr2->name = sstrdup (ptr1->name);
770                 }
771
772                 ptr2->value = ptr1->value;
773                 ptr1->value = 0;
774         }
775         return;
776 }
777
778 static int email_read (void)
779 {
780         type_t *ptr;
781
782         double score_old;
783         int score_count_old;
784
785         static type_list_t *cnt;
786         static type_list_t *sz;
787         static type_list_t *chk;
788
789         if (disabled)
790                 return (-1);
791
792         if (NULL == cnt) {
793                 cnt = (type_list_t *)smalloc (sizeof (type_list_t));
794                 cnt->head = NULL;
795         }
796
797         if (NULL == sz) {
798                 sz = (type_list_t *)smalloc (sizeof (type_list_t));
799                 sz->head = NULL;
800         }
801
802         if (NULL == chk) {
803                 chk = (type_list_t *)smalloc (sizeof (type_list_t));
804                 chk->head = NULL;
805         }
806
807         /* email count */
808         pthread_mutex_lock (&count_mutex);
809
810         copy_type_list (&count, cnt);
811
812         pthread_mutex_unlock (&count_mutex);
813
814         for (ptr = cnt->head; NULL != ptr; ptr = ptr->next) {
815                 email_submit ("email_count", ptr->name, ptr->value);
816         }
817
818         /* email size */
819         pthread_mutex_lock (&size_mutex);
820
821         copy_type_list (&size, sz);
822
823         pthread_mutex_unlock (&size_mutex);
824
825         for (ptr = sz->head; NULL != ptr; ptr = ptr->next) {
826                 email_submit ("email_size", ptr->name, ptr->value);
827         }
828
829         /* spam score */
830         pthread_mutex_lock (&score_mutex);
831
832         score_old = score;
833         score_count_old = score_count;
834         score = 0.0;
835         score_count = 0;
836
837         pthread_mutex_unlock (&score_mutex);
838
839         if (score_count_old > 0)
840                 email_submit ("spam_score", "", score_old);
841
842         /* spam checks */
843         pthread_mutex_lock (&check_mutex);
844
845         copy_type_list (&check, chk);
846
847         pthread_mutex_unlock (&check_mutex);
848
849         for (ptr = chk->head; NULL != ptr; ptr = ptr->next)
850                 email_submit ("spam_check", ptr->name, ptr->value);
851
852         return (0);
853 } /* int email_read */
854
855 void module_register (void)
856 {
857         plugin_register_config ("email", email_config, config_keys, config_keys_num);
858         plugin_register_init ("email", email_init);
859         plugin_register_read ("email", email_read);
860         plugin_register_shutdown ("email", email_shutdown);
861 } /* void module_register */
862
863 /* vim: set sw=4 ts=4 tw=78 noexpandtab : */