#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "smtpd.h"
#include "log.h"
#define PROTOCOL_VERSION "0.1"
struct table_proc_priv {
FILE *in;
FILE *out;
char *line;
size_t linesize;
char lastid[16];
};
static char *
table_proc_nextid(struct table *table)
{
struct table_proc_priv *priv = table->t_handle;
int r;
r = snprintf(priv->lastid, sizeof(priv->lastid), "%08x", arc4random());
if (r < 0 || (size_t)r >= sizeof(priv->lastid))
fatal("table-proc: snprintf");
return (priv->lastid);
}
static void
table_proc_send(struct table *table, const char *type, int service,
const char *param)
{
struct table_proc_priv *priv = table->t_handle;
struct timeval tv;
gettimeofday(&tv, NULL);
fprintf(priv->out, "table|%s|%lld.%06ld|%s|%s",
PROTOCOL_VERSION, (long long)tv.tv_sec, (long)tv.tv_usec,
table->t_name, type);
if (service != -1) {
fprintf(priv->out, "|%s|%s", table_service_name(service),
table_proc_nextid(table));
if (param)
fprintf(priv->out, "|%s", param);
fputc('\n', priv->out);
} else
fprintf(priv->out, "|%s\n", table_proc_nextid(table));
if (fflush(priv->out) == EOF)
fatal("table-proc: fflush");
}
static const char *
table_proc_recv(struct table *table, const char *type)
{
struct table_proc_priv *priv = table->t_handle;
const char *l;
ssize_t linelen;
size_t len;
if ((linelen = getline(&priv->line, &priv->linesize, priv->in)) == -1)
fatal("table-proc: getline");
priv->line[strcspn(priv->line, "\n")] = '\0';
l = priv->line;
len = strlen(type);
if (strncmp(l, type, len) != 0)
goto err;
l += len;
if (*l != '|')
goto err;
l++;
len = strlen(priv->lastid);
if (strncmp(l, priv->lastid, len) != 0)
goto err;
l += len;
if (*l != '|')
goto err;
return (++l);
err:
log_warnx("warn: table-proc: failed to parse reply");
fatalx("table-proc: exiting");
}
static int
table_proc_open(struct table *table)
{
struct table_proc_priv *priv;
const char *s;
ssize_t len;
int service, services = 0;
int fd, fdd;
priv = xcalloc(1, sizeof(*priv));
fd = fork_proc_backend("table", table->t_config, table->t_name, 1);
if (fd == -1)
fatalx("table-proc: exiting");
if ((fdd = dup(fd)) == -1) {
log_warnx("warn: table-proc: dup");
fatalx("table-proc: exiting");
}
if ((priv->in = fdopen(fd, "r")) == NULL)
fatalx("table-proc: fdopen");
if ((priv->out = fdopen(fdd, "w")) == NULL)
fatalx("table-proc: fdopen");
fprintf(priv->out, "config|smtpd-version|"SMTPD_VERSION"\n");
fprintf(priv->out, "config|protocol|"PROTOCOL_VERSION"\n");
fprintf(priv->out, "config|tablename|%s\n", table->t_name);
fprintf(priv->out, "config|ready\n");
if (fflush(priv->out) == EOF)
fatalx("table-proc: fflush");
while ((len = getline(&priv->line, &priv->linesize, priv->in)) != -1) {
priv->line[strcspn(priv->line, "\n")] = '\0';
if (strncmp(priv->line, "register|", 9) != 0)
fatalx("table-proc: invalid handshake reply");
s = priv->line + 9;
if (!strcmp(s, "ready"))
break;
service = table_service_from_name(s);
if (service == -1 || service == K_NONE)
fatalx("table-proc: unknown service %s", s);
services |= service;
}
if (ferror(priv->in))
fatal("table-proc: getline");
if (feof(priv->in))
fatalx("table-proc: unexpected EOF during handshake");
if (services == 0)
fatalx("table-proc: no services registered");
table->t_services = services;
table->t_handle = priv;
return (1);
}
static int
table_proc_update(struct table *table)
{
const char *r;
table_proc_send(table, "update", -1, NULL);
r = table_proc_recv(table, "update-result");
if (!strcmp(r, "ok"))
return (1);
if (!strncmp(r, "error", 5)) {
if (r[5] == '|') {
r += 6;
log_warnx("warn: table-proc: %s update failed: %s",
table->t_name, r);
}
return (0);
}
log_warnx("warn: table-proc: failed parse reply");
fatalx("table-proc: exiting");
}
static void
table_proc_close(struct table *table)
{
struct table_proc_priv *priv = table->t_handle;
if (fclose(priv->in) == EOF)
fatal("table-proc: fclose");
if (fclose(priv->out) == EOF)
fatal("table-proc: fclose");
free(priv->line);
free(priv);
table->t_handle = NULL;
}
static int
table_proc_lookup(struct table *table, enum table_service s, const char *k, char **dst)
{
const char *req = "lookup", *res = "lookup-result";
const char *r;
if (dst == NULL) {
req = "check";
res = "check-result";
}
if (k[strcspn(k, "\r\n")] != '\0')
return (-1);
table_proc_send(table, req, s, k);
r = table_proc_recv(table, res);
if (!strcmp(r, "not-found"))
return (0);
if (!strncmp(r, "error", 5)) {
if (r[5] == '|') {
r += 6;
log_warnx("warn: table-proc: %s %s failed: %s",
table->t_name, req, r);
}
return (-1);
}
if (dst == NULL) {
if (!strncmp(r, "found", 5))
return (1);
log_warnx("warn: table-proc: failed to parse reply");
fatalx("table-proc: exiting");
}
if (strncmp(r, "found|", 6) != 0) {
log_warnx("warn: table-proc: failed to parse reply");
fatalx("table-proc: exiting");
}
r += 6;
if (*r == '\0') {
log_warnx("warn: table-proc: empty response");
fatalx("table-proc: exiting");
}
if ((*dst = strdup(r)) == NULL)
return (-1);
return (1);
}
static int
table_proc_fetch(struct table *table, enum table_service s, char **dst)
{
const char *r;
table_proc_send(table, "fetch", s, NULL);
r = table_proc_recv(table, "fetch-result");
if (!strcmp(r, "not-found"))
return (0);
if (!strncmp(r, "error", 5)) {
if (r[5] == '|') {
r += 6;
log_warnx("warn: table-proc: %s fetch failed: %s",
table->t_name, r);
}
return (-1);
}
if (strncmp(r, "found|", 6) != 0) {
log_warnx("warn: table-proc: failed to parse reply");
fatalx("table-proc: exiting");
}
r += 6;
if (*r == '\0') {
log_warnx("warn: table-proc: empty response");
fatalx("table-proc: exiting");
}
if ((*dst = strdup(r)) == NULL)
return (-1);
return (1);
}
struct table_backend table_backend_proc = {
.name = "proc",
.services = K_ANY,
.config = NULL,
.add = NULL,
.dump = NULL,
.open = table_proc_open,
.update = table_proc_update,
.close = table_proc_close,
.lookup = table_proc_lookup,
.fetch = table_proc_fetch,
};