1 /* $OpenBSD: table_proc.c,v 1.23 2024/05/28 07:10:30 op Exp $ */
2
3 /*
4 * Copyright (c) 2024 Omar Polo <op@openbsd.org>
5 * Copyright (c) 2013 Eric Faurot <eric@openbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include "smtpd.h"
27 #include "log.h"
28
29 #define PROTOCOL_VERSION "0.1"
30
31 struct table_proc_priv {
32 FILE *in;
33 FILE *out;
34 char *line;
35 size_t linesize;
36
37 /*
38 * The last ID used in a request. At the moment the protocol
39 * is synchronous from our point of view, so it's used to
40 * assert that the table replied with the correct ID.
41 */
42 char lastid[16];
43 };
44
45 static char *
table_proc_nextid(struct table * table)46 table_proc_nextid(struct table *table)
47 {
48 struct table_proc_priv *priv = table->t_handle;
49 int r;
50
51 r = snprintf(priv->lastid, sizeof(priv->lastid), "%08x", arc4random());
52 if (r < 0 || (size_t)r >= sizeof(priv->lastid))
53 fatal("table-proc: snprintf");
54
55 return (priv->lastid);
56 }
57
58 static void
table_proc_send(struct table * table,const char * type,int service,const char * param)59 table_proc_send(struct table *table, const char *type, int service,
60 const char *param)
61 {
62 struct table_proc_priv *priv = table->t_handle;
63 struct timeval tv;
64
65 gettimeofday(&tv, NULL);
66 fprintf(priv->out, "table|%s|%lld.%06ld|%s|%s",
67 PROTOCOL_VERSION, (long long)tv.tv_sec, (long)tv.tv_usec,
68 table->t_name, type);
69 if (service != -1) {
70 fprintf(priv->out, "|%s|%s", table_service_name(service),
71 table_proc_nextid(table));
72 if (param)
73 fprintf(priv->out, "|%s", param);
74 fputc('\n', priv->out);
75 } else
76 fprintf(priv->out, "|%s\n", table_proc_nextid(table));
77
78 if (fflush(priv->out) == EOF)
79 fatal("table-proc: fflush");
80 }
81
82 static const char *
table_proc_recv(struct table * table,const char * type)83 table_proc_recv(struct table *table, const char *type)
84 {
85 struct table_proc_priv *priv = table->t_handle;
86 const char *l;
87 ssize_t linelen;
88 size_t len;
89
90 if ((linelen = getline(&priv->line, &priv->linesize, priv->in)) == -1)
91 fatal("table-proc: getline");
92 priv->line[strcspn(priv->line, "\n")] = '\0';
93 l = priv->line;
94
95 len = strlen(type);
96 if (strncmp(l, type, len) != 0)
97 goto err;
98 l += len;
99
100 if (*l != '|')
101 goto err;
102 l++;
103
104 len = strlen(priv->lastid);
105 if (strncmp(l, priv->lastid, len) != 0)
106 goto err;
107 l += len;
108
109 if (*l != '|')
110 goto err;
111 return (++l);
112
113 err:
114 log_warnx("warn: table-proc: failed to parse reply");
115 fatalx("table-proc: exiting");
116 }
117
118 /*
119 * API
120 */
121
122 static int
table_proc_open(struct table * table)123 table_proc_open(struct table *table)
124 {
125 struct table_proc_priv *priv;
126 const char *s;
127 ssize_t len;
128 int service, services = 0;
129 int fd, fdd;
130
131 priv = xcalloc(1, sizeof(*priv));
132
133 fd = fork_proc_backend("table", table->t_config, table->t_name, 1);
134 if (fd == -1)
135 fatalx("table-proc: exiting");
136 if ((fdd = dup(fd)) == -1) {
137 log_warnx("warn: table-proc: dup");
138 fatalx("table-proc: exiting");
139 }
140 if ((priv->in = fdopen(fd, "r")) == NULL)
141 fatalx("table-proc: fdopen");
142 if ((priv->out = fdopen(fdd, "w")) == NULL)
143 fatalx("table-proc: fdopen");
144
145 fprintf(priv->out, "config|smtpd-version|"SMTPD_VERSION"\n");
146 fprintf(priv->out, "config|protocol|"PROTOCOL_VERSION"\n");
147 fprintf(priv->out, "config|tablename|%s\n", table->t_name);
148 fprintf(priv->out, "config|ready\n");
149 if (fflush(priv->out) == EOF)
150 fatalx("table-proc: fflush");
151
152 while ((len = getline(&priv->line, &priv->linesize, priv->in)) != -1) {
153 priv->line[strcspn(priv->line, "\n")] = '\0';
154
155 if (strncmp(priv->line, "register|", 9) != 0)
156 fatalx("table-proc: invalid handshake reply");
157
158 s = priv->line + 9;
159 if (!strcmp(s, "ready"))
160 break;
161 service = table_service_from_name(s);
162 if (service == -1 || service == K_NONE)
163 fatalx("table-proc: unknown service %s", s);
164
165 services |= service;
166 }
167
168 if (ferror(priv->in))
169 fatal("table-proc: getline");
170 if (feof(priv->in))
171 fatalx("table-proc: unexpected EOF during handshake");
172 if (services == 0)
173 fatalx("table-proc: no services registered");
174
175 table->t_services = services;
176 table->t_handle = priv;
177
178 return (1);
179 }
180
181 static int
table_proc_update(struct table * table)182 table_proc_update(struct table *table)
183 {
184 const char *r;
185
186 table_proc_send(table, "update", -1, NULL);
187 r = table_proc_recv(table, "update-result");
188 if (!strcmp(r, "ok"))
189 return (1);
190
191 if (!strncmp(r, "error", 5)) {
192 if (r[5] == '|') {
193 r += 6;
194 log_warnx("warn: table-proc: %s update failed: %s",
195 table->t_name, r);
196 }
197 return (0);
198 }
199
200 log_warnx("warn: table-proc: failed parse reply");
201 fatalx("table-proc: exiting");
202 }
203
204 static void
table_proc_close(struct table * table)205 table_proc_close(struct table *table)
206 {
207 struct table_proc_priv *priv = table->t_handle;
208
209 if (fclose(priv->in) == EOF)
210 fatal("table-proc: fclose");
211 if (fclose(priv->out) == EOF)
212 fatal("table-proc: fclose");
213 free(priv->line);
214 free(priv);
215
216 table->t_handle = NULL;
217 }
218
219 static int
table_proc_lookup(struct table * table,enum table_service s,const char * k,char ** dst)220 table_proc_lookup(struct table *table, enum table_service s, const char *k, char **dst)
221 {
222 const char *req = "lookup", *res = "lookup-result";
223 const char *r;
224
225 if (dst == NULL) {
226 req = "check";
227 res = "check-result";
228 }
229
230 table_proc_send(table, req, s, k);
231 r = table_proc_recv(table, res);
232
233 if (!strcmp(r, "not-found"))
234 return (0);
235
236 if (!strncmp(r, "error", 5)) {
237 if (r[5] == '|') {
238 r += 6;
239 log_warnx("warn: table-proc: %s %s failed: %s",
240 table->t_name, req, r);
241 }
242 return (-1);
243 }
244
245 if (dst == NULL) {
246 /* check op */
247 if (!strncmp(r, "found", 5))
248 return (1);
249 log_warnx("warn: table-proc: failed to parse reply");
250 fatalx("table-proc: exiting");
251 }
252
253 /* lookup op */
254 if (strncmp(r, "found|", 6) != 0) {
255 log_warnx("warn: table-proc: failed to parse reply");
256 fatalx("table-proc: exiting");
257 }
258 r += 6;
259 if (*r == '\0') {
260 log_warnx("warn: table-proc: empty response");
261 fatalx("table-proc: exiting");
262 }
263 if ((*dst = strdup(r)) == NULL)
264 return (-1);
265 return (1);
266 }
267
268 static int
table_proc_fetch(struct table * table,enum table_service s,char ** dst)269 table_proc_fetch(struct table *table, enum table_service s, char **dst)
270 {
271 const char *r;
272
273 table_proc_send(table, "fetch", s, NULL);
274 r = table_proc_recv(table, "fetch-result");
275
276 if (!strcmp(r, "not-found"))
277 return (0);
278
279 if (!strncmp(r, "error", 5)) {
280 if (r[5] == '|') {
281 r += 6;
282 log_warnx("warn: table-proc: %s fetch failed: %s",
283 table->t_name, r);
284 }
285 return (-1);
286 }
287
288 if (strncmp(r, "found|", 6) != 0) {
289 log_warnx("warn: table-proc: failed to parse reply");
290 fatalx("table-proc: exiting");
291 }
292 r += 6;
293 if (*r == '\0') {
294 log_warnx("warn: table-proc: empty response");
295 fatalx("table-proc: exiting");
296 }
297
298 if ((*dst = strdup(r)) == NULL)
299 return (-1);
300 return (1);
301 }
302
303 struct table_backend table_backend_proc = {
304 .name = "proc",
305 .services = K_ANY,
306 .config = NULL,
307 .add = NULL,
308 .dump = NULL,
309 .open = table_proc_open,
310 .update = table_proc_update,
311 .close = table_proc_close,
312 .lookup = table_proc_lookup,
313 .fetch = table_proc_fetch,
314 };
315