Blob


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