src/usr.sbin/smtpd/mda_variables.c

362 lines
8.9 KiB
C

/* $OpenBSD: mda_variables.c,v 1.10 2024/04/23 13:34:51 jsg Exp $ */
/*
* Copyright (c) 2011-2017 Gilles Chehade <gilles@poolp.org>
* Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdlib.h>
#include <string.h>
#include "smtpd.h"
#include "log.h"
#define EXPAND_DEPTH 10
ssize_t mda_expand_format(char *, size_t, const struct deliver *,
const struct userinfo *, const char *);
static ssize_t mda_expand_token(char *, size_t, const char *,
const struct deliver *, const struct userinfo *, const char *);
static int mod_lowercase(char *, size_t);
static int mod_uppercase(char *, size_t);
static int mod_strip(char *, size_t);
static struct modifiers {
char *name;
int (*f)(char *buf, size_t len);
} token_modifiers[] = {
{ "lowercase", mod_lowercase },
{ "uppercase", mod_uppercase },
{ "strip", mod_strip },
{ "raw", NULL }, /* special case, must stay last */
};
#define MAXTOKENLEN 128
static ssize_t
mda_expand_token(char *dest, size_t len, const char *token,
const struct deliver *dlv, const struct userinfo *ui, const char *mda_command)
{
char rtoken[MAXTOKENLEN];
char tmp[EXPAND_BUFFER];
const char *string = NULL;
char *lbracket, *rbracket, *content, *sep, *mods;
ssize_t i;
ssize_t begoff, endoff;
const char *errstr = NULL;
int replace = 1;
int raw = 0;
begoff = 0;
endoff = EXPAND_BUFFER;
mods = NULL;
if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken)
return -1;
/* token[x[:y]] -> extracts optional x and y, converts into offsets */
if ((lbracket = strchr(rtoken, '[')) &&
(rbracket = strchr(rtoken, ']'))) {
/* ] before [ ... or empty */
if (rbracket < lbracket || rbracket - lbracket <= 1)
return -1;
*lbracket = *rbracket = '\0';
content = lbracket + 1;
if ((sep = strchr(content, ':')) == NULL)
endoff = begoff = strtonum(content, -EXPAND_BUFFER,
EXPAND_BUFFER, &errstr);
else {
*sep = '\0';
if (content != sep)
begoff = strtonum(content, -EXPAND_BUFFER,
EXPAND_BUFFER, &errstr);
if (*(++sep)) {
if (errstr == NULL)
endoff = strtonum(sep, -EXPAND_BUFFER,
EXPAND_BUFFER, &errstr);
}
}
if (errstr)
return -1;
/* token:mod_1,mod_2,mod_n -> extract modifiers */
mods = strchr(rbracket + 1, ':');
} else {
if ((mods = strchr(rtoken, ':')) != NULL)
*mods++ = '\0';
}
/* token -> expanded token */
if (!strcasecmp("sender", rtoken)) {
if (snprintf(tmp, sizeof tmp, "%s@%s",
dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp)
return -1;
if (strcmp(tmp, "@") == 0)
(void)strlcpy(tmp, "", sizeof tmp);
string = tmp;
}
else if (!strcasecmp("rcpt", rtoken)) {
if (snprintf(tmp, sizeof tmp, "%s@%s",
dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp)
return -1;
if (strcmp(tmp, "@") == 0)
(void)strlcpy(tmp, "", sizeof tmp);
string = tmp;
}
else if (!strcasecmp("dest", rtoken)) {
if (snprintf(tmp, sizeof tmp, "%s@%s",
dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp)
return -1;
if (strcmp(tmp, "@") == 0)
(void)strlcpy(tmp, "", sizeof tmp);
string = tmp;
}
else if (!strcasecmp("sender.user", rtoken))
string = dlv->sender.user;
else if (!strcasecmp("sender.domain", rtoken))
string = dlv->sender.domain;
else if (!strcasecmp("user.username", rtoken))
string = ui->username;
else if (!strcasecmp("user.directory", rtoken)) {
string = ui->directory;
replace = 0;
}
else if (!strcasecmp("rcpt.user", rtoken))
string = dlv->rcpt.user;
else if (!strcasecmp("rcpt.domain", rtoken))
string = dlv->rcpt.domain;
else if (!strcasecmp("dest.user", rtoken))
string = dlv->dest.user;
else if (!strcasecmp("dest.domain", rtoken))
string = dlv->dest.domain;
else if (!strcasecmp("mda", rtoken)) {
string = mda_command;
replace = 0;
}
else if (!strcasecmp("mbox.from", rtoken)) {
if (snprintf(tmp, sizeof tmp, "%s@%s",
dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp)
return -1;
if (strcmp(tmp, "@") == 0)
(void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp);
string = tmp;
}
else
return -1;
if (string != tmp) {
if (string == NULL)
return -1;
if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp)
return -1;
string = tmp;
}
/* apply modifiers */
if (mods != NULL) {
do {
if ((sep = strchr(mods, '|')) != NULL)
*sep++ = '\0';
for (i = 0; (size_t)i < nitems(token_modifiers); ++i) {
if (!strcasecmp(token_modifiers[i].name, mods)) {
if (token_modifiers[i].f == NULL) {
raw = 1;
break;
}
if (!token_modifiers[i].f(tmp, sizeof tmp))
return -1; /* modifier error */
break;
}
}
if ((size_t)i == nitems(token_modifiers))
return -1; /* modifier not found */
} while ((mods = sep) != NULL);
}
if (!raw && replace)
for (i = 0; (size_t)i < strlen(tmp); ++i)
if (strchr(MAILADDR_ESCAPE, tmp[i]))
tmp[i] = ':';
/* expanded string is empty */
i = strlen(string);
if (i == 0)
return 0;
/* begin offset beyond end of string */
if (begoff >= i)
return -1;
/* end offset beyond end of string, make it end of string */
if (endoff >= i)
endoff = i - 1;
/* negative begin offset, make it relative to end of string */
if (begoff < 0)
begoff += i;
/* negative end offset, make it relative to end of string,
* note that end offset is inclusive.
*/
if (endoff < 0)
endoff += i - 1;
/* check that final offsets are valid */
if (begoff < 0 || endoff < 0 || endoff < begoff)
return -1;
endoff += 1; /* end offset is inclusive */
/* check that substring does not exceed destination buffer length */
i = endoff - begoff;
if ((size_t)i + 1 >= len)
return -1;
string += begoff;
for (; i; i--) {
*dest = *string;
dest++;
string++;
}
return endoff - begoff;
}
ssize_t
mda_expand_format(char *buf, size_t len, const struct deliver *dlv,
const struct userinfo *ui, const char *mda_command)
{
char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf;
char exptok[EXPAND_BUFFER];
ssize_t exptoklen;
char token[MAXTOKENLEN];
size_t ret, tmpret, toklen;
if (len < sizeof tmpbuf) {
log_warnx("mda_expand_format: tmp buffer < rule buffer");
return -1;
}
memset(tmpbuf, 0, sizeof tmpbuf);
pbuf = buf;
ptmp = tmpbuf;
ret = tmpret = 0;
/* special case: ~/ only allowed expanded at the beginning */
if (strncmp(pbuf, "~/", 2) == 0) {
tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory);
if (tmpret >= sizeof tmpbuf) {
log_warnx("warn: user directory for %s too large",
ui->directory);
return 0;
}
ret += tmpret;
ptmp += tmpret;
pbuf += 2;
}
/* expansion loop */
for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) {
if (*pbuf == '%' && *(pbuf + 1) == '%') {
*ptmp++ = *pbuf++;
pbuf += 1;
tmpret = 1;
continue;
}
if (*pbuf != '%' || *(pbuf + 1) != '{') {
*ptmp++ = *pbuf++;
tmpret = 1;
continue;
}
/* %{...} otherwise fail */
if ((ebuf = strchr(pbuf+2, '}')) == NULL)
return 0;
/* extract token from %{token} */
toklen = ebuf - (pbuf+2);
if (toklen >= sizeof token)
return 0;
memcpy(token, pbuf+2, toklen);
token[toklen] = '\0';
exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv,
ui, mda_command);
if (exptoklen == -1)
return -1;
/* writing expanded token at ptmp will overflow tmpbuf */
if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen)
return -1;
memcpy(ptmp, exptok, exptoklen);
pbuf = ebuf + 1;
ptmp += exptoklen;
tmpret = exptoklen;
}
if (ret >= sizeof tmpbuf)
return -1;
if ((ret = strlcpy(buf, tmpbuf, len)) >= len)
return -1;
return ret;
}
static int
mod_lowercase(char *buf, size_t len)
{
char tmp[EXPAND_BUFFER];
if (!lowercase(tmp, buf, sizeof tmp))
return 0;
if (strlcpy(buf, tmp, len) >= len)
return 0;
return 1;
}
static int
mod_uppercase(char *buf, size_t len)
{
char tmp[EXPAND_BUFFER];
if (!uppercase(tmp, buf, sizeof tmp))
return 0;
if (strlcpy(buf, tmp, len) >= len)
return 0;
return 1;
}
static int
mod_strip(char *buf, size_t len)
{
char *tag, *at;
unsigned int i;
/* gilles+hackers -> gilles */
if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) {
/* gilles+hackers@poolp.org -> gilles@poolp.org */
if ((at = strchr(tag, '@')) != NULL) {
for (i = 0; i <= strlen(at); ++i)
tag[i] = at[i];
} else
*tag = '\0';
}
return 1;
}