0
|
1 /* Distributed Checksum Clearinghouse |
|
2 * |
|
3 * SMTP reply strings |
|
4 * |
|
5 * Copyright (c) 2008 by Rhyolite Software, LLC |
|
6 * |
|
7 * This agreement is not applicable to any entity which sells anti-spam |
|
8 * solutions to others or provides an anti-spam solution as part of a |
|
9 * security solution sold to other entities, or to a private network |
|
10 * which employs the DCC or uses data provided by operation of the DCC |
|
11 * but does not provide corresponding data to other users. |
|
12 * |
|
13 * Permission to use, copy, modify, and distribute this software without |
|
14 * changes for any purpose with or without fee is hereby granted, provided |
|
15 * that the above copyright notice and this permission notice appear in all |
|
16 * copies and any distributed versions or copies are either unchanged |
|
17 * or not called anything similar to "DCC" or "Distributed Checksum |
|
18 * Clearinghouse". |
|
19 * |
|
20 * Parties not eligible to receive a license under this agreement can |
|
21 * obtain a commercial license to use DCC by contacting Rhyolite Software |
|
22 * at sales@rhyolite.com. |
|
23 * |
|
24 * A commercial license would be for Distributed Checksum and Reputation |
|
25 * Clearinghouse software. That software includes additional features. This |
|
26 * free license for Distributed ChecksumClearinghouse Software does not in any |
|
27 * way grant permision to use Distributed Checksum and Reputation Clearinghouse |
|
28 * software |
|
29 * |
|
30 * THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL |
|
31 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES |
|
32 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC |
|
33 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES |
|
34 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, |
|
35 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, |
|
36 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS |
|
37 * SOFTWARE. |
|
38 * |
|
39 * Rhyolite Software DCC 1.3.103-1.30 $Revision$ |
|
40 */ |
|
41 |
|
42 |
|
43 #include "cmn_defs.h" |
|
44 |
|
45 REPLY_TPLT reject_reply; |
|
46 REPLY_TPLT reputation_reply; |
|
47 REPLY_TPLT grey_reply; |
|
48 REPLY_TPLT dnsbl_timeo_reply; |
|
49 |
|
50 |
|
51 /* default SMTP message templates */ |
|
52 typedef struct { |
|
53 REPLY_TPLT *reply; |
|
54 const char *rcode; /* default value such as "550" */ |
|
55 const char *xcode; /* default value such as "5.7.1" */ |
|
56 const char *def_pat; /* default -r pattern */ |
|
57 const char *log_result; /* "accept ..." etc. for log */ |
|
58 } REPLY_DEF; |
|
59 |
|
60 #define DEF_REJ_MSG "mail %ID from %CIP rejected by DCC" |
|
61 static REPLY_DEF rej_def = {&reject_reply, DCC_RCODE, DCC_XCODE, |
|
62 DEF_REJ_MSG, |
|
63 "reject"}; |
|
64 |
|
65 static REPLY_DEF grey_def = {&grey_reply, "452", "4.2.1", |
|
66 "mail %ID from %CIP temporary greylist embargoed", |
|
67 "temporary greylist embargo"}; |
|
68 |
|
69 static REPLY_DEF rep_def = {&reputation_reply, DCC_RCODE, DCC_XCODE, |
|
70 "%ID bad reputation; see http://commercial-dcc.rhyolite.com/cgi-bin/reps.cgi?tgt=%CIP", |
|
71 "reject by DCC reputation"}; |
|
72 |
|
73 static REPLY_DEF dnsbl_def = {0, DCC_RCODE, DCC_XCODE, |
|
74 DEF_REJ_MSG, |
|
75 "reject"}; |
|
76 |
|
77 static REPLY_DEF dnsbl_timeo_def = {&dnsbl_timeo_reply, "452", "4.2.1", |
|
78 "mail %ID from %CIP temporary delayed for DNSBL", |
|
79 "DNSBL check delay"}; |
|
80 |
|
81 |
|
82 REPLY_TPLT dcc_fail_reply = {"temporary reject", {REPLY_TPLT_NULL}, |
|
83 "451", "4.4.0", 0, DCC_XHDR_RESULT_DCC_FAIL}; |
|
84 |
|
85 |
|
86 /* Parse a string into RFC 821 and RFC 2034 codes and a pattern to make a |
|
87 * message, or generate the codes for a string that has a message |
|
88 * without codes. |
|
89 * The string should be something like "5.7.1 550 spammer go home" |
|
90 * or "spammer go home". */ |
|
91 void |
|
92 make_tplt(REPLY_TPLT *tplt, /* to here */ |
|
93 u_char mode, /* 0=sendmail 1=dccid 2=dnsbl */ |
|
94 const char *xcode, /* default value such as "5.7.1" */ |
|
95 const char *rcode, /* default value such as "550" */ |
|
96 const char *arg, /* using this pattern */ |
|
97 const char *log_result) /* "reject" etc. for log */ |
|
98 { |
|
99 const char *p; |
|
100 char c, *new_pat; |
|
101 char sb[5+1+3+1]; |
|
102 int p_cnt, i; |
|
103 |
|
104 arg += strspn(arg, DCC_WHITESPACE"\""); |
|
105 |
|
106 /* Does the message have a valid xcode and rcode? |
|
107 * First check for sendmail.cf ERROR:### and ERROR:D.S.N:### */ |
|
108 if (mode == 0 && !CLITCMP(arg, "ERROR:")) { |
|
109 p = dcc_parse_word(0, sb, sizeof(sb), arg+LITZ("ERROR:"), |
|
110 0, 0, 0); |
|
111 if (p) { |
|
112 i = strlen(sb); |
|
113 if (i == 5+1+3 && sb[5] == ':') { |
|
114 memcpy(tplt->xcode, &sb[0], 5); |
|
115 memcpy(tplt->rcode, &sb[6], 3); |
|
116 } else if (i == 3) { |
|
117 BUFCPY(tplt->xcode, xcode); |
|
118 memcpy(tplt->rcode, &sb[0], 3); |
|
119 } else { |
|
120 p = 0; |
|
121 } |
|
122 } |
|
123 } else { |
|
124 p = dcc_parse_word(0, tplt->xcode, sizeof(tplt->xcode), |
|
125 arg, 0, 0, 0); |
|
126 if (p) |
|
127 p = dcc_parse_word(0, tplt->rcode, sizeof(tplt->rcode), |
|
128 p, 0, 0, 0); |
|
129 } |
|
130 if (!p |
|
131 || tplt->rcode[0] < '4' || tplt->rcode[0] > '5' |
|
132 || tplt->rcode[1] < '0' || tplt->rcode[1] > '9' |
|
133 || tplt->rcode[2] < '0' || tplt->rcode[2] > '9' |
|
134 || tplt->rcode[0] != tplt->xcode[0] |
|
135 || tplt->xcode[1] != '.' |
|
136 || tplt->xcode[2] < '0' || tplt->xcode[2] > '9' |
|
137 || tplt->xcode[3] != '.' |
|
138 || tplt->xcode[4] < '0' || tplt->xcode[4] > '9') { |
|
139 BUFCPY(tplt->xcode, xcode); |
|
140 BUFCPY(tplt->rcode, rcode); |
|
141 p = arg; |
|
142 } else { |
|
143 p += strspn(p, DCC_WHITESPACE); |
|
144 } |
|
145 |
|
146 tplt->is_pat = 0; |
|
147 p_cnt = 0; |
|
148 new_pat = tplt->pat; |
|
149 do { |
|
150 c = *p++; |
|
151 if (c == '\0') |
|
152 break; |
|
153 if (c == '%') { |
|
154 /* parse %x for x= |
|
155 * ID message queue ID |
|
156 * CIP SMTP client IP address |
|
157 * EMBARGO null or #%d of greylist embargo number |
|
158 * BTYPE type of blacklist hit |
|
159 * BTGT IP address or name sought in DNSBL |
|
160 * BPROBE domain name found in blacklist |
|
161 * BRESULT value of domain name found in blacklist |
|
162 * |
|
163 * BT obsolete equivalent of BTYPE |
|
164 * BIP obsolete equivalent of BTGT |
|
165 * s obsolete; 1st same as ID; 2nd same as CIP |
|
166 */ |
|
167 if (new_pat >= LAST(tplt->pat)-2) |
|
168 continue; /* no room for "%s\0" */ |
|
169 |
|
170 tplt->is_pat = 1; |
|
171 *new_pat++ = '%'; |
|
172 if (mode == 0 |
|
173 || p_cnt >= NUM_REPLY_TPLT_ARGS || *p == '%') { |
|
174 /* translate %% and bad % to %% */ |
|
175 ++p; |
|
176 |
|
177 } else if (!LITCMP(p,"ID")) { |
|
178 p += LITZ("ID"); |
|
179 tplt->args[p_cnt++] = REPLY_TPLT_ID; |
|
180 c = 's'; |
|
181 } else if (!LITCMP(p,"CIP")) { |
|
182 p += LITZ("CIP"); |
|
183 tplt->args[p_cnt++] = REPLY_TPLT_CIP; |
|
184 c = 's'; |
|
185 } else if (mode == 2 && !LITCMP(p,"BTYPE")) { |
|
186 p += LITZ("BTYPE"); |
|
187 tplt->args[p_cnt++] = REPLY_TPLT_BTYPE; |
|
188 c = 's'; |
|
189 } else if (mode == 2 && !LITCMP(p,"BTGT")) { |
|
190 p += LITZ("BTGT"); |
|
191 tplt->args[p_cnt++] = REPLY_TPLT_BTGT; |
|
192 c = 's'; |
|
193 } else if (mode == 2 && !LITCMP(p,"BPROBE")) { |
|
194 p += LITZ("BPROBE"); |
|
195 tplt->args[p_cnt++] = REPLY_TPLT_BPROBE; |
|
196 c = 's'; |
|
197 } else if (mode == 2 && !LITCMP(p,"BRESULT")) { |
|
198 p += LITZ("BRESULT"); |
|
199 tplt->args[p_cnt++] = REPLY_TPLT_BRESULT; |
|
200 c = 's'; |
|
201 |
|
202 } else if (*p == 's' && p_cnt < 2) { |
|
203 /* obsolete |
|
204 * translate 1st "%s" to REPLY_TPLT_ID |
|
205 * 2nd to REPLY_TPLT_CIP */ |
|
206 ++p; |
|
207 tplt->args[p_cnt] = (p_cnt == 0 |
|
208 ? REPLY_TPLT_ID |
|
209 : REPLY_TPLT_CIP); |
|
210 ++p_cnt; |
|
211 c = 's'; |
|
212 } else if (mode == 2 && !LITCMP(p,"BIP")) { |
|
213 /* obsolete */ |
|
214 p += LITZ("BIP"); |
|
215 tplt->args[p_cnt++] = REPLY_TPLT_BTGT; |
|
216 c = 's'; |
|
217 } else if (mode == 2 && !LITCMP(p,"BT")) { |
|
218 /* obsolete */ |
|
219 p += LITZ("BT"); |
|
220 tplt->args[p_cnt++] = REPLY_TPLT_BTYPE; |
|
221 c = 's'; |
|
222 } |
|
223 |
|
224 } else if (c == '"' && mode == 0) { |
|
225 /* Strip double quotes from sendmail rejection |
|
226 * message. Sendmail needs quotes around the message |
|
227 * so it won't convert blanks to dots. */ |
|
228 continue; |
|
229 |
|
230 } else if (c < ' ' || c > '~') { |
|
231 /* sendmail does not like control characters in |
|
232 * SMTP status messages */ |
|
233 c = '.'; |
|
234 } |
|
235 |
|
236 *new_pat++ = c; |
|
237 } while (new_pat < LAST(tplt->pat)); |
|
238 *new_pat = '\0'; |
|
239 while (p_cnt < NUM_REPLY_TPLT_ARGS) |
|
240 tplt->args[p_cnt++] = REPLY_TPLT_NULL; |
|
241 |
|
242 tplt->log_result = log_result; |
|
243 } |
|
244 |
|
245 |
|
246 |
|
247 static inline void |
|
248 finish_reply(const REPLY_DEF *def, const char *pat) |
|
249 { |
|
250 make_tplt(def->reply, 1, def->xcode, def->rcode, pat, |
|
251 def->log_result); |
|
252 } |
|
253 |
|
254 |
|
255 |
|
256 void |
|
257 finish_replies(void) |
|
258 { |
|
259 /* make SMTP status strings for cases not set with -rPATTERN */ |
|
260 if (reject_reply.rcode[0] == '\0') |
|
261 finish_reply(&rej_def, rej_def.def_pat); |
|
262 if (reputation_reply.rcode[0] == '\0') |
|
263 finish_reply(&rep_def, rep_def.def_pat); |
|
264 if (grey_reply.rcode[0] == '\0') |
|
265 finish_reply(&grey_def, grey_def.def_pat); |
|
266 if (dnsbl_timeo_reply.rcode[0] == '\0') |
|
267 finish_reply(&dnsbl_timeo_def, dnsbl_timeo_def.def_pat); |
|
268 } |
|
269 |
|
270 |
|
271 |
|
272 /* parse another "-r pattern" argument */ |
|
273 void |
|
274 parse_reply_arg(const char *arg) |
|
275 { |
|
276 /* a blank string implies the default */ |
|
277 arg += strspn(arg, DCC_WHITESPACE); |
|
278 |
|
279 /* each -r finishes the next SMTP rejection message */ |
|
280 |
|
281 if (reject_reply.rcode[0] == '\0') { |
|
282 if (*arg == '\0') |
|
283 finish_reply(&rej_def, rej_def.def_pat); |
|
284 else |
|
285 finish_reply(&rej_def, arg); |
|
286 return; |
|
287 } |
|
288 |
|
289 if (grey_reply.rcode[0] == '\0') { |
|
290 if (*arg == '\0') { |
|
291 finish_reply(&grey_def, grey_def.def_pat); |
|
292 return; |
|
293 } |
|
294 finish_reply(&grey_def, arg); |
|
295 if (grey_reply.rcode[0] != '4' |
|
296 || grey_reply.xcode[0] != '4') { |
|
297 dcc_error_msg("invalid greylist message: %s", arg); |
|
298 finish_reply(&grey_def, grey_def.def_pat); |
|
299 } |
|
300 return; |
|
301 } |
|
302 |
|
303 if (reputation_reply.rcode[0] == '\0') { |
|
304 if (*arg == '\0') |
|
305 finish_reply(&rep_def, rep_def.def_pat); |
|
306 else |
|
307 finish_reply(&rep_def, arg); |
|
308 return; |
|
309 } |
|
310 |
|
311 dcc_error_msg("more than 3 -r settings"); |
|
312 } |
|
313 |
|
314 |
|
315 |
|
316 /* set up the DNSBL SMTP error message template for all threads based on |
|
317 * a -B arg. Allow % patterns and so forth */ |
|
318 const REPLY_TPLT * |
|
319 dnsbl_parse_reply(const char *pat) |
|
320 { |
|
321 REPLY_TPLT *tplt; |
|
322 |
|
323 tplt = malloc(sizeof(REPLY_TPLT)); |
|
324 memset(tplt, 0, sizeof(REPLY_TPLT)); |
|
325 make_tplt(tplt, 2, dnsbl_def.xcode, dnsbl_def.rcode, pat, |
|
326 dnsbl_def.log_result); |
|
327 return tplt; |
|
328 } |
|
329 |
|
330 |
|
331 |
|
332 void |
|
333 make_reply(REPLY_STRS *strs, const REPLY_TPLT *tplt, |
|
334 const CMN_WORK *cwp, const DNSBL_GROUP *blg) |
|
335 { |
|
336 const char *args[NUM_REPLY_TPLT_ARGS]; |
|
337 int i; |
|
338 |
|
339 strs->rcode = tplt->rcode; |
|
340 strs->xcode = tplt->xcode; |
|
341 if (!tplt->is_pat) { |
|
342 strs->str = tplt->pat; |
|
343 } else { |
|
344 for (i = 0; i < NUM_REPLY_TPLT_ARGS; ++i) { |
|
345 switch (tplt->args[i]) { |
|
346 case REPLY_TPLT_NULL: |
|
347 args[i] = ""; |
|
348 break; |
|
349 case REPLY_TPLT_ID: |
|
350 args[i] = cwp->id; |
|
351 break; |
|
352 case REPLY_TPLT_CIP: |
|
353 args[i] = dcc_trim_ffff(cwp->clnt_str); |
|
354 break; |
|
355 case REPLY_TPLT_BTYPE: |
|
356 args[i] = (blg && blg->btype) ? blg->btype : ""; |
|
357 break; |
|
358 case REPLY_TPLT_BTGT: |
|
359 args[i] = blg ? blg->tgt.c : ""; |
|
360 break; |
|
361 case REPLY_TPLT_BPROBE: |
|
362 args[i] = blg ? blg->probe.c : ""; |
|
363 break; |
|
364 case REPLY_TPLT_BRESULT: |
|
365 args[i] = blg ? blg->result : ""; |
|
366 break; |
|
367 } |
|
368 } |
|
369 snprintf(strs->str_buf, sizeof(strs->str_buf), tplt->pat, |
|
370 args[0], args[1], args[2], args[3], args[4], args[5]); |
|
371 strs->str = strs->str_buf; |
|
372 } |
|
373 |
|
374 strs->log_result = tplt->log_result; |
|
375 } |