1 #include <sys/types.h>
2 #include <sys/time.h>
3 #include <sys/uio.h>
4 #include <unistd.h>
5 #include <string.h>
6 #include <ctype.h>
7 #include <stdlib.h>
8 #include <signal.h>
9 #include <stdarg.h>
10 #include <stdio.h>
11 #include <syslog.h>
12 #include <errno.h>
13 #include "msgstore.h"
14 #include "spamtest.h"
15 
16 #define MYDOMAIN        "example.com"
17 #define TIMEOUT_MS      30000 /* we'll use it per sts_* call */
18 #define MAX_MSGBUF      1024000
19 
20 #define SPAM            1
21 #define PROBABLE_SPAM   2
22 #define NOT_SPAM        3
23 
24 #define FILTER_ERROR    4
25 #define SENDER_ERROR    5
26 #define CODE_ERROR      10
27 
28 typedef struct {
29         char    *ip;
30         char    *helo;
31         char    *from;
32         char    *rcpt;
33         char    *server;
34         char    *filename;
35 } message_info_t;
36 
37 message_info_t info;
38 
39 int verbose = 0;
40 
41 const int max_message_rss = 1024 * 1024 * 10;
42 
43 int send_body_and_process_result(FILE *f, spamtest_session_t *s,
44     long long *bytes);
45 
46 void
warning(const char * fmt,...)47 warning(const char *fmt, ...)
48 {
49         va_list ap;
50         char buff[512];
51 
52         va_start(ap, fmt);
53         vsnprintf(buff, sizeof(buff), fmt, ap);
54         syslog(LOG_ERR | LOG_LOCAL1, "%s", buff);
55         va_end(ap);
56 }
57 
58 void
free_info(message_info_t * info)59 free_info(message_info_t *info)
60 {
61         free(info->ip);
62         free(info->helo);
63         free(info->from);
64         free(info->rcpt);
65         free(info->server);
66         free(info->filename);
67 }
68 
69 int
process(FILE * f,message_info_t * info,long long * bytes)70 process(FILE *f, message_info_t *info, long long *bytes)
71 {
72         spamtest_session_t s;
73         int r;
74         char *comma, *p;
75 
76         r = 0;
77         memset(&s, 0, sizeof(s));
78         s.access_address = info->server;
79         s.mail_domain = MYDOMAIN;
80         s.rw_timeout= TIMEOUT_MS;
81         s.connect_timeout= TIMEOUT_MS;
82         s.flags = STS_USE_POLL;
83 
84         r = sts_init(&s);
85         if (r != 0) {
86                 warning("sts_init: %s", sts_strerror(r));
87                 return FILTER_ERROR;
88         }
89 
90         r = sts_prepare_envelope(&s, info->from, NULL, info->ip, NULL);
91         if (r != 0) {
92                 warning("sts_prepare_envelope: %s", sts_strerror(r));
93                 return FILTER_ERROR;
94         }
95 
96         comma = p = info->rcpt;
97         while (comma != NULL) {
98                 comma = strchr(p, ',');
99                 if (comma != NULL) {
100                         *comma = 0x0;
101                         r = sts_prepare_envelope(&s, NULL, p, NULL, NULL);
102                         if (r != 0) {
103                                 warning("sts_prepare_envelope: %s",
104                                     sts_strerror(r));
105                                 return FILTER_ERROR;
106                         }
107                         comma++;
108                         p = comma;
109                 } else {
110                         r = sts_prepare_envelope(&s, NULL, p, NULL, NULL);
111                         if (r != 0) {
112                                 warning("sts_prepare_envelope: %s",
113                                     sts_strerror(r));
114                                 return FILTER_ERROR;
115                         }
116                 }
117         }
118 
119         r = sts_add_attribute(&s, "helo_name", info->helo);
120         if (r != 0) {
121                 warning("sts_add_attribute: %s", sts_strerror(r));
122                 return FILTER_ERROR;
123         }
124 
125         r = send_body_and_process_result(f, &s, bytes);
126         sts_close(&s);
127 
128         return r;
129 }
130 
131 int
headers_check(message_status_t * mst)132 headers_check(message_status_t *mst)
133 {
134         int i;
135         hdraction_t *header_action;
136         int r = 0;
137 
138         if (mst->action == STS_ACTION_CHANGE) {
139                 for (i = 0; i < mst->action_count; i++) {
140                         header_action = &mst->act_array[i];
141                         switch (header_action->type) {
142                         case STS_HEADER_ADD:
143                         case STS_HEADER_NEW:
144                         case STS_HEADER_CHG:
145                                 if (verbose) {
146                                         fprintf(stdout, "%s: %s\n", header_action->header, header_action->value);
147                                 }
148                                 if (!strcmp(header_action->header,
149                                         "X-SpamTest-Status")) {
150                                         if (!strcmp(header_action->value,
151                                                 "SPAM")) {
152                                                 r = SPAM;
153                                         }
154                                         if (!strcmp(header_action->value,
155                                                 "Probable Spam")) {
156                                                 r = PROBABLE_SPAM;
157                                         }
158                                 }
159                                 break;
160                         default:;
161                         }
162                 }
163         }
164 
165         if (r) return r;
166         else return NOT_SPAM;
167 }
168 
169 int
send_body_and_process_result(FILE * f,spamtest_session_t * s,long long * bytes)170 send_body_and_process_result(FILE *f, spamtest_session_t *s, long long *bytes)
171 {
172         char linebuf[2048];
173         int l;
174         spamtest_status_t *st = NULL;
175         message_status_t *mst;
176         int status = 0;
177 
178         /* message processing loop */
179         while (fgets(linebuf, sizeof(linebuf) - 1, f)) {
180 	        l = strlen(linebuf);
181 	        *bytes += l;
182 
183                 /* simply send linebuf to filter */
184                 if (!(st = sts_send_body(s, linebuf, l, 0))) {
185                         status = FILTER_ERROR;
186                         warning("Filtering error: sts_send_body");
187                         goto end;
188                 }
189 
190                 /* analyze result code */
191                 switch(st->status) {
192                 default:
193 		    warning("Unknown result code: %d", st->status);
194 		    /* no break here */
195                 case STS_SENDERROR:
196                     status = SENDER_ERROR;
197                     goto end;
198                 case STS_CONTINUE:
199 		    /* do nothing */
200                     break;
201                 case STS_ACCEPT:
202                 case STS_FILTER_ERROR:
203                 case STS_REJECT:
204                 case STS_BLACKHOLE:
205                 case STS_SMTP_ACCEPT:
206                     goto ready;
207                 }
208         }
209 
210         st = sts_body_end(s, 0);
211         if (!st) {
212                 status = FILTER_ERROR;
213                 goto end;
214         }
215 
216 ready:
217         /* now we have message status in st, message body in message
218          * store, let's go*/
219         switch (st->status) {
220         case STS_ACCEPT:
221                 st = glue_actions(st);
222                 mst = &st->ms_array[0];
223                 status = headers_check(mst);
224 	        break;
225 	case STS_SMTP_ACCEPT:
226                 status = NOT_SPAM;
227 	        break;
228 	case STS_BLACKHOLE:
229                 status = SPAM;
230                 break;
231 	case STS_FILTER_ERROR:
232                 warning("filter error");
233                 status = FILTER_ERROR;
234                 break;
235 	case STS_SENDERROR:
236                 warning("sender error");
237                 status = SENDER_ERROR;
238                 break;
239 	case STS_REJECT:
240                 status = SPAM;
241                 break;
242 	default:
243                 status = CODE_ERROR;
244                 break;
245 	}
246 
247 end:
248         return status;
249 }
250 
251 void
usage(char * p)252 usage(char *p)
253 {
254         fprintf(stderr, "Usage: %s [params] spamtest-server inputfile\n", p);
255         fprintf(stderr, "Param:\n"
256                 "\t-i <ip>      -- ip address of relay\n"
257                 "\t-e <str>     -- ehlo (helo) given by last relay\n"
258                 "\t-f <str>     -- env. from given by last relay\n"
259                 "\t-r <str>     -- comma separated list of recipients\n"
260                 "\t-h           -- this help\n");
261         exit(0);
262 }
263 
264 int
main(int argc,char ** argv)265 main(int argc, char **argv)
266 {
267         int c, use_file, rc;
268         char *progname;
269         long long int bytes;
270         FILE *f;
271 
272         use_file = 0;
273         f = NULL;
274         bytes = 0;
275         verbose = 0;
276         memset(&info, 0, sizeof(info));
277 
278         progname = strdup(argv[0]);
279 
280         while ((c = getopt(argc, argv, "i:f:r:e:hv")) != -1) {
281                 switch (c) {
282                 case 'h':
283                         usage(progname);
284                         break;
285                 case 'i':
286                         info.ip = strdup(optarg);
287                         break;
288                 case 'f':
289                         info.from = strdup(optarg);
290                         break;
291                 case 'r':
292                         info.rcpt = strdup(optarg);
293                         break;
294                 case 'e':
295                         info.helo = strdup(optarg);
296                         break;
297                 case 'v':
298                         verbose = 1;
299                         break;
300                 default:
301                         usage(progname);
302                 }
303         }
304 
305         argc -= optind;
306         argv += optind;
307 
308         info.server = argv[0] ? strdup(argv[0]) : NULL;
309         info.filename = argv[1] ? strdup(argv[1]) : NULL;
310 
311         if (!info.server) {
312                 usage(progname);
313         }
314 
315         if (info.filename) {
316                 f = fopen(info.filename, "r");
317                 if (!f) {
318                         warning("can't open file %s: %s",
319                             info.filename, strerror(errno));
320                         exit(CODE_ERROR);
321                 } else {
322                         use_file = 1;
323                 }
324         } else {
325                 f = stdin;
326         }
327 
328         signal(SIGPIPE,SIG_IGN);
329         openlog("spamtest-checker", LOG_PID, LOG_LOCAL1);
330 
331         rc = process(f, &info, &bytes);
332 
333         if (verbose) {
334                 if (rc == SPAM) {
335                         fprintf(stderr, "SPAM\n");
336                 } else if (rc == PROBABLE_SPAM) {
337                         fprintf(stderr, "PROBABLE SPAM\n");
338                 } else if (rc == NOT_SPAM) {
339                         fprintf(stderr, "NOT SPAM\n");
340                 } else {
341                         fprintf(stderr, "filter error, see logs\n");
342                 }
343         }
344 
345         if (use_file) {
346                 fclose(f);
347         }
348 
349         free(progname);
350         free_info(&info);
351 
352         return rc;
353 }
354