You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lua_spf.c 14KB

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