comparison src/context.cpp @ 464:428de28b34b7

cleanup code for adding extra spf data in dkim_from
author Carl Byington <carl@five-ten-sg.com>
date Sun, 10 Mar 2019 08:49:27 -0700
parents f3f1ece619ba
children 79e944269c0b
comparison
equal deleted inserted replaced
463:95b4666a2a0b 464:428de28b34b7
614 w = w_; 614 w = w_;
615 con = con_; 615 con = con_;
616 } 616 }
617 617
618 618
619 DKIM::DKIM(const char *action_, const char *signer_) { 619 DKIM::DKIM(const char *action_, const char *signer_, const char*extraspf_) {
620 action = action_; 620 action = action_;
621 signer = signer_; 621 signer = signer_;
622 extraspf = extraspf_;
622 } 623 }
623 624
624 625
625 DNSBL::DNSBL(const char *n, const char *s, const char *m) { 626 DNSBL::DNSBL(const char *n, const char *s, const char *m) {
626 name = n; 627 name = n;
1109 my_syslog(queueid, buf); 1110 my_syslog(queueid, buf);
1110 } 1111 }
1111 } 1112 }
1112 1113
1113 1114
1114 const char *CONTEXT::extra_spf_data(const char *signers) {
1115 const char *e = strchr(signers, ';');
1116 if (e) e++;
1117 return e;
1118 }
1119
1120
1121 bool CONTEXT::in_signing_set(const char *s, const char *signers) { 1115 bool CONTEXT::in_signing_set(const char *s, const char *signers) {
1122 // s is an actual signer 1116 // s is an actual signer
1123 // signers is the set of acceptable signers, separated by commas 1117 // signers is the set of acceptable signers, separated by commas
1124 size_t n = strlen(s); 1118 size_t n = strlen(s);
1125 const char *p = signers; 1119 const char *p = signers;
1126 char *e = (char *)strchr(p, ';'); // only search up to ; which separates signers from extra spf data
1127 if (e) *e = '\0';
1128 bool rc = true;
1129 do { 1120 do {
1130 const char *c = strchr(p, ','); 1121 const char *c = strchr(p, ',');
1131 size_t m = (c) ? c-p : strlen(p); // length of this element in the signing set 1122 size_t m = (c) ? c-p : strlen(p); // length of this element in the signing set
1132 if ((m == n) && (strncasecmp(p, s, n) == 0)) break; // exact match 1123 if ((m == n) && (strncasecmp(p, s, n) == 0)) return true; // exact match
1133 if ((*p == '*') && (n >= m)) { 1124 if ((*p == '*') && (n >= m)) {
1134 if (strncasecmp(p+1, s+n-(m-1), m-1) == 0) break; // wildcard match 1125 if (strncasecmp(p+1, s+n-(m-1), m-1) == 0) return true; // wildcard match
1135 } 1126 }
1136 if (!c) { 1127 if (!c) return false;
1137 rc = false;
1138 break;
1139 }
1140 p = c + 1; 1128 p = c + 1;
1141 } while (true); 1129 } while (true);
1142 if (e) *e = ';'; 1130 }
1143 return rc; 1131
1144 } 1132
1145 1133 void CONTEXT::replace(char *buf, char *p, int nn, const char *what)
1146
1147 void CONTEXT::replace(char *buf, char *p, const char *what)
1148 { 1134 {
1149 // replace 4 chars in buf starting at p with what 1135 // replace nn chars in buf starting at p with what
1150 char repl[maxlen]; 1136 char repl[maxlen];
1151 size_t bn = strlen(buf); 1137 size_t bn = strlen(buf);
1152 size_t wn = strlen(what); 1138 size_t wn = strlen(what);
1153 if ((bn - 4 + wn) < (size_t)maxlen) { 1139 if ((bn - nn + wn) < (size_t)maxlen) {
1154 size_t n = p - buf; // leading part length 1140 size_t n = p - buf; // leading part length
1155 strncpy(repl, buf, n); // leading part 1141 strncpy(repl, buf, n); // leading part
1156 strcpy(repl+n, what); // replacement 1142 strcpy(repl+n, what); // replacement
1157 strcpy(repl+n+wn, buf+n+4); // trailing part 1143 strcpy(repl+n+wn, buf+n+nn); // trailing part
1158 strcpy(buf, repl); 1144 strcpy(buf, repl);
1159 } 1145 }
1160 } 1146 }
1161 1147
1162 1148
1163 bool CONTEXT::resolve_spf(const char *from, uint32_t ip, mlfiPriv *priv, const char *signers) 1149 bool CONTEXT::resolve_spf(const char *from, uint32_t ip, mlfiPriv *priv, const char *extraspf)
1164 { 1150 {
1165 const char *extraspf = extra_spf_data(signers);
1166 // ip is in host order 1151 // ip is in host order
1167 if (priv->mailaddr) { 1152 if (priv->mailaddr) {
1168 const char *f = strchr(priv->mailaddr, '@'); 1153 const char *f = strchr(priv->mailaddr, '@');
1169 if (f) { 1154 if (f) {
1170 f++; 1155 f++;
1187 bool CONTEXT::resolve_one_spf(const char *from, uint32_t ip, mlfiPriv *priv, const char *extraspf, int level) 1172 bool CONTEXT::resolve_one_spf(const char *from, uint32_t ip, mlfiPriv *priv, const char *extraspf, int level)
1188 { 1173 {
1189 char buf[maxdnslength]; 1174 char buf[maxdnslength];
1190 log(priv->queueid, "looking for %s txt record", from); 1175 log(priv->queueid, "looking for %s txt record", from);
1191 dns_interface(*priv, from, ns_t_txt, false, NULL, buf, maxdnslength); 1176 dns_interface(*priv, from, ns_t_txt, false, NULL, buf, maxdnslength);
1192 if ((level == 0) && 1177 if ((level == 0) && extraspf && ((strlen(buf) + strlen(extraspf) + 1) < sizeof(buf))) {
1193 extraspf && 1178 if (strlen(buf) >= 7) {
1194 ((strlen(buf) + strlen(extraspf) + 1) < sizeof(buf))) { 1179 // modify existing spf record
1195 strcat(buf, " "); 1180 replace(buf, buf+7, 0, extraspf);
1196 strcat(buf, extraspf); 1181 replace(buf, buf+7+strlen(extraspf), 0, " ");
1182 }
1183 else {
1184 // synthesize full spf record
1185 strcat(buf, "v=spf1 ");
1186 strcat(buf, extraspf);
1187 }
1197 } 1188 }
1198 if (*buf) { 1189 if (*buf) {
1199 log(priv->queueid, "found txt record %s", buf); 1190 log(priv->queueid, "found txt record %s", buf);
1200 // expand some macros here - a very restricted subset of all possible spf macros 1191 // expand some macros here - a very restricted subset of all possible spf macros
1201 // only expand the first instance of each. 1192 // only expand the first instance of each.
1202 char *p = strstr(buf, "%{i}"); 1193 char *p = strstr(buf, "%{i}");
1203 if (p) { 1194 if (p) {
1204 char adr[sizeof "255.255.255.255 "]; 1195 char adr[sizeof "255.255.255.255 "];
1205 adr[0] = '\0'; 1196 adr[0] = '\0';
1206 inet_ntop(AF_INET, (const u_char *)&priv->ip, adr, sizeof(adr)); 1197 inet_ntop(AF_INET, (const u_char *)&priv->ip, adr, sizeof(adr));
1207 replace(buf, p, adr); 1198 replace(buf, p, 4, adr);
1208 log(priv->queueid, "have txt record %s", buf); 1199 log(priv->queueid, "have txt record %s", buf);
1209 } 1200 }
1210 p = strstr(buf, "%{h}"); 1201 p = strstr(buf, "%{h}");
1211 if (p) { 1202 if (p) {
1212 replace(buf, p, priv->helo); 1203 replace(buf, p, 4, priv->helo);
1213 log(priv->queueid, "have txt record %s", buf); 1204 log(priv->queueid, "have txt record %s", buf);
1214 } 1205 }
1215 p = strstr(buf, "%{d}"); 1206 p = strstr(buf, "%{d}");
1216 if (p) { 1207 if (p) {
1217 replace(buf, p, from); 1208 replace(buf, p, 4, from);
1218 log(priv->queueid, "have txt record %s", buf); 1209 log(priv->queueid, "have txt record %s", buf);
1219 } 1210 }
1220 // 1211 //
1221 p = strchr(buf, ' '); // must start with 'v=spf1 ' 1212 p = strchr(buf, ' '); // must start with 'v=spf1 '
1222 if (!p) return false; // broken spf 1213 if (!p) return false; // broken spf
1373 } 1364 }
1374 } 1365 }
1375 if (st == token_unsigned_black) { 1366 if (st == token_unsigned_black) {
1376 // enforce dmarc 1367 // enforce dmarc
1377 if (!dmarc) { 1368 if (!dmarc) {
1378 dmarc = resolve_spf(from, ntohl(priv->ip), priv, dk->signer); 1369 dmarc = resolve_spf(from, ntohl(priv->ip), priv, dk->extraspf);
1379 } 1370 }
1380 if (!dmarc) { 1371 if (!dmarc) {
1381 // not signed and does not pass spf, reject it 1372 // not signed and does not pass spf, reject it
1382 char buf[maxlen]; 1373 char buf[maxlen];
1383 snprintf(buf, sizeof(buf), "Mail rejected - not dkim signed by %s", dk->signer); 1374 snprintf(buf, sizeof(buf), "Mail rejected - not dkim signed by %s", dk->signer);
1385 return token_black; 1376 return token_black;
1386 } 1377 }
1387 } 1378 }
1388 if (st == token_signed_white) { 1379 if (st == token_signed_white) {
1389 // not signed by a white listed signer, but maybe passes strong spf check 1380 // not signed by a white listed signer, but maybe passes strong spf check
1390 if (resolve_spf(from, ntohl(priv->ip), priv, dk->signer)) { 1381 if (resolve_spf(from, ntohl(priv->ip), priv, dk->extraspf)) {
1391 log(queueid, "spf pass for %s rather than whitelisted dkim signer", from); 1382 log(queueid, "spf pass for %s rather than whitelisted dkim signer", from);
1392 return token_white; 1383 return token_white;
1393 } 1384 }
1394 } 1385 }
1395 if (st == token_require_signed) { 1386 if (st == token_require_signed) {
1396 // not signed by a required signer, but maybe passes strong spf check 1387 // not signed by a required signer, but maybe passes strong spf check
1397 // only check spf if the list of required signers is not a single dot. 1388 // only check spf if the list of required signers is not a single dot.
1398 if (strcmp(dk->signer, ".") && resolve_spf(from, ntohl(priv->ip), priv, dk->signer)) { 1389 if (strcmp(dk->signer, ".") && resolve_spf(from, ntohl(priv->ip), priv, dk->extraspf)) {
1399 log(queueid, "spf pass for %s rather than required dkim signer", from); 1390 log(queueid, "spf pass for %s rather than required dkim signer", from);
1400 return token_white; 1391 return token_white;
1401 } 1392 }
1402 // todo - we could also check spf for the rfc5321 envelope from domain, 1393 // todo - we could also check spf for the rfc5321 envelope from domain,
1403 // if it is dmarc aligned (relaxed) with the rfc5322 header from domain. 1394 // if it is dmarc aligned (relaxed) with the rfc5322 header from domain.
1497 printf("%s }; \n", indent); 1488 printf("%s }; \n", indent);
1498 printf("%s dkim_from { \n", indent); 1489 printf("%s dkim_from { \n", indent);
1499 for (dkimp_map::iterator i=dkim_from_names.begin(); i!=dkim_from_names.end(); i++) { 1490 for (dkimp_map::iterator i=dkim_from_names.begin(); i!=dkim_from_names.end(); i++) {
1500 const char *n = (*i).first; 1491 const char *n = (*i).first;
1501 DKIM &d = *(*i).second; 1492 DKIM &d = *(*i).second;
1502 printf("%s %s %s \"%s\"; \n", indent, n, d.action, d.signer); 1493 if (d.extraspf) {
1494 printf("%s %s %s \"%s;%s\"; \n", indent, n, d.action, d.signer, d.extraspf);
1495 }
1496 else {
1497 printf("%s %s %s \"%s\"; \n", indent, n, d.action, d.signer);
1498 }
1503 } 1499 }
1504 printf("%s }; \n", indent); 1500 printf("%s }; \n", indent);
1505 if (content_suffix) { 1501 if (content_suffix) {
1506 printf("%s filter %s \"%s\"; \n", indent, content_suffix, content_message); 1502 printf("%s filter %s \"%s\"; \n", indent, content_suffix, content_message);
1507 } 1503 }
1821 const char *from = have; 1817 const char *from = have;
1822 const char *action = tok.next(); 1818 const char *action = tok.next();
1823 if ((action == token_signed_white) || (action == token_signed_black) || (action == token_unsigned_black) || (action == token_require_signed)) { 1819 if ((action == token_signed_white) || (action == token_signed_black) || (action == token_unsigned_black) || (action == token_require_signed)) {
1824 const char *signer = tok.next(); 1820 const char *signer = tok.next();
1825 if (!signer) break; 1821 if (!signer) break;
1826 else me.add_dkim_from(from, action, signer); 1822 else {
1823 const char *extraspf = NULL;
1824 if (strchr(signer, ';')) {
1825 char *x = strdup(signer);
1826 char *e = strchr(x, ';');
1827 *e = '\0';
1828 signer = register_string(x);
1829 extraspf = register_string(e+1);
1830 free(x);
1831 }
1832 me.add_dkim_from(from, action, signer, extraspf);
1833 }
1827 } 1834 }
1828 else { 1835 else {
1829 tok.token_error("signed_white/signed_black/unsigned_black/require_signed", action); 1836 tok.token_error("signed_white/signed_black/unsigned_black/require_signed", action);
1830 } 1837 }
1831 } 1838 }