xref: /openbsd-src/usr.sbin/smtpd/table_proc.c (revision 2ee83f25fcae94b5622e5946df51671cece070ef)
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