2 15cacd92 2019-03-29 martijn * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
4 15cacd92 2019-03-29 martijn * Permission to use, copy, modify, and distribute this software for any
5 15cacd92 2019-03-29 martijn * purpose with or without fee is hereby granted, provided that the above
6 15cacd92 2019-03-29 martijn * copyright notice and this permission notice appear in all copies.
8 15cacd92 2019-03-29 martijn * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 15cacd92 2019-03-29 martijn * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 15cacd92 2019-03-29 martijn * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 15cacd92 2019-03-29 martijn * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 15cacd92 2019-03-29 martijn * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 15cacd92 2019-03-29 martijn * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 15cacd92 2019-03-29 martijn * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 a248a3c1 2019-03-27 martijn #include <sys/time.h>
17 a248a3c1 2019-03-27 martijn #include <sys/socket.h>
19 a248a3c1 2019-03-27 martijn #include <arpa/inet.h>
20 a248a3c1 2019-03-27 martijn #include <errno.h>
21 eaefb262 2019-03-27 martijn #include <event.h>
22 eaefb262 2019-03-27 martijn #include <fcntl.h>
23 a248a3c1 2019-03-27 martijn #include <inttypes.h>
24 57cbfccc 2019-03-27 martijn #include <stdarg.h>
25 a248a3c1 2019-03-27 martijn #include <stdio.h>
26 a248a3c1 2019-03-27 martijn #include <stdlib.h>
27 a248a3c1 2019-03-27 martijn #include <string.h>
28 b633fa4c 2019-03-29 martijn #include <syslog.h>
29 eaefb262 2019-03-27 martijn #include <unistd.h>
31 b633fa4c 2019-03-29 martijn #include "log.h"
32 a248a3c1 2019-03-27 martijn #include "smtp_proc.h"
34 a248a3c1 2019-03-27 martijn #define NITEMS(x) (sizeof(x) / sizeof(*x))
36 a248a3c1 2019-03-27 martijn struct smtp_callback;
37 73ac3ec1 2019-03-27 martijn struct smtp_request;
39 03a6f52a 2019-03-29 martijn static int smtp_register(char *, char *, char *, void *);
40 6bf2fad2 2019-03-29 martijn static ssize_t smtp_getline(char ** restrict, size_t * restrict);
41 eaefb262 2019-03-27 martijn static void smtp_newline(int, short, void *);
42 42a4c512 2019-03-28 martijn static void smtp_connect(struct smtp_callback *, int, struct timespec *,
43 42a4c512 2019-03-28 martijn uint64_t, uint64_t, char *);
44 02090eee 2019-03-29 martijn static void smtp_dataline(struct smtp_callback *, int, struct timespec *,
45 02090eee 2019-03-29 martijn uint64_t, uint64_t, char *);
46 47628d21 2019-03-29 martijn static void smtp_in_link_disconnect(struct smtp_callback *, int, struct timespec *,
47 47628d21 2019-03-29 martijn uint64_t, char *);
49 a248a3c1 2019-03-27 martijn struct smtp_callback {
50 a248a3c1 2019-03-27 martijn char *type;
51 a248a3c1 2019-03-27 martijn char *phase;
52 a248a3c1 2019-03-27 martijn char *direction;
54 47628d21 2019-03-29 martijn void (*smtp_filter)(struct smtp_callback *, int,
55 47628d21 2019-03-29 martijn struct timespec *, uint64_t, uint64_t, char *);
56 47628d21 2019-03-29 martijn void (*smtp_report)(struct smtp_callback *, int,
57 47628d21 2019-03-29 martijn struct timespec *, uint64_t, char *);
59 03a6f52a 2019-03-29 martijn void *cb;
60 a248a3c1 2019-03-27 martijn } smtp_callbacks[] = {
61 47628d21 2019-03-29 martijn {"filter", "connect", "smtp-in", .smtp_filter = smtp_connect, NULL},
62 02090eee 2019-03-29 martijn {"filter", "data-line", "smtp-in", .smtp_filter = smtp_dataline, NULL},
63 47628d21 2019-03-29 martijn {"report", "link-disconnect", "smtp-in",
64 47628d21 2019-03-29 martijn .smtp_report = smtp_in_link_disconnect, NULL}
67 a248a3c1 2019-03-27 martijn static int ready = 0;
70 42a4c512 2019-03-28 martijn smtp_register_filter_connect(void (*cb)(char *, int, struct timespec *, char *,
71 03a6f52a 2019-03-29 martijn char *, uint64_t, uint64_t, char *, struct inx_addr *))
73 03a6f52a 2019-03-29 martijn return smtp_register("filter", "connect", "smtp-in", (void *)cb);
77 02090eee 2019-03-29 martijn smtp_register_filter_dataline(void (*cb)(char *, int, struct timespec *, char *,
78 02090eee 2019-03-29 martijn char *, uint64_t, uint64_t, char *))
80 02090eee 2019-03-29 martijn return smtp_register("filter", "data-line", "smtp-in", (void *)cb);
84 47628d21 2019-03-29 martijn smtp_in_register_report_disconnect(void (*cb)(char *, int, struct timespec *,
85 47628d21 2019-03-29 martijn char *, char *, uint64_t))
87 47628d21 2019-03-29 martijn return smtp_register("report", "link-disconnect", "smtp-in", (void *)cb);
91 b633fa4c 2019-03-29 martijn smtp_run(int debug)
93 eaefb262 2019-03-27 martijn struct event stdinev;
95 eaefb262 2019-03-27 martijn printf("register|ready\n");
96 6f50562c 2019-03-28 martijn fflush(stdout);
97 eaefb262 2019-03-27 martijn ready = 1;
99 b633fa4c 2019-03-29 martijn log_init(debug, LOG_MAIL);
100 eaefb262 2019-03-27 martijn event_init();
101 eaefb262 2019-03-27 martijn event_set(&stdinev, STDIN_FILENO, EV_READ | EV_PERSIST, smtp_newline,
102 eaefb262 2019-03-27 martijn &stdinev);
103 eaefb262 2019-03-27 martijn event_add(&stdinev, NULL);
105 eaefb262 2019-03-27 martijn if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1)
106 b633fa4c 2019-03-29 martijn fatal("fcntl");
107 eaefb262 2019-03-27 martijn event_dispatch();
110 6bf2fad2 2019-03-29 martijn static ssize_t
111 6bf2fad2 2019-03-29 martijn smtp_getline(char ** restrict buf, size_t * restrict size)
113 6bf2fad2 2019-03-29 martijn static char *rbuf = NULL;
114 6bf2fad2 2019-03-29 martijn static size_t rsoff = 0, reoff = 0;
115 6bf2fad2 2019-03-29 martijn static size_t rbsize = 0;
116 6bf2fad2 2019-03-29 martijn char *sep;
117 6bf2fad2 2019-03-29 martijn size_t sepoff;
118 6bf2fad2 2019-03-29 martijn ssize_t strlen, nread;
121 6bf2fad2 2019-03-29 martijn if (rsoff != reoff) {
122 6bf2fad2 2019-03-29 martijn if ((sep = memchr(rbuf + rsoff, '\n', reoff - rsoff))
123 6bf2fad2 2019-03-29 martijn != NULL) {
124 6bf2fad2 2019-03-29 martijn sepoff = sep - rbuf;
125 6bf2fad2 2019-03-29 martijn if (*buf == NULL)
126 6bf2fad2 2019-03-29 martijn *size = 0;
127 6bf2fad2 2019-03-29 martijn if (*size < (sepoff - rsoff + 1)) {
128 6bf2fad2 2019-03-29 martijn *size = sepoff - rsoff + 1;
129 6bf2fad2 2019-03-29 martijn *buf = realloc(*buf, sepoff - rsoff + 1);
130 6bf2fad2 2019-03-29 martijn if (*buf == NULL)
131 6bf2fad2 2019-03-29 martijn fatal(NULL);
133 6bf2fad2 2019-03-29 martijn sep[0] = '\0';
134 6bf2fad2 2019-03-29 martijn strlen = strlcpy(*buf, rbuf + rsoff, *size);
135 6bf2fad2 2019-03-29 martijn if (strlen >= *size)
136 6bf2fad2 2019-03-29 martijn fatalx("copy buffer too small");
137 6bf2fad2 2019-03-29 martijn rsoff = sepoff + 1;
138 6bf2fad2 2019-03-29 martijn return strlen;
141 6bf2fad2 2019-03-29 martijn if (reoff - rsoff < 1500) {
142 6bf2fad2 2019-03-29 martijn if ((rbuf = realloc(rbuf, rbsize + 4096)) == NULL)
143 6bf2fad2 2019-03-29 martijn fatal(NULL);
144 6bf2fad2 2019-03-29 martijn rbsize += 4096;
146 6bf2fad2 2019-03-29 martijn // If we can't fill at the end, move everything back.
147 6bf2fad2 2019-03-29 martijn if (rbsize - reoff < 1500)
148 6bf2fad2 2019-03-29 martijn memmove(rbuf + rsoff, rbuf, reoff - rsoff);
149 6bf2fad2 2019-03-29 martijn nread = read(STDIN_FILENO, rbuf + reoff, rbsize - reoff);
150 6bf2fad2 2019-03-29 martijn if (nread <= 0)
151 6bf2fad2 2019-03-29 martijn return nread;
152 6bf2fad2 2019-03-29 martijn reoff += nread;
153 6bf2fad2 2019-03-29 martijn } while (1);
156 eaefb262 2019-03-27 martijn static void
157 eaefb262 2019-03-27 martijn smtp_newline(int fd, short event, void *arg)
159 eaefb262 2019-03-27 martijn struct event *stdinev = (struct event *)arg;
160 ced7acc6 2019-03-29 martijn char *line = NULL, *linedup = NULL;
161 a248a3c1 2019-03-27 martijn size_t linesize = 0;
162 ced7acc6 2019-03-29 martijn size_t dupsize = 0;
163 a248a3c1 2019-03-27 martijn ssize_t linelen;
164 a248a3c1 2019-03-27 martijn char *start, *end, *type, *direction, *phase, *params;
165 a248a3c1 2019-03-27 martijn int version;
166 42a4c512 2019-03-28 martijn struct timespec tm;
167 a248a3c1 2019-03-27 martijn uint64_t reqid, token;
170 6bf2fad2 2019-03-29 martijn while ((linelen = smtp_getline(&line, &linesize)) > 0) {
171 ced7acc6 2019-03-29 martijn if (dupsize < linesize) {
172 ced7acc6 2019-03-29 martijn if ((linedup = realloc(linedup, linesize)) == NULL)
173 ced7acc6 2019-03-29 martijn fatal(NULL);
174 ced7acc6 2019-03-29 martijn dupsize = linesize;
176 ced7acc6 2019-03-29 martijn strlcpy(linedup, line, dupsize);
177 a248a3c1 2019-03-27 martijn type = line;
178 a248a3c1 2019-03-27 martijn if ((start = strchr(type, '|')) == NULL)
179 35699cf9 2019-03-29 martijn fatalx("Invalid line received: missing version: %s", linedup);
180 a248a3c1 2019-03-27 martijn start++[0] = '\0';
181 a248a3c1 2019-03-27 martijn if ((end = strchr(start, '|')) == NULL)
182 35699cf9 2019-03-29 martijn fatalx("Invalid line received: missing time: %s", linedup);
183 a248a3c1 2019-03-27 martijn end++[0] = '\0';
184 a248a3c1 2019-03-27 martijn if (strcmp(start, "1") != 0)
185 35699cf9 2019-03-29 martijn fatalx("Unsupported protocol received: %s: %s", start, linedup);
186 a248a3c1 2019-03-27 martijn version = 1;
187 a248a3c1 2019-03-27 martijn start = end;
188 a248a3c1 2019-03-27 martijn if ((direction = strchr(start, '|')) == NULL)
189 35699cf9 2019-03-29 martijn fatalx("Invalid line received: missing direction: %s", linedup);
190 a248a3c1 2019-03-27 martijn direction++[0] = '\0';
191 42a4c512 2019-03-28 martijn tm.tv_sec = (time_t) strtoull(start, &end, 10);
192 42a4c512 2019-03-28 martijn tm.tv_nsec = 0;
193 42a4c512 2019-03-28 martijn if (start[0] == '\0' || (end[0] != '\0' && end[0] != '.'))
194 35699cf9 2019-03-29 martijn fatalx("Invalid line received: invalid timestamp: %s", linedup);
195 42a4c512 2019-03-28 martijn if (end[0] == '.') {
196 42a4c512 2019-03-28 martijn start = end + 1;
197 42a4c512 2019-03-28 martijn tm.tv_nsec = strtol(start, &end, 10);
198 42a4c512 2019-03-28 martijn if (start[0] == '\0' || end[0] != '\0')
199 b633fa4c 2019-03-29 martijn fatalx("Invalid line received: invalid "
200 35699cf9 2019-03-29 martijn "timestamp: %s", linedup);
201 57d45cc8 2019-03-28 martijn for (i = 9 - (end - start); i > 0; i--)
202 42a4c512 2019-03-28 martijn tm.tv_nsec *= 10;
204 a248a3c1 2019-03-27 martijn if ((phase = strchr(direction, '|')) == NULL)
205 35699cf9 2019-03-29 martijn fatalx("Invalid line receieved: missing phase: %s", linedup);
206 a248a3c1 2019-03-27 martijn phase++[0] = '\0';
207 a248a3c1 2019-03-27 martijn if ((start = strchr(phase, '|')) == NULL)
208 35699cf9 2019-03-29 martijn fatalx("Invalid line received: missing reqid: %s", linedup);
209 a248a3c1 2019-03-27 martijn start++[0] = '\0';
210 47628d21 2019-03-29 martijn reqid = strtoull(start, ¶ms, 16);
211 47628d21 2019-03-29 martijn if (start[0] == '|' || (params[0] != '|' & params[0] != '\0'))
212 35699cf9 2019-03-29 martijn fatalx("Invalid line received: invalid reqid: %s", linedup);
213 47628d21 2019-03-29 martijn params++;
215 a248a3c1 2019-03-27 martijn for (i = 0; i < NITEMS(smtp_callbacks); i++) {
216 a248a3c1 2019-03-27 martijn if (strcmp(type, smtp_callbacks[i].type) == 0 &&
217 a248a3c1 2019-03-27 martijn strcmp(phase, smtp_callbacks[i].phase) == 0 &&
218 a248a3c1 2019-03-27 martijn strcmp(direction, smtp_callbacks[i].direction) == 0)
221 a248a3c1 2019-03-27 martijn if (i == NITEMS(smtp_callbacks)) {
222 b633fa4c 2019-03-29 martijn fatalx("Invalid line received: received unregistered "
223 35699cf9 2019-03-29 martijn "%s: %s: %s", type, phase, linedup);
225 47628d21 2019-03-29 martijn if (strcmp(type, "filter") == 0) {
226 47628d21 2019-03-29 martijn start = params;
227 47628d21 2019-03-29 martijn token = strtoull(start, ¶ms, 16);
228 47628d21 2019-03-29 martijn if (start[0] == '|' || params[0] != '|')
229 35699cf9 2019-03-29 martijn fatalx("Invalid line received: invalid token: %s", linedup);
230 47628d21 2019-03-29 martijn params++;
231 47628d21 2019-03-29 martijn smtp_callbacks[i].smtp_filter(&(smtp_callbacks[i]),
232 47628d21 2019-03-29 martijn version, &tm, reqid, token, params);
234 47628d21 2019-03-29 martijn smtp_callbacks[i].smtp_report(&(smtp_callbacks[i]),
235 47628d21 2019-03-29 martijn version, &tm, reqid, params);
237 6bf2fad2 2019-03-29 martijn if (linelen == 0 || errno != EAGAIN)
238 eaefb262 2019-03-27 martijn event_del(stdinev);
241 a248a3c1 2019-03-27 martijn static void
242 42a4c512 2019-03-28 martijn smtp_connect(struct smtp_callback *cb, int version, struct timespec *tm,
243 42a4c512 2019-03-28 martijn uint64_t reqid, uint64_t token, char *params)
245 03a6f52a 2019-03-29 martijn struct inx_addr addrx;
246 03a6f52a 2019-03-29 martijn char *hostname;
247 a248a3c1 2019-03-27 martijn char *address;
248 a248a3c1 2019-03-27 martijn int ret;
249 03a6f52a 2019-03-29 martijn void (*f)(char *, int, struct timespec *,char *, char *, uint64_t,
250 03a6f52a 2019-03-29 martijn uint64_t, char *, struct inx_addr *);
252 03a6f52a 2019-03-29 martijn hostname = params;
253 a248a3c1 2019-03-27 martijn if ((address = strchr(params, '|')) == NULL)
254 44775e42 2019-03-29 martijn fatalx("Invalid line received: missing address: %s", params);
255 a248a3c1 2019-03-27 martijn address++[0] = '\0';
257 03a6f52a 2019-03-29 martijn addrx.af = AF_INET;
258 a248a3c1 2019-03-27 martijn if (strncasecmp(address, "ipv6:", 5) == 0) {
259 03a6f52a 2019-03-29 martijn addrx.af = AF_INET6;
260 a248a3c1 2019-03-27 martijn address += 5;
263 03a6f52a 2019-03-29 martijn ret = inet_pton(addrx.af, address, addrx.af == AF_INET ?
264 03a6f52a 2019-03-29 martijn (void *)&(addrx.addr) : (void *)&(addrx.addr6));
265 a248a3c1 2019-03-27 martijn if (ret == 0)
266 44775e42 2019-03-29 martijn fatalx("Invalid line received: Couldn't parse address: %s", params);
267 a248a3c1 2019-03-27 martijn if (ret == -1)
268 44775e42 2019-03-29 martijn fatal("Couldn't convert address: %s", params);
270 03a6f52a 2019-03-29 martijn f = cb->cb;
271 03a6f52a 2019-03-29 martijn f(cb->type, version, tm, cb->direction, cb->phase, reqid, token,
272 03a6f52a 2019-03-29 martijn hostname, &addrx);
275 47628d21 2019-03-29 martijn static void
276 02090eee 2019-03-29 martijn smtp_dataline(struct smtp_callback *cb, int version, struct timespec *tm,
277 02090eee 2019-03-29 martijn uint64_t reqid, uint64_t token, char *line)
279 02090eee 2019-03-29 martijn void (*f)(char *, int, struct timespec *,char *, char *, uint64_t,
280 02090eee 2019-03-29 martijn uint64_t, char *);
282 02090eee 2019-03-29 martijn f = cb->cb;
283 02090eee 2019-03-29 martijn f(cb->type, version, tm, cb->direction, cb->phase, reqid, token,
287 02090eee 2019-03-29 martijn static void
288 47628d21 2019-03-29 martijn smtp_in_link_disconnect(struct smtp_callback *cb, int version,
289 47628d21 2019-03-29 martijn struct timespec *tm, uint64_t reqid, char *params)
291 47628d21 2019-03-29 martijn void (*f)(char *, int, struct timespec *, char *, char *, uint64_t);
293 47628d21 2019-03-29 martijn f = cb->cb;
294 47628d21 2019-03-29 martijn f(cb->type, version, tm, cb->direction, cb->phase, reqid);
298 57cbfccc 2019-03-27 martijn smtp_filter_proceed(uint64_t reqid, uint64_t token)
300 42a4c512 2019-03-28 martijn printf("filter-result|%016"PRIx64"|%016"PRIx64"|proceed\n", token,
302 6f50562c 2019-03-28 martijn fflush(stdout);
306 57cbfccc 2019-03-27 martijn smtp_filter_reject(uint64_t reqid, uint64_t token, int code,
307 57cbfccc 2019-03-27 martijn const char *reason, ...)
309 57cbfccc 2019-03-27 martijn va_list ap;
311 57cbfccc 2019-03-27 martijn if (code < 200 || code > 599)
312 b633fa4c 2019-03-29 martijn fatalx("Invalid reject code");
314 57cbfccc 2019-03-27 martijn printf("filter-result|%016"PRIx64"|%016"PRIx64"|reject|%d ", token,
315 57cbfccc 2019-03-27 martijn reqid, code);
316 57cbfccc 2019-03-27 martijn va_start(ap, reason);
317 57cbfccc 2019-03-27 martijn vprintf(reason, ap);
318 57cbfccc 2019-03-27 martijn va_end(ap);
319 57cbfccc 2019-03-27 martijn putchar('\n');
320 6f50562c 2019-03-28 martijn fflush(stdout);
324 57cbfccc 2019-03-27 martijn smtp_filter_disconnect(uint64_t reqid, uint64_t token, const char *reason, ...)
326 57cbfccc 2019-03-27 martijn va_list ap;
328 57cbfccc 2019-03-27 martijn printf("filter-result|%016"PRIx64"|%016"PRIx64"|disconnect|421 ",
329 57cbfccc 2019-03-27 martijn token, reqid);
330 57cbfccc 2019-03-27 martijn va_start(ap, reason);
331 57cbfccc 2019-03-27 martijn vprintf(reason, ap);
332 57cbfccc 2019-03-27 martijn va_end(ap);
333 57cbfccc 2019-03-27 martijn putchar('\n');
334 6f50562c 2019-03-28 martijn fflush(stdout);
338 02090eee 2019-03-29 martijn smtp_filter_dataline(uint64_t reqid, uint64_t token, const char *line, ...)
340 02090eee 2019-03-29 martijn va_list ap;
342 02090eee 2019-03-29 martijn printf("filter-dataline|%016"PRIx64"|%016"PRIx64"|", token, reqid);
343 02090eee 2019-03-29 martijn va_start(ap, line);
344 02090eee 2019-03-29 martijn vprintf(line, ap);
345 02090eee 2019-03-29 martijn va_end(ap);
346 02090eee 2019-03-29 martijn putchar('\n');
347 02090eee 2019-03-29 martijn fflush(stdout);
350 a248a3c1 2019-03-27 martijn static int
351 03a6f52a 2019-03-29 martijn smtp_register(char *type, char *phase, char *direction, void *cb)
355 73ac3ec1 2019-03-27 martijn if (ready)
356 b633fa4c 2019-03-29 martijn fatalx("Can't register when proc is running");
358 a248a3c1 2019-03-27 martijn for (i = 0; i < NITEMS(smtp_callbacks); i++) {
359 a248a3c1 2019-03-27 martijn if (strcmp(type, smtp_callbacks[i].type) == 0 &&
360 a248a3c1 2019-03-27 martijn strcmp(phase, smtp_callbacks[i].phase) == 0 &&
361 a248a3c1 2019-03-27 martijn strcmp(direction, smtp_callbacks[i].direction) == 0) {
362 a248a3c1 2019-03-27 martijn if (smtp_callbacks[i].cb != NULL) {
363 a248a3c1 2019-03-27 martijn errno = EALREADY;
364 a248a3c1 2019-03-27 martijn return -1;
366 a248a3c1 2019-03-27 martijn smtp_callbacks[i].cb = cb;
367 a248a3c1 2019-03-27 martijn printf("register|%s|%s|%s\n", type, direction, phase);
368 a248a3c1 2019-03-27 martijn return 0;
371 a248a3c1 2019-03-27 martijn errno = EINVAL;
372 a248a3c1 2019-03-27 martijn return -1;