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.

rspamdclient.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /*-
  2. * Copyright 2016 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. #include "rspamdclient.h"
  17. #include "libutil/util.h"
  18. #include "libserver/http/http_connection.h"
  19. #include "libserver/http/http_private.h"
  20. #include "libserver/protocol_internal.h"
  21. #include "unix-std.h"
  22. #include "contrib/zstd/zstd.h"
  23. #include "contrib/zstd/zdict.h"
  24. #ifdef HAVE_FETCH_H
  25. #include <fetch.h>
  26. #elif defined(CURL_FOUND)
  27. #include <curl/curl.h>
  28. #endif
  29. struct rspamd_client_request;
  30. /*
  31. * Since rspamd uses untagged HTTP we can pass a single message per socket
  32. */
  33. struct rspamd_client_connection {
  34. gint fd;
  35. GString *server_name;
  36. struct rspamd_cryptobox_pubkey *key;
  37. struct rspamd_cryptobox_keypair *keypair;
  38. struct ev_loop *event_loop;
  39. ev_tstamp timeout;
  40. struct rspamd_http_connection *http_conn;
  41. gboolean req_sent;
  42. gdouble start_time;
  43. gdouble send_time;
  44. struct rspamd_client_request *req;
  45. struct rspamd_keypair_cache *keys_cache;
  46. };
  47. struct rspamd_client_request {
  48. struct rspamd_client_connection *conn;
  49. struct rspamd_http_message *msg;
  50. GString *input;
  51. rspamd_client_callback cb;
  52. gpointer ud;
  53. };
  54. #define RCLIENT_ERROR rspamd_client_error_quark ()
  55. GQuark
  56. rspamd_client_error_quark (void)
  57. {
  58. return g_quark_from_static_string ("rspamd-client-error");
  59. }
  60. static void
  61. rspamd_client_request_free (struct rspamd_client_request *req)
  62. {
  63. if (req != NULL) {
  64. if (req->conn) {
  65. req->conn->req = NULL;
  66. }
  67. if (req->input) {
  68. g_string_free (req->input, TRUE);
  69. }
  70. g_free (req);
  71. }
  72. }
  73. static gint
  74. rspamd_client_body_handler (struct rspamd_http_connection *conn,
  75. struct rspamd_http_message *msg,
  76. const gchar *chunk, gsize len)
  77. {
  78. /* Do nothing here */
  79. return 0;
  80. }
  81. static void
  82. rspamd_client_error_handler (struct rspamd_http_connection *conn, GError *err)
  83. {
  84. struct rspamd_client_request *req =
  85. (struct rspamd_client_request *)conn->ud;
  86. struct rspamd_client_connection *c;
  87. c = req->conn;
  88. req->cb (c, NULL, c->server_name->str, NULL,
  89. req->input, req->ud,
  90. c->start_time, c->send_time, NULL, 0, err);
  91. }
  92. static gint
  93. rspamd_client_finish_handler (struct rspamd_http_connection *conn,
  94. struct rspamd_http_message *msg)
  95. {
  96. struct rspamd_client_request *req =
  97. (struct rspamd_client_request *)conn->ud;
  98. struct rspamd_client_connection *c;
  99. struct ucl_parser *parser;
  100. GError *err;
  101. const rspamd_ftok_t *tok;
  102. const gchar *start, *body = NULL;
  103. guchar *out = NULL;
  104. gsize len, bodylen = 0;
  105. c = req->conn;
  106. if (!c->req_sent) {
  107. c->req_sent = TRUE;
  108. c->send_time = rspamd_get_ticks (FALSE);
  109. rspamd_http_connection_reset (c->http_conn);
  110. rspamd_http_connection_read_message (c->http_conn,
  111. c->req,
  112. c->timeout);
  113. return 0;
  114. }
  115. else {
  116. if (rspamd_http_message_get_body (msg, NULL) == NULL || msg->code / 100 != 2) {
  117. err = g_error_new (RCLIENT_ERROR, msg->code, "HTTP error: %d, %.*s",
  118. msg->code,
  119. (gint)msg->status->len, msg->status->str);
  120. req->cb (c, msg, c->server_name->str, NULL, req->input, req->ud,
  121. c->start_time, c->send_time, body, bodylen, err);
  122. g_error_free (err);
  123. return 0;
  124. }
  125. tok = rspamd_http_message_find_header (msg, COMPRESSION_HEADER);
  126. if (tok) {
  127. /* Need to uncompress */
  128. rspamd_ftok_t t;
  129. t.begin = "zstd";
  130. t.len = 4;
  131. if (rspamd_ftok_casecmp (tok, &t) == 0) {
  132. ZSTD_DStream *zstream;
  133. ZSTD_inBuffer zin;
  134. ZSTD_outBuffer zout;
  135. gsize outlen, r;
  136. zstream = ZSTD_createDStream ();
  137. ZSTD_initDStream (zstream);
  138. zin.pos = 0;
  139. zin.src = msg->body_buf.begin;
  140. zin.size = msg->body_buf.len;
  141. if ((outlen = ZSTD_getDecompressedSize (zin.src, zin.size)) == 0) {
  142. outlen = ZSTD_DStreamOutSize ();
  143. }
  144. out = g_malloc (outlen);
  145. zout.dst = out;
  146. zout.pos = 0;
  147. zout.size = outlen;
  148. while (zin.pos < zin.size) {
  149. r = ZSTD_decompressStream (zstream, &zout, &zin);
  150. if (ZSTD_isError (r)) {
  151. err = g_error_new (RCLIENT_ERROR, 500,
  152. "Decompression error: %s",
  153. ZSTD_getErrorName (r));
  154. req->cb (c, msg, c->server_name->str, NULL,
  155. req->input, req->ud, c->start_time,
  156. c->send_time, body, bodylen, err);
  157. g_error_free (err);
  158. ZSTD_freeDStream (zstream);
  159. goto end;
  160. }
  161. if (zout.pos == zout.size) {
  162. /* We need to extend output buffer */
  163. zout.size = zout.size * 2;
  164. zout.dst = g_realloc (zout.dst, zout.size);
  165. }
  166. }
  167. ZSTD_freeDStream (zstream);
  168. start = zout.dst;
  169. len = zout.pos;
  170. }
  171. else {
  172. err = g_error_new (RCLIENT_ERROR, 500,
  173. "Invalid compression method");
  174. req->cb (c, msg, c->server_name->str, NULL,
  175. req->input, req->ud, c->start_time, c->send_time,
  176. body, bodylen, err);
  177. g_error_free (err);
  178. return 0;
  179. }
  180. }
  181. else {
  182. start = msg->body_buf.begin;
  183. len = msg->body_buf.len;
  184. }
  185. /* Deal with body */
  186. tok = rspamd_http_message_find_header (msg, MESSAGE_OFFSET_HEADER);
  187. if (tok) {
  188. gulong value = 0;
  189. if (rspamd_strtoul (tok->begin, tok->len, &value) &&
  190. value < len) {
  191. body = start + value;
  192. bodylen = len - value;
  193. len = value;
  194. }
  195. }
  196. parser = ucl_parser_new (0);
  197. if (!ucl_parser_add_chunk (parser, start, len)) {
  198. err = g_error_new (RCLIENT_ERROR, msg->code, "Cannot parse UCL: %s",
  199. ucl_parser_get_error (parser));
  200. ucl_parser_free (parser);
  201. req->cb (c, msg, c->server_name->str, NULL,
  202. req->input, req->ud,
  203. c->start_time, c->send_time, body, bodylen, err);
  204. g_error_free (err);
  205. goto end;
  206. }
  207. req->cb (c, msg, c->server_name->str,
  208. ucl_parser_get_object (parser),
  209. req->input, req->ud,
  210. c->start_time, c->send_time, body, bodylen, NULL);
  211. ucl_parser_free (parser);
  212. }
  213. end:
  214. if (out) {
  215. g_free (out);
  216. }
  217. return 0;
  218. }
  219. struct rspamd_client_connection *
  220. rspamd_client_init (struct rspamd_http_context *http_ctx,
  221. struct ev_loop *ev_base, const gchar *name,
  222. guint16 port, gdouble timeout, const gchar *key)
  223. {
  224. struct rspamd_client_connection *conn;
  225. gint fd;
  226. fd = rspamd_socket (name, port, SOCK_STREAM, TRUE, FALSE, TRUE);
  227. if (fd == -1) {
  228. return NULL;
  229. }
  230. conn = g_malloc0 (sizeof (struct rspamd_client_connection));
  231. conn->event_loop = ev_base;
  232. conn->fd = fd;
  233. conn->req_sent = FALSE;
  234. conn->http_conn = rspamd_http_connection_new_client_socket (http_ctx,
  235. rspamd_client_body_handler,
  236. rspamd_client_error_handler,
  237. rspamd_client_finish_handler,
  238. 0,
  239. fd);
  240. conn->server_name = g_string_new (name);
  241. if (port != 0) {
  242. rspamd_printf_gstring (conn->server_name, ":%d", (int)port);
  243. }
  244. conn->timeout = timeout;
  245. if (key) {
  246. conn->key = rspamd_pubkey_from_base32 (key, 0, RSPAMD_KEYPAIR_KEX,
  247. RSPAMD_CRYPTOBOX_MODE_25519);
  248. if (conn->key) {
  249. conn->keypair = rspamd_keypair_new (RSPAMD_KEYPAIR_KEX,
  250. RSPAMD_CRYPTOBOX_MODE_25519);
  251. rspamd_http_connection_set_key (conn->http_conn, conn->keypair);
  252. }
  253. else {
  254. rspamd_client_destroy (conn);
  255. return NULL;
  256. }
  257. }
  258. return conn;
  259. }
  260. gboolean
  261. rspamd_client_command (struct rspamd_client_connection *conn,
  262. const gchar *command, GQueue *attrs,
  263. FILE *in, rspamd_client_callback cb,
  264. gpointer ud, gboolean compressed,
  265. const gchar *comp_dictionary,
  266. const gchar *filename,
  267. GError **err)
  268. {
  269. struct rspamd_client_request *req;
  270. struct rspamd_http_client_header *nh;
  271. gchar *p;
  272. gsize remain, old_len;
  273. GList *cur;
  274. GString *input = NULL;
  275. rspamd_fstring_t *body;
  276. guint dict_id = 0;
  277. gsize dict_len = 0;
  278. void *dict = NULL;
  279. ZSTD_CCtx *zctx;
  280. gboolean ret;
  281. req = g_malloc0 (sizeof (struct rspamd_client_request));
  282. req->conn = conn;
  283. req->cb = cb;
  284. req->ud = ud;
  285. req->msg = rspamd_http_new_message (HTTP_REQUEST);
  286. if (conn->key) {
  287. req->msg->peer_key = rspamd_pubkey_ref (conn->key);
  288. }
  289. if (in != NULL) {
  290. /* Read input stream */
  291. input = g_string_sized_new (BUFSIZ);
  292. while (!feof (in)) {
  293. p = input->str + input->len;
  294. remain = input->allocated_len - input->len - 1;
  295. if (remain == 0) {
  296. old_len = input->len;
  297. g_string_set_size (input, old_len * 2);
  298. input->len = old_len;
  299. continue;
  300. }
  301. remain = fread (p, 1, remain, in);
  302. if (remain > 0) {
  303. input->len += remain;
  304. input->str[input->len] = '\0';
  305. }
  306. }
  307. if (ferror (in) != 0) {
  308. g_set_error (err, RCLIENT_ERROR, ferror (
  309. in), "input IO error: %s", strerror (ferror (in)));
  310. g_free (req);
  311. g_string_free (input, TRUE);
  312. return FALSE;
  313. }
  314. if (!compressed) {
  315. body = rspamd_fstring_new_init (input->str, input->len);
  316. }
  317. else {
  318. if (comp_dictionary) {
  319. dict = rspamd_file_xmap (comp_dictionary, PROT_READ, &dict_len,
  320. TRUE);
  321. if (dict == NULL) {
  322. g_set_error (err, RCLIENT_ERROR, errno,
  323. "cannot open dictionary %s: %s",
  324. comp_dictionary,
  325. strerror (errno));
  326. g_free (req);
  327. g_string_free (input, TRUE);
  328. return FALSE;
  329. }
  330. dict_id = ZDICT_getDictID (comp_dictionary, dict_len);
  331. if (dict_id == 0) {
  332. g_set_error (err, RCLIENT_ERROR, errno,
  333. "cannot open dictionary %s: %s",
  334. comp_dictionary,
  335. strerror (errno));
  336. g_free (req);
  337. g_string_free (input, TRUE);
  338. munmap (dict, dict_len);
  339. return FALSE;
  340. }
  341. }
  342. body = rspamd_fstring_sized_new (ZSTD_compressBound (input->len));
  343. zctx = ZSTD_createCCtx ();
  344. body->len = ZSTD_compress_usingDict (zctx, body->str, body->allocated,
  345. input->str, input->len,
  346. dict, dict_len,
  347. 1);
  348. munmap (dict, dict_len);
  349. if (ZSTD_isError (body->len)) {
  350. g_set_error (err, RCLIENT_ERROR, ferror (
  351. in), "compression error");
  352. g_free (req);
  353. g_string_free (input, TRUE);
  354. rspamd_fstring_free (body);
  355. ZSTD_freeCCtx (zctx);
  356. return FALSE;
  357. }
  358. ZSTD_freeCCtx (zctx);
  359. }
  360. rspamd_http_message_set_body_from_fstring_steal (req->msg, body);
  361. req->input = input;
  362. }
  363. else {
  364. req->input = NULL;
  365. }
  366. /* Convert headers */
  367. cur = attrs->head;
  368. while (cur != NULL) {
  369. nh = cur->data;
  370. rspamd_http_message_add_header (req->msg, nh->name, nh->value);
  371. cur = g_list_next (cur);
  372. }
  373. if (compressed) {
  374. rspamd_http_message_add_header (req->msg, COMPRESSION_HEADER, "zstd");
  375. if (dict_id != 0) {
  376. gchar dict_str[32];
  377. rspamd_snprintf (dict_str, sizeof (dict_str), "%ud", dict_id);
  378. rspamd_http_message_add_header (req->msg, "Dictionary", dict_str);
  379. }
  380. }
  381. if (filename) {
  382. rspamd_http_message_add_header (req->msg, "Filename", filename);
  383. }
  384. req->msg->url = rspamd_fstring_append (req->msg->url, "/", 1);
  385. req->msg->url = rspamd_fstring_append (req->msg->url, command, strlen (command));
  386. conn->req = req;
  387. conn->start_time = rspamd_get_ticks (FALSE);
  388. if (compressed) {
  389. ret = rspamd_http_connection_write_message (conn->http_conn, req->msg,
  390. NULL,"application/x-compressed", req,
  391. conn->timeout);
  392. }
  393. else {
  394. ret = rspamd_http_connection_write_message (conn->http_conn, req->msg,
  395. NULL,"text/plain", req, conn->timeout);
  396. }
  397. return ret;
  398. }
  399. void
  400. rspamd_client_destroy (struct rspamd_client_connection *conn)
  401. {
  402. if (conn != NULL) {
  403. rspamd_http_connection_unref (conn->http_conn);
  404. if (conn->req != NULL) {
  405. rspamd_client_request_free (conn->req);
  406. }
  407. close (conn->fd);
  408. if (conn->key) {
  409. rspamd_pubkey_unref (conn->key);
  410. }
  411. if (conn->keypair) {
  412. rspamd_keypair_unref (conn->keypair);
  413. }
  414. g_string_free (conn->server_name, TRUE);
  415. g_free (conn);
  416. }
  417. }