1 /* $OpenBSD: mda_variables.c,v 1.7 2021/06/14 17:58:15 eric Exp $ */ 2 3 /* 4 * Copyright (c) 2011-2017 Gilles Chehade <gilles@poolp.org> 5 * Copyright (c) 2012 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 <stdlib.h> 21 #include <string.h> 22 23 #include "smtpd.h" 24 #include "log.h" 25 26 #define EXPAND_DEPTH 10 27 28 ssize_t mda_expand_format(char *, size_t, const struct deliver *, 29 const struct userinfo *, const char *); 30 static ssize_t mda_expand_token(char *, size_t, const char *, 31 const struct deliver *, const struct userinfo *, const char *); 32 static int mod_lowercase(char *, size_t); 33 static int mod_uppercase(char *, size_t); 34 static int mod_strip(char *, size_t); 35 36 static struct modifiers { 37 char *name; 38 int (*f)(char *buf, size_t len); 39 } token_modifiers[] = { 40 { "lowercase", mod_lowercase }, 41 { "uppercase", mod_uppercase }, 42 { "strip", mod_strip }, 43 { "raw", NULL }, /* special case, must stay last */ 44 }; 45 46 #define MAXTOKENLEN 128 47 48 static ssize_t 49 mda_expand_token(char *dest, size_t len, const char *token, 50 const struct deliver *dlv, const struct userinfo *ui, const char *mda_command) 51 { 52 char rtoken[MAXTOKENLEN]; 53 char tmp[EXPAND_BUFFER]; 54 const char *string; 55 char *lbracket, *rbracket, *content, *sep, *mods; 56 ssize_t i; 57 ssize_t begoff, endoff; 58 const char *errstr = NULL; 59 int replace = 1; 60 int raw = 0; 61 62 begoff = 0; 63 endoff = EXPAND_BUFFER; 64 mods = NULL; 65 66 if (strlcpy(rtoken, token, sizeof rtoken) >= sizeof rtoken) 67 return -1; 68 69 /* token[x[:y]] -> extracts optional x and y, converts into offsets */ 70 if ((lbracket = strchr(rtoken, '[')) && 71 (rbracket = strchr(rtoken, ']'))) { 72 /* ] before [ ... or empty */ 73 if (rbracket < lbracket || rbracket - lbracket <= 1) 74 return -1; 75 76 *lbracket = *rbracket = '\0'; 77 content = lbracket + 1; 78 79 if ((sep = strchr(content, ':')) == NULL) 80 endoff = begoff = strtonum(content, -EXPAND_BUFFER, 81 EXPAND_BUFFER, &errstr); 82 else { 83 *sep = '\0'; 84 if (content != sep) 85 begoff = strtonum(content, -EXPAND_BUFFER, 86 EXPAND_BUFFER, &errstr); 87 if (*(++sep)) { 88 if (errstr == NULL) 89 endoff = strtonum(sep, -EXPAND_BUFFER, 90 EXPAND_BUFFER, &errstr); 91 } 92 } 93 if (errstr) 94 return -1; 95 96 /* token:mod_1,mod_2,mod_n -> extract modifiers */ 97 mods = strchr(rbracket + 1, ':'); 98 } else { 99 if ((mods = strchr(rtoken, ':')) != NULL) 100 *mods++ = '\0'; 101 } 102 103 /* token -> expanded token */ 104 if (!strcasecmp("sender", rtoken)) { 105 if (snprintf(tmp, sizeof tmp, "%s@%s", 106 dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) 107 return -1; 108 if (strcmp(tmp, "@") == 0) 109 (void)strlcpy(tmp, "", sizeof tmp); 110 string = tmp; 111 } 112 else if (!strcasecmp("rcpt", rtoken)) { 113 if (snprintf(tmp, sizeof tmp, "%s@%s", 114 dlv->rcpt.user, dlv->rcpt.domain) >= (int)sizeof tmp) 115 return -1; 116 if (strcmp(tmp, "@") == 0) 117 (void)strlcpy(tmp, "", sizeof tmp); 118 string = tmp; 119 } 120 else if (!strcasecmp("dest", rtoken)) { 121 if (snprintf(tmp, sizeof tmp, "%s@%s", 122 dlv->dest.user, dlv->dest.domain) >= (int)sizeof tmp) 123 return -1; 124 if (strcmp(tmp, "@") == 0) 125 (void)strlcpy(tmp, "", sizeof tmp); 126 string = tmp; 127 } 128 else if (!strcasecmp("sender.user", rtoken)) 129 string = dlv->sender.user; 130 else if (!strcasecmp("sender.domain", rtoken)) 131 string = dlv->sender.domain; 132 else if (!strcasecmp("user.username", rtoken)) 133 string = ui->username; 134 else if (!strcasecmp("user.directory", rtoken)) { 135 string = ui->directory; 136 replace = 0; 137 } 138 else if (!strcasecmp("rcpt.user", rtoken)) 139 string = dlv->rcpt.user; 140 else if (!strcasecmp("rcpt.domain", rtoken)) 141 string = dlv->rcpt.domain; 142 else if (!strcasecmp("dest.user", rtoken)) 143 string = dlv->dest.user; 144 else if (!strcasecmp("dest.domain", rtoken)) 145 string = dlv->dest.domain; 146 else if (!strcasecmp("mda", rtoken)) { 147 string = mda_command; 148 replace = 0; 149 } 150 else if (!strcasecmp("mbox.from", rtoken)) { 151 if (snprintf(tmp, sizeof tmp, "%s@%s", 152 dlv->sender.user, dlv->sender.domain) >= (int)sizeof tmp) 153 return -1; 154 if (strcmp(tmp, "@") == 0) 155 (void)strlcpy(tmp, "MAILER-DAEMON", sizeof tmp); 156 string = tmp; 157 } 158 else 159 return -1; 160 161 if (string != tmp) { 162 if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp) 163 return -1; 164 string = tmp; 165 } 166 167 /* apply modifiers */ 168 if (mods != NULL) { 169 do { 170 if ((sep = strchr(mods, '|')) != NULL) 171 *sep++ = '\0'; 172 for (i = 0; (size_t)i < nitems(token_modifiers); ++i) { 173 if (!strcasecmp(token_modifiers[i].name, mods)) { 174 if (token_modifiers[i].f == NULL) { 175 raw = 1; 176 break; 177 } 178 if (!token_modifiers[i].f(tmp, sizeof tmp)) 179 return -1; /* modifier error */ 180 break; 181 } 182 } 183 if ((size_t)i == nitems(token_modifiers)) 184 return -1; /* modifier not found */ 185 } while ((mods = sep) != NULL); 186 } 187 188 if (!raw && replace) 189 for (i = 0; (size_t)i < strlen(tmp); ++i) 190 if (strchr(MAILADDR_ESCAPE, tmp[i])) 191 tmp[i] = ':'; 192 193 /* expanded string is empty */ 194 i = strlen(string); 195 if (i == 0) 196 return 0; 197 198 /* begin offset beyond end of string */ 199 if (begoff >= i) 200 return -1; 201 202 /* end offset beyond end of string, make it end of string */ 203 if (endoff >= i) 204 endoff = i - 1; 205 206 /* negative begin offset, make it relative to end of string */ 207 if (begoff < 0) 208 begoff += i; 209 /* negative end offset, make it relative to end of string, 210 * note that end offset is inclusive. 211 */ 212 if (endoff < 0) 213 endoff += i - 1; 214 215 /* check that final offsets are valid */ 216 if (begoff < 0 || endoff < 0 || endoff < begoff) 217 return -1; 218 endoff += 1; /* end offset is inclusive */ 219 220 /* check that substring does not exceed destination buffer length */ 221 i = endoff - begoff; 222 if ((size_t)i + 1 >= len) 223 return -1; 224 225 string += begoff; 226 for (; i; i--) { 227 *dest = *string; 228 dest++; 229 string++; 230 } 231 232 return endoff - begoff; 233 } 234 235 236 ssize_t 237 mda_expand_format(char *buf, size_t len, const struct deliver *dlv, 238 const struct userinfo *ui, const char *mda_command) 239 { 240 char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf; 241 char exptok[EXPAND_BUFFER]; 242 ssize_t exptoklen; 243 char token[MAXTOKENLEN]; 244 size_t ret, tmpret; 245 246 if (len < sizeof tmpbuf) { 247 log_warnx("mda_expand_format: tmp buffer < rule buffer"); 248 return -1; 249 } 250 251 memset(tmpbuf, 0, sizeof tmpbuf); 252 pbuf = buf; 253 ptmp = tmpbuf; 254 ret = tmpret = 0; 255 256 /* special case: ~/ only allowed expanded at the beginning */ 257 if (strncmp(pbuf, "~/", 2) == 0) { 258 tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory); 259 if (tmpret >= sizeof tmpbuf) { 260 log_warnx("warn: user directory for %s too large", 261 ui->directory); 262 return 0; 263 } 264 ret += tmpret; 265 ptmp += tmpret; 266 pbuf += 2; 267 } 268 269 270 /* expansion loop */ 271 for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) { 272 if (*pbuf == '%' && *(pbuf + 1) == '%') { 273 *ptmp++ = *pbuf++; 274 pbuf += 1; 275 tmpret = 1; 276 continue; 277 } 278 279 if (*pbuf != '%' || *(pbuf + 1) != '{') { 280 *ptmp++ = *pbuf++; 281 tmpret = 1; 282 continue; 283 } 284 285 /* %{...} otherwise fail */ 286 if (*(pbuf+1) != '{' || (ebuf = strchr(pbuf+1, '}')) == NULL) 287 return 0; 288 289 /* extract token from %{token} */ 290 if ((size_t)(ebuf - pbuf) - 1 >= sizeof token) 291 return 0; 292 293 memcpy(token, pbuf+2, ebuf-pbuf-1); 294 if (strchr(token, '}') == NULL) 295 return 0; 296 *strchr(token, '}') = '\0'; 297 298 exptoklen = mda_expand_token(exptok, sizeof exptok, token, dlv, 299 ui, mda_command); 300 if (exptoklen == -1) 301 return -1; 302 303 /* writing expanded token at ptmp will overflow tmpbuf */ 304 if (sizeof (tmpbuf) - (ptmp - tmpbuf) <= (size_t)exptoklen) 305 return -1; 306 307 memcpy(ptmp, exptok, exptoklen); 308 pbuf = ebuf + 1; 309 ptmp += exptoklen; 310 tmpret = exptoklen; 311 } 312 if (ret >= sizeof tmpbuf) 313 return -1; 314 315 if ((ret = strlcpy(buf, tmpbuf, len)) >= len) 316 return -1; 317 318 return ret; 319 } 320 321 static int 322 mod_lowercase(char *buf, size_t len) 323 { 324 char tmp[EXPAND_BUFFER]; 325 326 if (!lowercase(tmp, buf, sizeof tmp)) 327 return 0; 328 if (strlcpy(buf, tmp, len) >= len) 329 return 0; 330 return 1; 331 } 332 333 static int 334 mod_uppercase(char *buf, size_t len) 335 { 336 char tmp[EXPAND_BUFFER]; 337 338 if (!uppercase(tmp, buf, sizeof tmp)) 339 return 0; 340 if (strlcpy(buf, tmp, len) >= len) 341 return 0; 342 return 1; 343 } 344 345 static int 346 mod_strip(char *buf, size_t len) 347 { 348 char *tag, *at; 349 unsigned int i; 350 351 /* gilles+hackers -> gilles */ 352 if ((tag = strchr(buf, *env->sc_subaddressing_delim)) != NULL) { 353 /* gilles+hackers@poolp.org -> gilles@poolp.org */ 354 if ((at = strchr(tag, '@')) != NULL) { 355 for (i = 0; i <= strlen(at); ++i) 356 tag[i] = at[i]; 357 } else 358 *tag = '\0'; 359 } 360 return 1; 361 } 362