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