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 void smtp_newline(int, short, void *);
41 static void smtp_connect(struct smtp_callback *, int, struct timespec *,
42 uint64_t, uint64_t, char *);
43 static void smtp_dataline(struct smtp_callback *, int, struct timespec *,
44 uint64_t, uint64_t, char *);
45 static void smtp_in_link_disconnect(struct smtp_callback *, int, struct timespec *,
46 uint64_t, char *);
48 struct smtp_callback {
49 char *type;
50 char *phase;
51 char *direction;
52 union {
53 void (*smtp_filter)(struct smtp_callback *, int,
54 struct timespec *, uint64_t, uint64_t, char *);
55 void (*smtp_report)(struct smtp_callback *, int,
56 struct timespec *, uint64_t, char *);
57 };
58 void *cb;
59 } smtp_callbacks[] = {
60 {"filter", "connect", "smtp-in", .smtp_filter = smtp_connect, NULL},
61 {"filter", "data-line", "smtp-in", .smtp_filter = smtp_dataline, NULL},
62 {"report", "link-disconnect", "smtp-in",
63 .smtp_report = smtp_in_link_disconnect, NULL}
64 };
66 static int ready = 0;
68 int
69 smtp_register_filter_connect(void (*cb)(char *, int, struct timespec *, char *,
70 char *, uint64_t, uint64_t, char *, struct inx_addr *))
71 {
72 return smtp_register("filter", "connect", "smtp-in", (void *)cb);
73 }
75 int
76 smtp_register_filter_dataline(void (*cb)(char *, int, struct timespec *, char *,
77 char *, uint64_t, uint64_t, char *))
78 {
79 return smtp_register("filter", "data-line", "smtp-in", (void *)cb);
80 }
82 int
83 smtp_in_register_report_disconnect(void (*cb)(char *, int, struct timespec *,
84 char *, char *, uint64_t))
85 {
86 return smtp_register("report", "link-disconnect", "smtp-in", (void *)cb);
87 }
89 void
90 smtp_run(int debug)
91 {
92 struct event stdinev;
94 printf("register|ready\n");
95 fflush(stdout);
96 ready = 1;
98 log_init(debug, LOG_MAIL);
99 event_init();
100 event_set(&stdinev, STDIN_FILENO, EV_READ | EV_PERSIST, smtp_newline,
101 &stdinev);
102 event_add(&stdinev, NULL);
104 if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1)
105 fatal("fcntl");
106 event_dispatch();
109 static void
110 smtp_newline(int fd, short event, void *arg)
112 struct event *stdinev = (struct event *)arg;
113 char *line = NULL;
114 size_t linesize = 0;
115 ssize_t linelen;
116 char *start, *end, *type, *direction, *phase, *params;
117 int version;
118 struct timespec tm;
119 uint64_t reqid, token;
120 int i;
122 while ((linelen = getline(&line, &linesize, stdin)) != -1) {
123 if (line[linelen - 1] != '\n')
124 fatalx("Invalid line received: missing newline");
125 line[linelen - 1] = '\0';
126 type = line;
127 if ((start = strchr(type, '|')) == NULL)
128 fatalx("Invalid line received: missing version");
129 start++[0] = '\0';
130 if ((end = strchr(start, '|')) == NULL)
131 fatalx("Invalid line received: missing time");
132 end++[0] = '\0';
133 if (strcmp(start, "1") != 0)
134 fatalx("Unsupported protocol received: %s", start);
135 version = 1;
136 start = end;
137 if ((direction = strchr(start, '|')) == NULL)
138 fatalx("Invalid line received: missing direction");
139 direction++[0] = '\0';
140 tm.tv_sec = (time_t) strtoull(start, &end, 10);
141 tm.tv_nsec = 0;
142 if (start[0] == '\0' || (end[0] != '\0' && end[0] != '.'))
143 fatalx("Invalid line received: invalid timestamp");
144 if (end[0] == '.') {
145 start = end + 1;
146 tm.tv_nsec = strtol(start, &end, 10);
147 if (start[0] == '\0' || end[0] != '\0')
148 fatalx("Invalid line received: invalid "
149 "timestamp");
150 for (i = 9 - (end - start); i > 0; i--)
151 tm.tv_nsec *= 10;
153 if ((phase = strchr(direction, '|')) == NULL)
154 fatalx("Invalid line receieved: missing phase");
155 phase++[0] = '\0';
156 if ((start = strchr(phase, '|')) == NULL)
157 fatalx("Invalid line received: missing reqid");
158 start++[0] = '\0';
159 reqid = strtoull(start, &params, 16);
160 if (start[0] == '|' || (params[0] != '|' & params[0] != '\0'))
161 fatalx("Invalid line received: invalid reqid");
162 params++;
164 for (i = 0; i < NITEMS(smtp_callbacks); i++) {
165 if (strcmp(type, smtp_callbacks[i].type) == 0 &&
166 strcmp(phase, smtp_callbacks[i].phase) == 0 &&
167 strcmp(direction, smtp_callbacks[i].direction) == 0)
168 break;
170 if (i == NITEMS(smtp_callbacks)) {
171 fatalx("Invalid line received: received unregistered "
172 "%s: %s", type, phase);
174 if (strcmp(type, "filter") == 0) {
175 start = params;
176 token = strtoull(start, &params, 16);
177 if (start[0] == '|' || params[0] != '|')
178 fatalx("Invalid line received: invalid token");
179 params++;
180 smtp_callbacks[i].smtp_filter(&(smtp_callbacks[i]),
181 version, &tm, reqid, token, params);
182 } else
183 smtp_callbacks[i].smtp_report(&(smtp_callbacks[i]),
184 version, &tm, reqid, params);
186 if (feof(stdin) || (ferror(stdin) && errno != EAGAIN))
187 event_del(stdinev);
190 static void
191 smtp_connect(struct smtp_callback *cb, int version, struct timespec *tm,
192 uint64_t reqid, uint64_t token, char *params)
194 struct inx_addr addrx;
195 char *hostname;
196 char *address;
197 int ret;
198 void (*f)(char *, int, struct timespec *,char *, char *, uint64_t,
199 uint64_t, char *, struct inx_addr *);
201 hostname = params;
202 if ((address = strchr(params, '|')) == NULL)
203 fatalx("Invalid line received: missing address");
204 address++[0] = '\0';
206 addrx.af = AF_INET;
207 if (strncasecmp(address, "ipv6:", 5) == 0) {
208 addrx.af = AF_INET6;
209 address += 5;
212 ret = inet_pton(addrx.af, address, addrx.af == AF_INET ?
213 (void *)&(addrx.addr) : (void *)&(addrx.addr6));
214 if (ret == 0)
215 fatalx("Invalid line received: Couldn't parse address");
216 if (ret == -1)
217 fatalx("Couldn't convert address");
219 f = cb->cb;
220 f(cb->type, version, tm, cb->direction, cb->phase, reqid, token,
221 hostname, &addrx);
224 static void
225 smtp_dataline(struct smtp_callback *cb, int version, struct timespec *tm,
226 uint64_t reqid, uint64_t token, char *line)
228 void (*f)(char *, int, struct timespec *,char *, char *, uint64_t,
229 uint64_t, char *);
231 f = cb->cb;
232 f(cb->type, version, tm, cb->direction, cb->phase, reqid, token,
233 line);
236 static void
237 smtp_in_link_disconnect(struct smtp_callback *cb, int version,
238 struct timespec *tm, uint64_t reqid, char *params)
240 void (*f)(char *, int, struct timespec *, char *, char *, uint64_t);
242 f = cb->cb;
243 f(cb->type, version, tm, cb->direction, cb->phase, reqid);
246 void
247 smtp_filter_proceed(uint64_t reqid, uint64_t token)
249 printf("filter-result|%016"PRIx64"|%016"PRIx64"|proceed\n", token,
250 reqid);
251 fflush(stdout);
254 void
255 smtp_filter_reject(uint64_t reqid, uint64_t token, int code,
256 const char *reason, ...)
258 va_list ap;
260 if (code < 200 || code > 599)
261 fatalx("Invalid reject code");
263 printf("filter-result|%016"PRIx64"|%016"PRIx64"|reject|%d ", token,
264 reqid, code);
265 va_start(ap, reason);
266 vprintf(reason, ap);
267 va_end(ap);
268 putchar('\n');
269 fflush(stdout);
272 void
273 smtp_filter_disconnect(uint64_t reqid, uint64_t token, const char *reason, ...)
275 va_list ap;
277 printf("filter-result|%016"PRIx64"|%016"PRIx64"|disconnect|421 ",
278 token, reqid);
279 va_start(ap, reason);
280 vprintf(reason, ap);
281 va_end(ap);
282 putchar('\n');
283 fflush(stdout);
286 void
287 smtp_filter_dataline(uint64_t reqid, uint64_t token, const char *line, ...)
289 va_list ap;
291 printf("filter-dataline|%016"PRIx64"|%016"PRIx64"|", token, reqid);
292 va_start(ap, line);
293 vprintf(line, ap);
294 va_end(ap);
295 putchar('\n');
296 fflush(stdout);
299 static int
300 smtp_register(char *type, char *phase, char *direction, void *cb)
302 int i;
304 if (ready)
305 fatalx("Can't register when proc is running");
307 for (i = 0; i < NITEMS(smtp_callbacks); i++) {
308 if (strcmp(type, smtp_callbacks[i].type) == 0 &&
309 strcmp(phase, smtp_callbacks[i].phase) == 0 &&
310 strcmp(direction, smtp_callbacks[i].direction) == 0) {
311 if (smtp_callbacks[i].cb != NULL) {
312 errno = EALREADY;
313 return -1;
315 smtp_callbacks[i].cb = cb;
316 printf("register|%s|%s|%s\n", type, direction, phase);
317 return 0;
320 errno = EINVAL;
321 return -1;