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 * 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 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 * 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 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 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 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 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 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