1 /*-
2 * Copyright 2019 Vsevolod Stakhov
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 /**
17 * @file lua_spf.c
18 * This module exports spf functions to Lua
19 */
20
21 #include "lua_common.h"
22 #include "libserver/spf.h"
23 #include "libutil/ref.h"
24
25 #define SPF_RECORD_CLASS "rspamd{spf_record}"
26
27 LUA_FUNCTION_DEF (spf, resolve);
28 LUA_FUNCTION_DEF (spf, config);
29
30 LUA_FUNCTION_DEF (spf_record, check_ip);
31 LUA_FUNCTION_DEF (spf_record, dtor);
32 LUA_FUNCTION_DEF (spf_record, get_domain);
33 LUA_FUNCTION_DEF (spf_record, get_elts);
34 LUA_FUNCTION_DEF (spf_record, get_ttl);
35 LUA_FUNCTION_DEF (spf_record, get_timestamp);
36 LUA_FUNCTION_DEF (spf_record, get_digest);
37
38 static luaL_reg rspamd_spf_f[] = {
39 LUA_INTERFACE_DEF (spf, resolve),
40 LUA_INTERFACE_DEF (spf, config),
41 {NULL, NULL},
42 };
43
44 static luaL_reg rspamd_spf_record_m[] = {
45 LUA_INTERFACE_DEF (spf_record, check_ip),
46 LUA_INTERFACE_DEF (spf_record, get_domain),
47 LUA_INTERFACE_DEF (spf_record, get_ttl),
48 LUA_INTERFACE_DEF (spf_record, get_digest),
49 LUA_INTERFACE_DEF (spf_record, get_elts),
50 LUA_INTERFACE_DEF (spf_record, get_timestamp),
51 {"__gc", lua_spf_record_dtor},
52 {NULL, NULL},
53 };
54
55 struct rspamd_lua_spf_cbdata {
56 struct rspamd_task *task;
57 lua_State *L;
58 struct rspamd_symcache_item *item;
59 gint cbref;
60 ref_entry_t ref;
61 };
62
63 static gint
lua_load_spf(lua_State * L)64 lua_load_spf (lua_State * L)
65 {
66 lua_newtable (L);
67
68 /* Create integer arguments to check SPF results */
69 lua_newtable (L);
70 lua_pushinteger (L, SPF_FAIL);
71 lua_setfield (L, -2, "fail");
72 lua_pushinteger (L, SPF_PASS);
73 lua_setfield (L, -2, "pass");
74 lua_pushinteger (L, SPF_NEUTRAL);
75 lua_setfield (L, -2, "neutral");
76 lua_pushinteger (L, SPF_SOFT_FAIL);
77 lua_setfield (L, -2, "soft_fail");
78
79 lua_setfield (L, -2, "policy");
80
81 /* Flags stuff */
82 lua_newtable (L);
83
84 lua_pushinteger (L, RSPAMD_SPF_RESOLVED_TEMP_FAILED);
85 lua_setfield (L, -2, "temp_fail");
86 lua_pushinteger (L, RSPAMD_SPF_RESOLVED_NA);
87 lua_setfield (L, -2, "na");
88 lua_pushinteger (L, RSPAMD_SPF_RESOLVED_PERM_FAILED);
89 lua_setfield (L, -2, "perm_fail");
90 lua_pushinteger (L, RSPAMD_SPF_FLAG_CACHED);
91 lua_setfield (L, -2, "cached");
92
93 lua_setfield (L, -2, "flags");
94
95 luaL_register (L, NULL, rspamd_spf_f);
96
97 return 1;
98 }
99
luaopen_spf(lua_State * L)100 void luaopen_spf (lua_State *L)
101 {
102 rspamd_lua_new_class (L, SPF_RECORD_CLASS, rspamd_spf_record_m);
103 lua_pop (L, 1); /* No need in metatable... */
104
105 rspamd_lua_add_preload (L, "rspamd_spf", lua_load_spf);
106 lua_settop (L, 0);
107 }
108
109 static void
lua_spf_push_result(struct rspamd_lua_spf_cbdata * cbd,gint code_flags,struct spf_resolved * resolved,const gchar * err)110 lua_spf_push_result (struct rspamd_lua_spf_cbdata *cbd, gint code_flags,
111 struct spf_resolved *resolved, const gchar *err)
112 {
113 g_assert (cbd != NULL);
114 REF_RETAIN (cbd);
115
116 lua_pushcfunction (cbd->L, &rspamd_lua_traceback);
117 gint err_idx = lua_gettop (cbd->L);
118
119 lua_rawgeti (cbd->L, LUA_REGISTRYINDEX, cbd->cbref);
120
121 if (resolved) {
122 struct spf_resolved **presolved;
123
124 presolved = lua_newuserdata (cbd->L, sizeof (*presolved));
125 rspamd_lua_setclass (cbd->L, SPF_RECORD_CLASS, -1);
126 *presolved = spf_record_ref (resolved);
127 }
128 else {
129 lua_pushnil (cbd->L);
130 }
131
132 lua_pushinteger (cbd->L, code_flags);
133
134 if (err) {
135 lua_pushstring (cbd->L, err);
136 }
137 else {
138 lua_pushnil (cbd->L);
139 }
140
141 if (lua_pcall (cbd->L, 3, 0, err_idx) != 0) {
142 struct rspamd_task *task = cbd->task;
143
144 msg_err_task ("cannot call callback function for spf: %s",
145 lua_tostring (cbd->L, -1));
146 }
147
148 lua_settop (cbd->L, err_idx - 1);
149
150 REF_RELEASE (cbd);
151 }
152
153 static void
lua_spf_dtor(struct rspamd_lua_spf_cbdata * cbd)154 lua_spf_dtor (struct rspamd_lua_spf_cbdata *cbd)
155 {
156 if (cbd) {
157 luaL_unref (cbd->L, LUA_REGISTRYINDEX, cbd->cbref);
158 if (cbd->item) {
159 rspamd_symcache_item_async_dec_check (cbd->task, cbd->item,
160 "lua_spf");
161 }
162 }
163 }
164
165 static void
spf_lua_lib_callback(struct spf_resolved * record,struct rspamd_task * task,gpointer ud)166 spf_lua_lib_callback (struct spf_resolved *record, struct rspamd_task *task,
167 gpointer ud)
168 {
169 struct rspamd_lua_spf_cbdata *cbd = (struct rspamd_lua_spf_cbdata *)ud;
170
171 if (record) {
172 if ((record->flags & RSPAMD_SPF_RESOLVED_NA)) {
173 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_NA, NULL,
174 "no SPF record");
175 }
176 else if (record->elts->len == 0) {
177 if (record->flags & RSPAMD_SPF_RESOLVED_PERM_FAILED) {
178 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
179 "bad SPF record");
180 }
181 else if ((record->flags & RSPAMD_SPF_RESOLVED_TEMP_FAILED)) {
182 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_TEMP_FAILED, NULL,
183 "temporary DNS error");
184 }
185 else {
186 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
187 "empty SPF record");
188 }
189 }
190 else if (record->domain) {
191 spf_record_ref (record);
192 lua_spf_push_result (cbd, record->flags, record, NULL);
193 spf_record_unref (record);
194 }
195 else {
196 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
197 "internal error: non empty record for no domain");
198 }
199 }
200 else {
201 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_PERM_FAILED, NULL,
202 "internal error: no record");
203 }
204
205 REF_RELEASE (cbd);
206 }
207
208 /***
209 * @function rspamd_spf.resolve(task, callback)
210 * Resolves SPF credentials for a task
211 * @param {rspamd_task} task task
212 * @param {function} callback callback that is called on spf resolution
213 */
214 gint
lua_spf_resolve(lua_State * L)215 lua_spf_resolve (lua_State * L)
216 {
217 struct rspamd_task *task = lua_check_task (L, 1);
218
219 if (task && lua_isfunction (L, 2)) {
220 struct rspamd_lua_spf_cbdata *cbd = rspamd_mempool_alloc0 (task->task_pool,
221 sizeof (*cbd));
222 struct rspamd_spf_cred *spf_cred;
223
224 cbd->task = task;
225 cbd->L = L;
226 lua_pushvalue (L, 2);
227 cbd->cbref = luaL_ref (L, LUA_REGISTRYINDEX);
228 /* TODO: make it as an optional parameter */
229 spf_cred = rspamd_spf_get_cred (task);
230 cbd->item = rspamd_symcache_get_cur_item (task);
231
232 if (cbd->item) {
233 rspamd_symcache_item_async_inc (task, cbd->item, "lua_spf");
234 }
235 REF_INIT_RETAIN (cbd, lua_spf_dtor);
236
237 if (!rspamd_spf_resolve (task, spf_lua_lib_callback, cbd, spf_cred)) {
238 msg_info_task ("cannot make spf request for %s",
239 spf_cred ? spf_cred->domain : "empty domain");
240 if (spf_cred) {
241 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_TEMP_FAILED,
242 NULL, "DNS failed");
243 }
244 else {
245 lua_spf_push_result (cbd, RSPAMD_SPF_RESOLVED_NA,
246 NULL, "No domain");
247 }
248 REF_RELEASE (cbd);
249 }
250 }
251 else {
252 return luaL_error (L, "invalid arguments");
253 }
254
255 return 0;
256 }
257
258 static gint
lua_spf_record_dtor(lua_State * L)259 lua_spf_record_dtor (lua_State *L)
260 {
261 struct spf_resolved *record;
262
263 RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
264 struct spf_resolved,
265 record);
266
267 if (record) {
268 spf_record_unref (record);
269 }
270
271 return 0;
272 }
273
274 static void
lua_spf_push_spf_addr(lua_State * L,struct spf_addr * addr)275 lua_spf_push_spf_addr (lua_State *L, struct spf_addr *addr)
276 {
277 gchar *addr_mask;
278
279 lua_createtable (L, 0, 4);
280
281 lua_pushinteger (L, addr->mech);
282 lua_setfield (L, -2, "result");
283 lua_pushinteger (L, addr->flags);
284 lua_setfield (L, -2, "flags");
285
286 if (addr->spf_string) {
287 lua_pushstring (L, addr->spf_string);
288 lua_setfield (L, -2, "str");
289 }
290
291 addr_mask = spf_addr_mask_to_string (addr);
292
293 if (addr_mask) {
294 lua_pushstring (L, addr_mask);
295 lua_setfield (L, -2, "addr");
296 g_free (addr_mask);
297 }
298 }
299
300 static gint
spf_check_element(lua_State * L,struct spf_resolved * rec,struct spf_addr * addr,struct rspamd_lua_ip * ip)301 spf_check_element (lua_State *L, struct spf_resolved *rec, struct spf_addr *addr,
302 struct rspamd_lua_ip *ip)
303 {
304 gboolean res = FALSE;
305 const guint8 *s, *d;
306 guint af, mask, bmask, addrlen;
307
308
309 if (addr->flags & RSPAMD_SPF_FLAG_TEMPFAIL) {
310 /* Ignore failed addresses */
311
312 return -1;
313 }
314
315 af = rspamd_inet_address_get_af (ip->addr);
316 /* Basic comparing algorithm */
317 if (((addr->flags & RSPAMD_SPF_FLAG_IPV6) && af == AF_INET6) ||
318 ((addr->flags & RSPAMD_SPF_FLAG_IPV4) && af == AF_INET)) {
319 d = rspamd_inet_address_get_hash_key (ip->addr, &addrlen);
320
321 if (af == AF_INET6) {
322 s = (const guint8 *)addr->addr6;
323 mask = addr->m.dual.mask_v6;
324 }
325 else {
326 s = (const guint8 *)addr->addr4;
327 mask = addr->m.dual.mask_v4;
328 }
329
330 /* Compare the first bytes */
331 bmask = mask / CHAR_BIT;
332 if (mask > addrlen * CHAR_BIT) {
333 /* XXX: add logging */
334 }
335 else if (memcmp (s, d, bmask) == 0) {
336 if (bmask * CHAR_BIT < mask) {
337 /* Compare the remaining bits */
338 s += bmask;
339 d += bmask;
340 mask = (0xff << (CHAR_BIT - (mask - bmask * 8))) & 0xff;
341
342 if ((*s & mask) == (*d & mask)) {
343 res = TRUE;
344 }
345 }
346 else {
347 res = TRUE;
348 }
349 }
350 }
351 else {
352 if (addr->flags & RSPAMD_SPF_FLAG_ANY) {
353 res = TRUE;
354 }
355 else {
356 res = FALSE;
357 }
358 }
359
360 if (res) {
361 if (addr->flags & RSPAMD_SPF_FLAG_ANY) {
362 if (rec->flags & RSPAMD_SPF_RESOLVED_PERM_FAILED) {
363 lua_pushboolean (L, false);
364 lua_pushinteger (L, RSPAMD_SPF_RESOLVED_PERM_FAILED);
365 lua_pushfstring (L, "%cany", spf_mech_char (addr->mech));
366 }
367 else if (rec->flags & RSPAMD_SPF_RESOLVED_TEMP_FAILED) {
368 lua_pushboolean (L, false);
369 lua_pushinteger (L, RSPAMD_SPF_RESOLVED_TEMP_FAILED);
370 lua_pushfstring (L, "%cany", spf_mech_char (addr->mech));
371 }
372 else {
373 lua_pushboolean (L, true);
374 lua_pushinteger (L, addr->mech);
375 lua_spf_push_spf_addr (L, addr);
376 }
377 }
378 else {
379 lua_pushboolean (L, true);
380 lua_pushinteger (L, addr->mech);
381 lua_spf_push_spf_addr (L, addr);
382 }
383
384 return 3;
385 }
386
387 return -1;
388 }
389
390 /***
391 * @method rspamd_spf_record:check_ip(ip)
392 * Checks the processed record versus a specific IP address. This function
393 * returns 3 values normally:
394 * 1. Boolean check result
395 * 2. If result is `false` then the second value is the error flag (e.g. rspamd_spf.flags.temp_fail), otherwise it will be an SPF method
396 * 3. If result is `false` then this will be an error string, otherwise - an SPF string (e.g. `mx` or `ip4:x.y.z.1`)
397 * @param {rspamd_ip|string} ip address
398 * @return {result,flag_or_policy,error_or_addr} - triplet
399 */
400 static gint
lua_spf_record_check_ip(lua_State * L)401 lua_spf_record_check_ip (lua_State *L)
402 {
403 struct spf_resolved *record;
404 RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
405 struct spf_resolved,
406 record);
407 struct rspamd_lua_ip *ip = NULL;
408 gint nres = 0;
409 gboolean need_free_ip = FALSE;
410
411 if (lua_type (L, 2) == LUA_TUSERDATA) {
412 ip = lua_check_ip (L, 2);
413 }
414 else if (lua_type (L, 2) == LUA_TSTRING) {
415 const gchar *ip_str;
416 gsize iplen;
417
418 ip = g_malloc0 (sizeof (struct rspamd_lua_ip));
419 ip_str = lua_tolstring (L, 2, &iplen);
420
421 if (!rspamd_parse_inet_address (&ip->addr,
422 ip_str, iplen, RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
423 g_free (ip);
424 ip = NULL;
425 }
426 else {
427 need_free_ip = TRUE;
428 }
429 }
430
431 if (record && ip && ip->addr) {
432 for (guint i = 0; i < record->elts->len; i ++) {
433 struct spf_addr *addr = &g_array_index (record->elts, struct spf_addr, i);
434 if ((nres = spf_check_element (L, record, addr, ip)) > 0) {
435 if (need_free_ip) {
436 g_free (ip);
437 }
438
439 return nres;
440 }
441 }
442 }
443 else {
444 if (need_free_ip) {
445 g_free (ip);
446 }
447
448 return luaL_error (L, "invalid arguments");
449 }
450
451 if (need_free_ip) {
452 g_free (ip);
453 }
454
455 /* If we are here it means that there is no ALL record */
456 /*
457 * According to https://tools.ietf.org/html/rfc7208#section-4.7 it means
458 * SPF neutral
459 */
460 struct spf_addr fake_all;
461
462 fake_all.mech = SPF_NEUTRAL;
463 fake_all.flags = RSPAMD_SPF_FLAG_ANY;
464 fake_all.spf_string = "all";
465
466 lua_pushboolean (L, true);
467 lua_pushinteger (L, SPF_NEUTRAL);
468 lua_spf_push_spf_addr (L, &fake_all);
469
470 return 3;
471 }
472
473 /***
474 * @method rspamd_spf_record:get_domain()
475 * Returns domain for the specific spf record
476 */
477 static gint
lua_spf_record_get_domain(lua_State * L)478 lua_spf_record_get_domain (lua_State *L)
479 {
480 struct spf_resolved *record;
481 RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
482 struct spf_resolved,
483 record);
484
485 if (record) {
486 lua_pushstring (L, record->domain);
487 }
488 else {
489 return luaL_error (L, "invalid arguments");
490 }
491
492 return 1;
493 }
494
495 /***
496 * @method rspamd_spf_record:get_ttl()
497 * Returns ttl for the specific spf record
498 */
499 static gint
lua_spf_record_get_ttl(lua_State * L)500 lua_spf_record_get_ttl (lua_State *L)
501 {
502 struct spf_resolved *record;
503 RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
504 struct spf_resolved,
505 record);
506
507 if (record) {
508 lua_pushinteger (L, record->ttl);
509 }
510 else {
511 return luaL_error (L, "invalid arguments");
512 }
513
514 return 1;
515 }
516
517 /***
518 * @method rspamd_spf_record:get_timestamp()
519 * Returns ttl for the specific spf record
520 */
521 static gint
lua_spf_record_get_timestamp(lua_State * L)522 lua_spf_record_get_timestamp (lua_State *L)
523 {
524 struct spf_resolved *record;
525 RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
526 struct spf_resolved,
527 record);
528
529 if (record) {
530 lua_pushnumber (L, record->timestamp);
531 }
532 else {
533 return luaL_error (L, "invalid arguments");
534 }
535
536 return 1;
537 }
538
539 /***
540 * @method rspamd_spf_record:get_digest()
541 * Returns string hex representation of the record digest (fast hash function)
542 */
543 static gint
lua_spf_record_get_digest(lua_State * L)544 lua_spf_record_get_digest (lua_State *L)
545 {
546 struct spf_resolved *record;
547 RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
548 struct spf_resolved,
549 record);
550
551 if (record) {
552 gchar hexbuf[64];
553
554 rspamd_snprintf (hexbuf, sizeof (hexbuf), "%xuL", record->digest);
555 lua_pushstring (L, hexbuf);
556 }
557 else {
558 return luaL_error (L, "invalid arguments");
559 }
560
561 return 1;
562 }
563
564 /***
565 * @method rspamd_spf_record:get_elts()
566 * Returns a list of all elements in an SPF record. Each element is a table with the
567 * following fields:
568 *
569 * - result - mech flag from rspamd_spf.results
570 * - flags - all flags
571 * - addr - address and mask as a string
572 * - str - string representation (if available)
573 */
574 static gint
lua_spf_record_get_elts(lua_State * L)575 lua_spf_record_get_elts (lua_State *L)
576 {
577 struct spf_resolved *record;
578 RSPAMD_LUA_CHECK_UDATA_PTR_OR_RETURN(L, 1, SPF_RECORD_CLASS,
579 struct spf_resolved,
580 record);
581
582 if (record) {
583 guint i;
584 struct spf_addr *addr;
585
586 lua_createtable (L, record->elts->len, 0);
587
588 for (i = 0; i < record->elts->len; i ++) {
589 addr = (struct spf_addr *)&g_array_index (record->elts,
590 struct spf_addr, i);
591 lua_spf_push_spf_addr (L, addr);
592
593 lua_rawseti (L, -2, i + 1);
594 }
595 }
596 else {
597 return luaL_error (L, "invalid arguments");
598 }
599
600 return 1;
601 }
602
603 /***
604 * @function rspamd_spf.config(object)
605 * Configures SPF library according to the UCL config
606 * @param {table} object configuration object
607 */
608 gint
lua_spf_config(lua_State * L)609 lua_spf_config (lua_State * L)
610 {
611 ucl_object_t *config_obj = ucl_object_lua_import (L, 1);
612
613 if (config_obj) {
614 spf_library_config (config_obj);
615 ucl_object_unref (config_obj); /* As we copy data all the time */
616 }
617 else {
618 return luaL_error (L, "invalid arguments");
619 }
620
621 return 0;
622 }