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