1 #include <stdlib.h>
2 #include <string.h>
3 #include <unistd.h>
4
5 #include "mailfront.h"
6
7 #include <bglibs/cdb.h>
8 #include <bglibs/dict.h>
9 #include <bglibs/dict.h>
10 #include <bglibs/iobuf.h>
11 #include <bglibs/str.h>
12
13 static RESPONSE(erropen,421,"4.3.0 Could not open $MAILRULES file");
14 static RESPONSE(syntax,421,"4.3.0 Syntax error in $MAILRULES");
15 static RESPONSE(erropenref,421,"4.3.0 Error opening file referenced from $MAILRULES");
16
17 #define NUL 0
18
19 /* rule tables ************************************************************* */
20 struct pattern
21 {
22 str pattern;
23 dict* file;
24 struct cdb* cdb;
25 int negated;
26 };
27
28 struct rule
29 {
30 int code;
31 struct pattern sender;
32 struct pattern recipient;
33 str response;
34 str relayclient;
35 str environment;
36 unsigned long databytes;
37 struct rule* next;
38 };
39
40 static struct rule* sender_rules = 0;
41 static struct rule* recip_rules = 0;
42 static struct rule** current_rules = 0;
append_rule(struct rule * r)43 static void append_rule(struct rule* r)
44 {
45 static struct rule* sender_tail = 0;
46 static struct rule* recip_tail = 0;
47 struct rule** head;
48 struct rule** tail;
49 if (current_rules != 0)
50 tail = ((head = current_rules) == &sender_rules) ?
51 &sender_tail : &recip_tail;
52 else if (r->recipient.pattern.len == 1 && r->recipient.pattern.s[0] == '*') {
53 head = &sender_rules;
54 tail = &sender_tail;
55 }
56 else {
57 head = &recip_rules;
58 tail = &recip_tail;
59 }
60 if (*tail == 0)
61 *head = r;
62 else
63 (*tail)->next = r;
64 *tail = r;
65 }
66
alloc_rule(void)67 static struct rule* alloc_rule(void)
68 {
69 struct rule* r;
70 if ((r = malloc(sizeof *r)) != 0)
71 memset(r, 0, sizeof *r);
72 return r;
73 }
74
75 #if 0
76 static void free_rule(struct rule* r)
77 {
78 str_free(&r->sender.pattern);
79 str_free(&r->recipient.pattern);
80 str_free(&r->response);
81 str_free(&r->relayclient);
82 str_free(&r->environment);
83 free(r);
84 }
85 #endif
86
87 /* embeded filename handling *********************************************** */
88 static dict cdb_files;
open_cdb(const str * filename)89 static struct cdb* open_cdb(const str* filename)
90 {
91 int fd;
92 struct cdb* c;
93 if ((c = malloc(sizeof *c)) == 0) return 0;
94 fd = open(filename->s, O_RDONLY);
95 cdb_init(c, fd);
96 if (!dict_add(&cdb_files, filename, c)) return 0;
97 return c;
98 }
99
lower(str * s)100 static int lower(str* s)
101 {
102 str_lower(s);
103 return 1;
104 }
105
106 static dict text_files;
load_text(const str * filename)107 static dict* load_text(const str* filename)
108 {
109 dict* d;
110 if ((d = malloc(sizeof *d)) == 0) return 0;
111 memset(d, 0, sizeof *d);
112 if (!dict_load_list(d, filename->s, 1, lower)) return 0;
113 if (!dict_add(&text_files, filename, d)) return 0;
114 return d;
115 }
116
is_special(const str * pattern)117 static int is_special(const str* pattern)
118 {
119 long len = pattern->len;
120 return len > 5 &&
121 pattern->s[0] == '[' &&
122 pattern->s[1] == '[' &&
123 pattern->s[len-2] == ']' &&
124 pattern->s[len-1] == ']';
125 }
126
is_cdb(const str * filename)127 static int is_cdb(const str* filename)
128 {
129 const char* end = filename->s + filename->len;
130 return filename->len > 4 &&
131 end[-4] == '.' &&
132 end[-3] == 'c' &&
133 end[-2] == 'd' &&
134 end[-1] == 'b';
135 }
136
137 static str filename;
extract_filename(const str * pattern)138 static int extract_filename(const str* pattern)
139 {
140 if (!is_special(pattern)) return 0;
141 if (pattern->s[2] == '@')
142 str_copyb(&filename, pattern->s+3, pattern->len-5);
143 else
144 str_copyb(&filename, pattern->s+2, pattern->len-4);
145 return 1;
146 }
147
try_load(struct pattern * pattern)148 static int try_load(struct pattern* pattern)
149 {
150 if (extract_filename(&pattern->pattern)) {
151 if (is_cdb(&filename))
152 return (pattern->cdb = open_cdb(&filename)) != 0;
153 else
154 return (pattern->file = load_text(&filename)) != 0;
155 }
156 return 1;
157 }
158
apply_environment(const str * s)159 static void apply_environment(const str* s)
160 {
161 unsigned i;
162 unsigned len;
163 for (i = 0; i < s->len; i += len + 1) {
164 len = strlen(s->s + i);
165 session_putenv(s->s + i);
166 }
167 }
168
169 /* file parsing ************************************************************ */
isoctal(int ch)170 static int isoctal(int ch) { return ch >= '0' && ch <= '8'; }
171
parse_uint(const char * ptr,char sep,unsigned long * out)172 static const char* parse_uint(const char* ptr, char sep,
173 unsigned long* out)
174 {
175 for (*out = 0; *ptr != 0 && *ptr != sep; ++ptr) {
176 if (*ptr >= '0' && *ptr <= '9')
177 *out = (*out * 10) + (*ptr - '0');
178 else
179 return 0;
180 }
181 return ptr;
182 }
183
parse_char(const char * ptr,char * out)184 static const char* parse_char(const char* ptr, char* out)
185 {
186 int o;
187 switch (*ptr) {
188 case 0: return ptr;
189 case '\\':
190 switch (*++ptr) {
191 case 'n': *out = '\n'; break;
192 case '0': case '1': case '2': case '3':
193 case '4': case '5': case '6': case '7':
194 o = *ptr - '0';
195 if (isoctal(ptr[1])) {
196 o = (o << 3) | (*++ptr - '0');
197 if (isoctal(ptr[1]))
198 o = (o << 3) | (*++ptr - '0');
199 }
200 *out = 0;
201 break;
202 default: *out = *ptr;
203 }
204 break;
205 default: *out = *ptr;
206 }
207 return ptr + 1;
208 }
209
parse_str(const char * ptr,char sep,str * out)210 static const char* parse_str(const char* ptr, char sep, str* out)
211 {
212 char ch = 0;
213 /* str_truncate(out, 0); */
214 for (;;) {
215 if (*ptr == sep || *ptr == NUL) return ptr;
216 ptr = parse_char(ptr, &ch);
217 str_catc(out, ch);
218 }
219 return ptr;
220 }
221
parse_pattern(const char * ptr,char sep,struct pattern * out)222 static const char* parse_pattern(const char* ptr, char sep,
223 struct pattern* out)
224 {
225 while (*ptr != sep && *ptr == '!') {
226 out->negated = !out->negated;
227 ++ptr;
228 }
229 return parse_str(ptr, sep, &out->pattern);
230 }
231
parse_env(const char * ptr,str * out)232 static void parse_env(const char* ptr, str* out)
233 {
234 while (ptr && *ptr != 0)
235 if ((ptr = parse_str(ptr, ',', out)) != 0) {
236 str_catc(out, 0);
237 if (*ptr == ',') ++ptr;
238 }
239 }
240
iscode(char ch)241 static int iscode(char ch)
242 {
243 return ch == 'k' || ch == 'K' || ch == 'd' || ch == 'z' || ch == 'p' || ch == 'n' || ch == '&';
244 }
245
add(const char * l)246 static const response* add(const char* l)
247 {
248 struct rule* r;
249
250 if (!iscode(*l)) return 0;
251 r = alloc_rule();
252 r->code = *l++;
253
254 if ((l = parse_pattern(l, ':', &r->sender)) != 0 && *l == ':')
255 if ((l = parse_pattern(l+1, ':', &r->recipient)) != 0 && *l == ':')
256 if ((l = parse_str(l+1, ':', &r->response)) != 0 && *l == ':')
257 if ((l = parse_uint(l+1, ':', &r->databytes)) != 0)
258 if (*l == ':'
259 && (l = parse_str(l+1, ':', &r->relayclient)) != 0
260 && *l == ':')
261 parse_env(l+1, &r->environment);
262
263 if (l == 0) return &resp_syntax;
264 append_rule(r);
265
266 /* Pre-load text files and pre-open CDB files */
267 if (!try_load(&r->sender)) return &resp_erropenref;
268 if (!try_load(&r->recipient)) return &resp_erropenref;
269 return 0;
270 }
271
272 static int loaded = 0;
273
init(void)274 static const response* init(void)
275 {
276 const char* path;
277 str rule = {0,0,0};
278 ibuf in;
279 const response* r;
280
281 if ((path = getenv("MAILRULES")) == 0) return 0;
282 loaded = 1;
283
284 if (!ibuf_open(&in, path, 0)) return &resp_erropen;
285 while (ibuf_getstr(&in, &rule, LF)) {
286 str_strip(&rule);
287 if (rule.len == 0) continue;
288 if (rule.s[0] == ':') {
289 switch (rule.s[1]) {
290 case 's': current_rules = &sender_rules; break;
291 case 'r': current_rules = &recip_rules; break;
292 default: return &resp_syntax;
293 }
294 }
295 else if ((r = add(rule.s)) != 0)
296 return r;
297 }
298 ibuf_close(&in);
299 str_free(&rule);
300 return 0;
301 }
302
reset(void)303 static const response* reset(void)
304 {
305 if (loaded) {
306 session_resetenv();
307 session_delnum("maxdatabytes");
308 }
309 return 0;
310 }
311
312 /* rule application ******************************************************** */
matches(const struct pattern * pattern,const str * addr,const str * atdomain)313 static int matches(const struct pattern* pattern,
314 const str* addr, const str* atdomain)
315 {
316 static str domain;
317 int result;
318 if (pattern->cdb != 0) {
319 if (pattern->pattern.s[2] == '@') {
320 result = (atdomain->len == 0)
321 ? 0
322 : cdb_find(pattern->cdb, atdomain->s+1, atdomain->len-1) != 0;
323 }
324 else {
325 result = (cdb_find(pattern->cdb, addr->s, addr->len) != 0) ?
326 1 :
327 cdb_find(pattern->cdb, atdomain->s, atdomain->len) != 0;
328 }
329 }
330 else if (pattern->file != 0) {
331 if (pattern->pattern.s[2] == '@') {
332 if (atdomain->len > 0) {
333 str_copyb(&domain, atdomain->s+1, atdomain->len-1);
334 result = dict_get(pattern->file, &domain) != 0;
335 }
336 else
337 result = 0;
338 }
339 else {
340 result = (dict_get(pattern->file, addr) != 0) ?
341 1 :
342 dict_get(pattern->file, atdomain) != 0;
343 }
344 }
345 else
346 result = str_case_glob(addr, &pattern->pattern);
347 if (pattern->negated)
348 result = !result;
349 return result;
350 }
351
build_response(int type,const str * message)352 static const response* build_response(int type, const str* message)
353 {
354 static response resp;
355 unsigned code;
356 const char* defmsg = NULL;
357
358 switch (type) {
359 case 'p': return 0;
360 case 'n': return 0;
361 case 'k': code = 250; break;
362 case 'K': code = 250 | RESPONSE_FINAL; break;
363 case 'd': code = 553; defmsg = "Rejected"; break;
364 case 'z': code = 451; defmsg = "Deferred"; break;
365 default: code = 451; defmsg = "Temporary failure"; break;
366 }
367
368 resp.number = code;
369 resp.message = (message->len == 0) ? defmsg : message->s;
370 return &resp;
371 }
372
apply_rule(const struct rule * rule)373 static const response* apply_rule(const struct rule* rule)
374 {
375 const response* resp;
376 unsigned long maxdatabytes;
377 resp = build_response(rule->code, &rule->response);
378 apply_environment(&rule->environment);
379 maxdatabytes = session_getnum("maxdatabytes", ~0UL);
380 if (maxdatabytes == 0
381 || (rule->databytes > 0
382 && maxdatabytes > rule->databytes))
383 session_setnum("maxdatabytes", rule->databytes);
384 return resp;
385 }
386
copy_addr(const str * addr,str * saved,str * domain)387 static void copy_addr(const str* addr,
388 str* saved, str* domain)
389 {
390 int at;
391 str_copy(saved, addr);
392 str_lower(saved);
393 if ((at = str_findlast(saved, '@')) != -1)
394 str_copyb(domain, saved->s + at, saved->len - at);
395 else
396 str_truncate(domain, 0);
397 }
398
399 static str saved_sender;
400 static str sender_domain;
401
402 static str saved_recip;
403 static str recip_domain;
404
rule_matches(const struct rule * rule,int is_recip)405 static int rule_matches(const struct rule* rule, int is_recip)
406 {
407 return matches(&rule->sender, &saved_sender, &sender_domain)
408 && (!is_recip || matches(&rule->recipient, &saved_recip, &recip_domain));
409 }
410
run_rules(const struct rule * rule,int is_recip,str * addr,str * save_addr,str * save_domain)411 static const response* run_rules(const struct rule* rule, int is_recip, str* addr, str* save_addr, str* save_domain)
412 {
413 int prev_and;
414 if (!loaded)
415 return NULL;
416 copy_addr(addr, save_addr, save_domain);
417
418 for (prev_and = 1; rule != NULL; rule = rule->next) {
419 if (rule->code == '&') {
420 prev_and = prev_and && rule_matches(rule, is_recip);
421 }
422 else if (prev_and && rule_matches(rule, is_recip)) {
423 const response* r = apply_rule(rule);
424 if (is_recip)
425 str_cat(addr, &rule->relayclient);
426 if (rule->code != 'n')
427 return r;
428 }
429 else
430 prev_and = 1;
431 }
432 return NULL;
433 }
434
validate_sender(str * sender,str * params)435 static const response* validate_sender(str* sender, str* params)
436 {
437 return run_rules(sender_rules, 0, sender, &saved_sender, &sender_domain);
438 (void)params;
439 }
440
validate_recipient(str * recipient,str * params)441 static const response* validate_recipient(str* recipient, str* params)
442 {
443 return run_rules(recip_rules, 1, recipient, &saved_recip, &recip_domain);
444 (void)params;
445 }
446
447 struct plugin plugin = {
448 .version = PLUGIN_VERSION,
449 .init = init,
450 .reset = reset,
451 .sender = validate_sender,
452 .recipient = validate_recipient,
453 };
454