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 }