1 /* $OpenBSD: mda_variables.c,v 1.9 2023/03/19 16:43:44 millert 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 = NULL; 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 (string == NULL) 163 return -1; 164 if (strlcpy(tmp, string, sizeof tmp) >= sizeof tmp) 165 return -1; 166 string = tmp; 167 } 168 169 /* apply modifiers */ 170 if (mods != NULL) { 171 do { 172 if ((sep = strchr(mods, '|')) != NULL) 173 *sep++ = '\0'; 174 for (i = 0; (size_t)i < nitems(token_modifiers); ++i) { 175 if (!strcasecmp(token_modifiers[i].name, mods)) { 176 if (token_modifiers[i].f == NULL) { 177 raw = 1; 178 break; 179 } 180 if (!token_modifiers[i].f(tmp, sizeof tmp)) 181 return -1; /* modifier error */ 182 break; 183 } 184 } 185 if ((size_t)i == nitems(token_modifiers)) 186 return -1; /* modifier not found */ 187 } while ((mods = sep) != NULL); 188 } 189 190 if (!raw && replace) 191 for (i = 0; (size_t)i < strlen(tmp); ++i) 192 if (strchr(MAILADDR_ESCAPE, tmp[i])) 193 tmp[i] = ':'; 194 195 /* expanded string is empty */ 196 i = strlen(string); 197 if (i == 0) 198 return 0; 199 200 /* begin offset beyond end of string */ 201 if (begoff >= i) 202 return -1; 203 204 /* end offset beyond end of string, make it end of string */ 205 if (endoff >= i) 206 endoff = i - 1; 207 208 /* negative begin offset, make it relative to end of string */ 209 if (begoff < 0) 210 begoff += i; 211 /* negative end offset, make it relative to end of string, 212 * note that end offset is inclusive. 213 */ 214 if (endoff < 0) 215 endoff += i - 1; 216 217 /* check that final offsets are valid */ 218 if (begoff < 0 || endoff < 0 || endoff < begoff) 219 return -1; 220 endoff += 1; /* end offset is inclusive */ 221 222 /* check that substring does not exceed destination buffer length */ 223 i = endoff - begoff; 224 if ((size_t)i + 1 >= len) 225 return -1; 226 227 string += begoff; 228 for (; i; i--) { 229 *dest = *string; 230 dest++; 231 string++; 232 } 233 234 return endoff - begoff; 235 } 236 237 238 ssize_t 239 mda_expand_format(char *buf, size_t len, const struct deliver *dlv, 240 const struct userinfo *ui, const char *mda_command) 241 { 242 char tmpbuf[EXPAND_BUFFER], *ptmp, *pbuf, *ebuf; 243 char exptok[EXPAND_BUFFER]; 244 ssize_t exptoklen; 245 char token[MAXTOKENLEN]; 246 size_t ret, tmpret, toklen; 247 248 if (len < sizeof tmpbuf) { 249 log_warnx("mda_expand_format: tmp buffer < rule buffer"); 250 return -1; 251 } 252 253 memset(tmpbuf, 0, sizeof tmpbuf); 254 pbuf = buf; 255 ptmp = tmpbuf; 256 ret = tmpret = 0; 257 258 /* special case: ~/ only allowed expanded at the beginning */ 259 if (strncmp(pbuf, "~/", 2) == 0) { 260 tmpret = snprintf(ptmp, sizeof tmpbuf, "%s/", ui->directory); 261 if (tmpret >= sizeof tmpbuf) { 262 log_warnx("warn: user directory for %s too large", 263 ui->directory); 264 return 0; 265 } 266 ret += tmpret; 267 ptmp += tmpret; 268 pbuf += 2; 269 } 270 271 /* expansion loop */ 272 for (; *pbuf && ret < sizeof tmpbuf; ret += tmpret) { 273 if (*pbuf == '%' && *(pbuf + 1) == '%') { 274 *ptmp++ = *pbuf++; 275 pbuf += 1; 276 tmpret = 1; 277 continue; 278 } 279 280 if (*pbuf != '%' || *(pbuf + 1) != '{') { 281 *ptmp++ = *pbuf++; 282 tmpret = 1; 283 continue; 284 } 285 286 /* %{...} otherwise fail */ 287 if ((ebuf = strchr(pbuf+2, '}')) == NULL) 288 return 0; 289 290 /* extract token from %{token} */ 291 toklen = ebuf - (pbuf+2); 292 if (toklen >= sizeof token) 293 return 0; 294 295 memcpy(token, pbuf+2, toklen); 296 token[toklen] = '\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