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.

rspamc.c 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506
  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 "config.h"
  17. #include "util.h"
  18. #include "http.h"
  19. #include "rspamdclient.h"
  20. #include "utlist.h"
  21. #include "unix-std.h"
  22. #ifdef HAVE_SYS_WAIT_H
  23. #include <sys/wait.h>
  24. #endif
  25. #define DEFAULT_PORT 11333
  26. #define DEFAULT_CONTROL_PORT 11334
  27. static gchar *connect_str = "localhost";
  28. static gchar *password = NULL;
  29. static gchar *ip = NULL;
  30. static gchar *from = NULL;
  31. static gchar *deliver_to = NULL;
  32. static gchar **rcpts = NULL;
  33. static gchar *user = NULL;
  34. static gchar *helo = "rspamc.local";
  35. static gchar *hostname = "localhost";
  36. static gchar *classifier = NULL;
  37. static gchar *local_addr = NULL;
  38. static gchar *execute = NULL;
  39. static gchar *sort = NULL;
  40. static gchar **http_headers = NULL;
  41. static gint weight = 0;
  42. static gint flag = 0;
  43. static gint max_requests = 8;
  44. static gdouble timeout = 10.0;
  45. static gboolean pass_all;
  46. static gboolean tty = FALSE;
  47. static gboolean verbose = FALSE;
  48. static gboolean print_commands = FALSE;
  49. static gboolean json = FALSE;
  50. static gboolean headers = FALSE;
  51. static gboolean raw = FALSE;
  52. static gboolean extended_urls = FALSE;
  53. static gboolean mime_output = FALSE;
  54. static gchar *key = NULL;
  55. static GList *children;
  56. #define ADD_CLIENT_HEADER(o, n, v) do { \
  57. struct rspamd_http_client_header *nh; \
  58. nh = g_malloc (sizeof (*nh)); \
  59. nh->name = (n); \
  60. nh->value = (v); \
  61. g_queue_push_tail ((o), nh); \
  62. } while (0)
  63. static GOptionEntry entries[] =
  64. {
  65. { "connect", 'h', 0, G_OPTION_ARG_STRING, &connect_str,
  66. "Specify host and port", NULL },
  67. { "password", 'P', 0, G_OPTION_ARG_STRING, &password,
  68. "Specify control password", NULL },
  69. { "classifier", 'c', 0, G_OPTION_ARG_STRING, &classifier,
  70. "Classifier to learn spam or ham", NULL },
  71. { "weight", 'w', 0, G_OPTION_ARG_INT, &weight,
  72. "Weight for fuzzy operations", NULL },
  73. { "flag", 'f', 0, G_OPTION_ARG_INT, &flag, "Flag for fuzzy operations",
  74. NULL },
  75. { "pass-all", 'p', 0, G_OPTION_ARG_NONE, &pass_all, "Pass all filters",
  76. NULL },
  77. { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "More verbose output",
  78. NULL },
  79. { "ip", 'i', 0, G_OPTION_ARG_STRING, &ip,
  80. "Emulate that message was received from specified ip address",
  81. NULL },
  82. { "user", 'u', 0, G_OPTION_ARG_STRING, &user,
  83. "Emulate that message was from specified user", NULL },
  84. { "deliver", 'd', 0, G_OPTION_ARG_STRING, &deliver_to,
  85. "Emulate that message is delivered to specified user", NULL },
  86. { "from", 'F', 0, G_OPTION_ARG_STRING, &from,
  87. "Emulate that message is from specified user", NULL },
  88. { "rcpt", 'r', 0, G_OPTION_ARG_STRING_ARRAY, &rcpts,
  89. "Emulate that message is for specified user", NULL },
  90. { "helo", 0, 0, G_OPTION_ARG_STRING, &helo,
  91. "Imitate SMTP HELO passing from MTA", NULL },
  92. { "hostname", 0, 0, G_OPTION_ARG_STRING, &hostname,
  93. "Imitate hostname passing from MTA", NULL },
  94. { "timeout", 't', 0, G_OPTION_ARG_DOUBLE, &timeout,
  95. "Time in seconds to wait for a reply", NULL },
  96. { "bind", 'b', 0, G_OPTION_ARG_STRING, &local_addr,
  97. "Bind to specified ip address", NULL },
  98. { "commands", 0, 0, G_OPTION_ARG_NONE, &print_commands,
  99. "List available commands", NULL },
  100. { "json", 'j', 0, G_OPTION_ARG_NONE, &json, "Output json reply", NULL },
  101. { "headers", 0, 0, G_OPTION_ARG_NONE, &headers, "Output HTTP headers",
  102. NULL },
  103. { "raw", 0, 0, G_OPTION_ARG_NONE, &raw, "Output raw reply from rspamd",
  104. NULL },
  105. { "ucl", 0, 0, G_OPTION_ARG_NONE, &raw, "Output ucl reply from rspamd",
  106. NULL },
  107. { "max-requests", 'n', 0, G_OPTION_ARG_INT, &max_requests,
  108. "Maximum count of parallel requests to rspamd", NULL },
  109. { "extended-urls", 0, 0, G_OPTION_ARG_NONE, &extended_urls,
  110. "Output urls in extended format", NULL },
  111. { "key", 0, 0, G_OPTION_ARG_STRING, &key,
  112. "Use specified pubkey to encrypt request", NULL },
  113. { "exec", 'e', 0, G_OPTION_ARG_STRING, &execute,
  114. "Execute the specified command and pass output to it", NULL },
  115. { "mime", 'e', 0, G_OPTION_ARG_NONE, &mime_output,
  116. "Write mime body of message with headers instead of just a scan's result", NULL },
  117. {"header", 0, 0, G_OPTION_ARG_STRING_ARRAY, &http_headers,
  118. "Add custom HTTP header to query (can be repeated)", NULL},
  119. {"sort", 0, 0, G_OPTION_ARG_STRING, &sort,
  120. "Sort output in a specific order (name, weight, time)", NULL},
  121. { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
  122. };
  123. /* Copy to avoid linking with librspamdserver */
  124. enum rspamd_metric_action {
  125. METRIC_ACTION_REJECT = 0,
  126. METRIC_ACTION_SOFT_REJECT,
  127. METRIC_ACTION_REWRITE_SUBJECT,
  128. METRIC_ACTION_ADD_HEADER,
  129. METRIC_ACTION_GREYLIST,
  130. METRIC_ACTION_NOACTION,
  131. METRIC_ACTION_MAX
  132. };
  133. static void rspamc_symbols_output (FILE *out, ucl_object_t *obj);
  134. static void rspamc_uptime_output (FILE *out, ucl_object_t *obj);
  135. static void rspamc_counters_output (FILE *out, ucl_object_t *obj);
  136. static void rspamc_stat_output (FILE *out, ucl_object_t *obj);
  137. enum rspamc_command_type {
  138. RSPAMC_COMMAND_UNKNOWN = 0,
  139. RSPAMC_COMMAND_SYMBOLS,
  140. RSPAMC_COMMAND_LEARN_SPAM,
  141. RSPAMC_COMMAND_LEARN_HAM,
  142. RSPAMC_COMMAND_FUZZY_ADD,
  143. RSPAMC_COMMAND_FUZZY_DEL,
  144. RSPAMC_COMMAND_STAT,
  145. RSPAMC_COMMAND_STAT_RESET,
  146. RSPAMC_COMMAND_COUNTERS,
  147. RSPAMC_COMMAND_UPTIME,
  148. RSPAMC_COMMAND_ADD_SYMBOL,
  149. RSPAMC_COMMAND_ADD_ACTION
  150. };
  151. struct rspamc_command {
  152. enum rspamc_command_type cmd;
  153. const char *name;
  154. const char *description;
  155. const char *path;
  156. gboolean is_controller;
  157. gboolean is_privileged;
  158. gboolean need_input;
  159. void (*command_output_func)(FILE *, ucl_object_t *obj);
  160. } rspamc_commands[] = {
  161. {
  162. .cmd = RSPAMC_COMMAND_SYMBOLS,
  163. .name = "symbols",
  164. .path = "check",
  165. .description = "scan message and show symbols (default command)",
  166. .is_controller = FALSE,
  167. .is_privileged = FALSE,
  168. .need_input = TRUE,
  169. .command_output_func = rspamc_symbols_output
  170. },
  171. {
  172. .cmd = RSPAMC_COMMAND_LEARN_SPAM,
  173. .name = "learn_spam",
  174. .path = "learnspam",
  175. .description = "learn message as spam",
  176. .is_controller = TRUE,
  177. .is_privileged = TRUE,
  178. .need_input = TRUE,
  179. .command_output_func = NULL
  180. },
  181. {
  182. .cmd = RSPAMC_COMMAND_LEARN_HAM,
  183. .name = "learn_ham",
  184. .path = "learnham",
  185. .description = "learn message as ham",
  186. .is_controller = TRUE,
  187. .is_privileged = TRUE,
  188. .need_input = TRUE,
  189. .command_output_func = NULL
  190. },
  191. {
  192. .cmd = RSPAMC_COMMAND_FUZZY_ADD,
  193. .name = "fuzzy_add",
  194. .path = "fuzzyadd",
  195. .description =
  196. "add message to fuzzy storage (check -f and -w options for this command)",
  197. .is_controller = TRUE,
  198. .is_privileged = TRUE,
  199. .need_input = TRUE,
  200. .command_output_func = NULL
  201. },
  202. {
  203. .cmd = RSPAMC_COMMAND_FUZZY_DEL,
  204. .name = "fuzzy_del",
  205. .path = "fuzzydel",
  206. .description =
  207. "delete message from fuzzy storage (check -f option for this command)",
  208. .is_controller = TRUE,
  209. .is_privileged = TRUE,
  210. .need_input = TRUE,
  211. .command_output_func = NULL
  212. },
  213. {
  214. .cmd = RSPAMC_COMMAND_STAT,
  215. .name = "stat",
  216. .path = "stat",
  217. .description = "show rspamd statistics",
  218. .is_controller = TRUE,
  219. .is_privileged = FALSE,
  220. .need_input = FALSE,
  221. .command_output_func = rspamc_stat_output,
  222. },
  223. {
  224. .cmd = RSPAMC_COMMAND_STAT_RESET,
  225. .name = "stat_reset",
  226. .path = "statreset",
  227. .description = "show and reset rspamd statistics (useful for graphs)",
  228. .is_controller = TRUE,
  229. .is_privileged = TRUE,
  230. .need_input = FALSE,
  231. .command_output_func = rspamc_stat_output
  232. },
  233. {
  234. .cmd = RSPAMC_COMMAND_COUNTERS,
  235. .name = "counters",
  236. .path = "counters",
  237. .description = "display rspamd symbols statistics",
  238. .is_controller = TRUE,
  239. .is_privileged = FALSE,
  240. .need_input = FALSE,
  241. .command_output_func = rspamc_counters_output
  242. },
  243. {
  244. .cmd = RSPAMC_COMMAND_UPTIME,
  245. .name = "uptime",
  246. .path = "auth",
  247. .description = "show rspamd uptime",
  248. .is_controller = TRUE,
  249. .is_privileged = FALSE,
  250. .need_input = FALSE,
  251. .command_output_func = rspamc_uptime_output
  252. },
  253. {
  254. .cmd = RSPAMC_COMMAND_ADD_SYMBOL,
  255. .name = "add_symbol",
  256. .path = "addsymbol",
  257. .description = "add or modify symbol settings in rspamd",
  258. .is_controller = TRUE,
  259. .is_privileged = TRUE,
  260. .need_input = FALSE,
  261. .command_output_func = NULL
  262. },
  263. {
  264. .cmd = RSPAMC_COMMAND_ADD_ACTION,
  265. .name = "add_action",
  266. .path = "addaction",
  267. .description = "add or modify action settings",
  268. .is_controller = TRUE,
  269. .is_privileged = TRUE,
  270. .need_input = FALSE,
  271. .command_output_func = NULL
  272. }
  273. };
  274. struct rspamc_callback_data {
  275. struct rspamc_command *cmd;
  276. gchar *filename;
  277. gdouble start;
  278. };
  279. /*
  280. * Parse command line
  281. */
  282. static void
  283. read_cmd_line (gint *argc, gchar ***argv)
  284. {
  285. GError *error = NULL;
  286. GOptionContext *context;
  287. /* Prepare parser */
  288. context = g_option_context_new ("- run rspamc client");
  289. g_option_context_set_summary (context,
  290. "Summary:\n Rspamd client version " RVERSION "\n Release id: " RID);
  291. g_option_context_add_main_entries (context, entries, NULL);
  292. /* Parse options */
  293. if (!g_option_context_parse (context, argc, argv, &error)) {
  294. fprintf (stderr, "option parsing failed: %s\n", error->message);
  295. exit (EXIT_FAILURE);
  296. }
  297. if (json) {
  298. raw = TRUE;
  299. }
  300. /* Argc and argv are shifted after this function */
  301. }
  302. /*
  303. * Check rspamc command from string (used for arguments parsing)
  304. */
  305. static struct rspamc_command *
  306. check_rspamc_command (const gchar *cmd)
  307. {
  308. enum rspamc_command_type ct = 0;
  309. guint i;
  310. if (g_ascii_strcasecmp (cmd, "SYMBOLS") == 0 ||
  311. g_ascii_strcasecmp (cmd, "CHECK") == 0 ||
  312. g_ascii_strcasecmp (cmd, "REPORT") == 0) {
  313. /* These all are symbols, don't use other commands */
  314. ct = RSPAMC_COMMAND_SYMBOLS;
  315. }
  316. else if (g_ascii_strcasecmp (cmd, "LEARN_SPAM") == 0) {
  317. ct = RSPAMC_COMMAND_LEARN_SPAM;
  318. }
  319. else if (g_ascii_strcasecmp (cmd, "LEARN_HAM") == 0) {
  320. ct = RSPAMC_COMMAND_LEARN_HAM;
  321. }
  322. else if (g_ascii_strcasecmp (cmd, "FUZZY_ADD") == 0) {
  323. ct = RSPAMC_COMMAND_FUZZY_ADD;
  324. }
  325. else if (g_ascii_strcasecmp (cmd, "FUZZY_DEL") == 0) {
  326. ct = RSPAMC_COMMAND_FUZZY_DEL;
  327. }
  328. else if (g_ascii_strcasecmp (cmd, "STAT") == 0) {
  329. ct = RSPAMC_COMMAND_STAT;
  330. }
  331. else if (g_ascii_strcasecmp (cmd, "STAT_RESET") == 0) {
  332. ct = RSPAMC_COMMAND_STAT_RESET;
  333. }
  334. else if (g_ascii_strcasecmp (cmd, "COUNTERS") == 0) {
  335. ct = RSPAMC_COMMAND_COUNTERS;
  336. }
  337. else if (g_ascii_strcasecmp (cmd, "UPTIME") == 0) {
  338. ct = RSPAMC_COMMAND_UPTIME;
  339. }
  340. else if (g_ascii_strcasecmp (cmd, "ADD_SYMBOL") == 0) {
  341. ct = RSPAMC_COMMAND_ADD_SYMBOL;
  342. }
  343. else if (g_ascii_strcasecmp (cmd, "ADD_ACTION") == 0) {
  344. ct = RSPAMC_COMMAND_ADD_ACTION;
  345. }
  346. for (i = 0; i < G_N_ELEMENTS (rspamc_commands); i++) {
  347. if (rspamc_commands[i].cmd == ct) {
  348. return &rspamc_commands[i];
  349. }
  350. }
  351. return NULL;
  352. }
  353. static void
  354. print_commands_list (void)
  355. {
  356. guint i;
  357. rspamd_fprintf (stdout, "Rspamc commands summary:\n");
  358. for (i = 0; i < G_N_ELEMENTS (rspamc_commands); i++) {
  359. rspamd_fprintf (stdout,
  360. " %10s (%7s%1s)\t%s\n",
  361. rspamc_commands[i].name,
  362. rspamc_commands[i].is_controller ? "control" : "normal",
  363. rspamc_commands[i].is_privileged ? "*" : "",
  364. rspamc_commands[i].description);
  365. }
  366. rspamd_fprintf (stdout,
  367. "\n* is for privileged commands that may need password (see -P option)\n");
  368. rspamd_fprintf (stdout,
  369. "control commands use port 11334 while normal use 11333 by default (see -h option)\n");
  370. }
  371. static void
  372. add_options (GQueue *opts)
  373. {
  374. GString *numbuf;
  375. gchar **hdr, **rcpt;
  376. if (ip != NULL) {
  377. ADD_CLIENT_HEADER (opts, "Ip", ip);
  378. }
  379. if (from != NULL) {
  380. ADD_CLIENT_HEADER (opts, "From", from);
  381. }
  382. if (user != NULL) {
  383. ADD_CLIENT_HEADER (opts, "User", user);
  384. }
  385. if (rcpts != NULL) {
  386. for (rcpt = rcpts; *rcpt != NULL; rcpt ++) {
  387. ADD_CLIENT_HEADER (opts, "Rcpt", *rcpt);
  388. }
  389. }
  390. if (deliver_to != NULL) {
  391. ADD_CLIENT_HEADER (opts, "Deliver-To", deliver_to);
  392. }
  393. if (helo != NULL) {
  394. ADD_CLIENT_HEADER (opts, "Helo", helo);
  395. }
  396. if (hostname != NULL) {
  397. ADD_CLIENT_HEADER (opts, "Hostname", hostname);
  398. }
  399. if (password != NULL) {
  400. ADD_CLIENT_HEADER (opts, "Password", password);
  401. }
  402. if (pass_all) {
  403. ADD_CLIENT_HEADER (opts, "Pass", "all");
  404. }
  405. if (classifier) {
  406. ADD_CLIENT_HEADER (opts, "Classifier", classifier);
  407. }
  408. if (weight != 0) {
  409. numbuf = g_string_sized_new (8);
  410. rspamd_printf_gstring (numbuf, "%d", weight);
  411. ADD_CLIENT_HEADER (opts, "Weight", numbuf->str);
  412. }
  413. if (flag != 0) {
  414. numbuf = g_string_sized_new (8);
  415. rspamd_printf_gstring (numbuf, "%d", flag);
  416. ADD_CLIENT_HEADER (opts, "Flag", numbuf->str);
  417. }
  418. if (extended_urls) {
  419. ADD_CLIENT_HEADER (opts, "URL-Format", "extended");
  420. }
  421. hdr = http_headers;
  422. while (hdr != NULL && *hdr != NULL) {
  423. gchar **kv = g_strsplit_set (*hdr, ":=", 2);
  424. if (kv == NULL || kv[1] == NULL) {
  425. ADD_CLIENT_HEADER (opts, *hdr, "");
  426. if (kv) {
  427. g_strfreev (kv);
  428. }
  429. }
  430. else {
  431. ADD_CLIENT_HEADER (opts, kv[0], kv[1]);
  432. }
  433. hdr ++;
  434. }
  435. }
  436. static void
  437. rspamc_symbol_output (FILE *out, const ucl_object_t *obj)
  438. {
  439. const ucl_object_t *val, *cur;
  440. ucl_object_iter_t it = NULL;
  441. gboolean first = TRUE;
  442. rspamd_fprintf (out, "Symbol: %s ", ucl_object_key (obj));
  443. val = ucl_object_find_key (obj, "score");
  444. if (val != NULL) {
  445. rspamd_fprintf (out, "(%.2f)", ucl_object_todouble (val));
  446. }
  447. val = ucl_object_find_key (obj, "options");
  448. if (val != NULL && val->type == UCL_ARRAY) {
  449. rspamd_fprintf (out, "[");
  450. while ((cur = ucl_iterate_object (val, &it, TRUE)) != NULL) {
  451. if (first) {
  452. rspamd_fprintf (out, "%s", ucl_object_tostring (cur));
  453. first = FALSE;
  454. }
  455. else {
  456. rspamd_fprintf (out, ", %s", ucl_object_tostring (cur));
  457. }
  458. }
  459. rspamd_fprintf (out, "]");
  460. }
  461. rspamd_fprintf (out, "\n");
  462. }
  463. static gint
  464. rspamc_symbols_sort_func (gconstpointer a, gconstpointer b)
  465. {
  466. ucl_object_t * const *ua = a, * const *ub = b;
  467. return strcmp (ucl_object_key (*ua), ucl_object_key (*ub));
  468. }
  469. static void
  470. rspamc_metric_output (FILE *out, const ucl_object_t *obj)
  471. {
  472. ucl_object_iter_t it = NULL;
  473. const ucl_object_t *cur;
  474. gdouble score = 0, required_score = 0;
  475. gint got_scores = 0;
  476. GPtrArray *sym_ptr;
  477. guint i;
  478. sym_ptr = g_ptr_array_new ();
  479. rspamd_fprintf (out, "[Metric: %s]\n", ucl_object_key (obj));
  480. while ((cur = ucl_iterate_object (obj, &it, true)) != NULL) {
  481. if (g_ascii_strcasecmp (ucl_object_key (cur), "is_spam") == 0) {
  482. rspamd_fprintf (out, "Spam: %s\n", ucl_object_toboolean (cur) ?
  483. "true" : "false");
  484. }
  485. else if (g_ascii_strcasecmp (ucl_object_key (cur), "score") == 0) {
  486. score = ucl_object_todouble (cur);
  487. got_scores++;
  488. }
  489. else if (g_ascii_strcasecmp (ucl_object_key (cur),
  490. "required_score") == 0) {
  491. required_score = ucl_object_todouble (cur);
  492. got_scores++;
  493. }
  494. else if (g_ascii_strcasecmp (ucl_object_key (cur), "action") == 0) {
  495. rspamd_fprintf (out, "Action: %s\n", ucl_object_tostring (cur));
  496. }
  497. else if (cur->type == UCL_OBJECT) {
  498. g_ptr_array_add (sym_ptr, (void *)cur);
  499. }
  500. if (got_scores == 2) {
  501. rspamd_fprintf (out,
  502. "Score: %.2f / %.2f\n",
  503. score,
  504. required_score);
  505. got_scores = 0;
  506. }
  507. }
  508. g_ptr_array_sort (sym_ptr, rspamc_symbols_sort_func);
  509. for (i = 0; i < sym_ptr->len; i ++) {
  510. cur = (const ucl_object_t *)g_ptr_array_index (sym_ptr, i);
  511. rspamc_symbol_output (out, cur);
  512. }
  513. g_ptr_array_free (sym_ptr, TRUE);
  514. }
  515. static void
  516. rspamc_symbols_output (FILE *out, ucl_object_t *obj)
  517. {
  518. ucl_object_iter_t it = NULL, mit = NULL;
  519. const ucl_object_t *cur, *cmesg;
  520. gchar *emitted;
  521. while ((cur = ucl_iterate_object (obj, &it, true)) != NULL) {
  522. if (g_ascii_strcasecmp (ucl_object_key (cur), "message-id") == 0) {
  523. rspamd_fprintf (out, "Message-ID: %s\n", ucl_object_tostring (
  524. cur));
  525. }
  526. else if (g_ascii_strcasecmp (ucl_object_key (cur), "queue-id") == 0) {
  527. rspamd_fprintf (out, "Queue-ID: %s\n",
  528. ucl_object_tostring (cur));
  529. }
  530. else if (g_ascii_strcasecmp (ucl_object_key (cur), "urls") == 0) {
  531. if (!extended_urls) {
  532. emitted = ucl_object_emit (cur, UCL_EMIT_JSON_COMPACT);
  533. }
  534. else {
  535. emitted = ucl_object_emit (cur, UCL_EMIT_JSON);
  536. }
  537. rspamd_fprintf (out, "Urls: %s\n", emitted);
  538. free (emitted);
  539. }
  540. else if (g_ascii_strcasecmp (ucl_object_key (cur), "emails") == 0) {
  541. emitted = ucl_object_emit (cur, UCL_EMIT_JSON_COMPACT);
  542. rspamd_fprintf (out, "Emails: %s\n", emitted);
  543. free (emitted);
  544. }
  545. else if (g_ascii_strcasecmp (ucl_object_key (cur), "error") == 0) {
  546. rspamd_fprintf (out, "Scan error: %s\n", ucl_object_tostring (
  547. cur));
  548. }
  549. else if (g_ascii_strcasecmp (ucl_object_key (cur), "messages") == 0) {
  550. if (cur->type == UCL_ARRAY) {
  551. mit = NULL;
  552. while ((cmesg = ucl_iterate_object (cur, &mit, true)) != NULL) {
  553. rspamd_fprintf (out, "Message: %s\n",
  554. ucl_object_tostring (cmesg));
  555. }
  556. }
  557. }
  558. else if (cur->type == UCL_OBJECT) {
  559. /* Parse metric */
  560. rspamc_metric_output (out, cur);
  561. }
  562. }
  563. }
  564. static void
  565. rspamc_uptime_output (FILE *out, ucl_object_t *obj)
  566. {
  567. const ucl_object_t *elt;
  568. int64_t seconds, days, hours, minutes;
  569. elt = ucl_object_find_key (obj, "version");
  570. if (elt != NULL) {
  571. rspamd_fprintf (out, "Rspamd version: %s\n", ucl_object_tostring (
  572. elt));
  573. }
  574. elt = ucl_object_find_key (obj, "uptime");
  575. if (elt != NULL) {
  576. rspamd_printf ("Uptime: ");
  577. seconds = ucl_object_toint (elt);
  578. if (seconds >= 2 * 3600) {
  579. days = seconds / 86400;
  580. hours = seconds / 3600 - days * 24;
  581. minutes = seconds / 60 - hours * 60 - days * 1440;
  582. rspamd_printf ("%L day%s %L hour%s %L minute%s\n", days,
  583. days > 1 ? "s" : "", hours, hours > 1 ? "s" : "",
  584. minutes, minutes > 1 ? "s" : "");
  585. }
  586. /* If uptime is less than 1 minute print only seconds */
  587. else if (seconds / 60 == 0) {
  588. rspamd_printf ("%L second%s\n", seconds,
  589. (gint)seconds > 1 ? "s" : "");
  590. }
  591. /* Else print the minutes and seconds. */
  592. else {
  593. hours = seconds / 3600;
  594. minutes = seconds / 60 - hours * 60;
  595. seconds -= hours * 3600 + minutes * 60;
  596. rspamd_printf ("%L hour %L minute%s %L second%s\n", hours,
  597. minutes, minutes > 1 ? "s" : "",
  598. seconds, seconds > 1 ? "s" : "");
  599. }
  600. }
  601. }
  602. static gint
  603. rspamc_counters_sort (const ucl_object_t **o1, const ucl_object_t **o2)
  604. {
  605. gint order1 = 0, order2 = 0, c;
  606. const ucl_object_t *elt1, *elt2;
  607. gboolean inverse = FALSE;
  608. gchar **args;
  609. if (sort != NULL) {
  610. args = g_strsplit_set (sort, ":", 2);
  611. if (args && args[0]) {
  612. if (args[1] && g_ascii_strcasecmp (args[1], "desc") == 0) {
  613. inverse = TRUE;
  614. }
  615. if (g_ascii_strcasecmp (args[0], "name") == 0) {
  616. elt1 = ucl_object_find_key (*o1, "symbol");
  617. elt2 = ucl_object_find_key (*o2, "symbol");
  618. if (elt1 && elt2) {
  619. c = strcmp (ucl_object_tostring (elt1),
  620. ucl_object_tostring (elt2));
  621. order1 = c > 0 ? 1 : 0;
  622. order2 = c < 0 ? 1 : 0;
  623. }
  624. }
  625. else if (g_ascii_strcasecmp (args[0], "weight") == 0) {
  626. elt1 = ucl_object_find_key (*o1, "weight");
  627. elt2 = ucl_object_find_key (*o2, "weight");
  628. if (elt1 && elt2) {
  629. order1 = ucl_object_todouble (elt1) * 1000.0;
  630. order2 = ucl_object_todouble (elt2) * 1000.0;
  631. }
  632. }
  633. else if (g_ascii_strcasecmp (args[0], "frequency") == 0) {
  634. elt1 = ucl_object_find_key (*o1, "frequency");
  635. elt2 = ucl_object_find_key (*o2, "frequency");
  636. if (elt1 && elt2) {
  637. order1 = ucl_object_toint (elt1);
  638. order2 = ucl_object_toint (elt2);
  639. }
  640. }
  641. else if (g_ascii_strcasecmp (args[0], "time") == 0) {
  642. elt1 = ucl_object_find_key (*o1, "time");
  643. elt2 = ucl_object_find_key (*o2, "time");
  644. if (elt1 && elt2) {
  645. order1 = ucl_object_todouble (elt1) * 1000000;
  646. order2 = ucl_object_todouble (elt2) * 1000000;
  647. }
  648. }
  649. g_strfreev (args);
  650. }
  651. }
  652. return (inverse ? (order2 - order1) : (order1 - order2));
  653. }
  654. static void
  655. rspamc_counters_output (FILE *out, ucl_object_t *obj)
  656. {
  657. const ucl_object_t *cur, *sym, *weight, *freq, *tim;
  658. ucl_object_iter_t iter = NULL;
  659. gchar fmt_buf[64], dash_buf[82];
  660. gint l, max_len = INT_MIN, i;
  661. if (obj->type != UCL_ARRAY) {
  662. rspamd_printf ("Bad output\n");
  663. return;
  664. }
  665. /* Sort symbols by their order */
  666. if (sort != NULL) {
  667. ucl_object_array_sort (obj, rspamc_counters_sort);
  668. }
  669. /* Find maximum width of symbol's name */
  670. while ((cur = ucl_iterate_object (obj, &iter, true)) != NULL) {
  671. sym = ucl_object_find_key (cur, "symbol");
  672. if (sym != NULL) {
  673. l = sym->len;
  674. if (l > max_len) {
  675. max_len = MIN (40, l);
  676. }
  677. }
  678. }
  679. rspamd_snprintf (fmt_buf, sizeof (fmt_buf),
  680. "| %%3s | %%%ds | %%6s | %%9s | %%9s |\n", max_len);
  681. memset (dash_buf, '-', 40 + max_len);
  682. dash_buf[40 + max_len] = '\0';
  683. printf ("Symbols cache\n");
  684. printf (" %s \n", dash_buf);
  685. if (tty) {
  686. printf ("\033[1m");
  687. }
  688. printf (fmt_buf, "Pri", "Symbol", "Weight", "Frequency", "Avg. time");
  689. if (tty) {
  690. printf ("\033[0m");
  691. }
  692. rspamd_snprintf (fmt_buf, sizeof (fmt_buf),
  693. "| %%3d | %%%ds | %%6.1f | %%9d | %%9.3f |\n", max_len);
  694. iter = NULL;
  695. i = 0;
  696. while ((cur = ucl_iterate_object (obj, &iter, true)) != NULL) {
  697. printf (" %s \n", dash_buf);
  698. sym = ucl_object_find_key (cur, "symbol");
  699. weight = ucl_object_find_key (cur, "weight");
  700. freq = ucl_object_find_key (cur, "frequency");
  701. tim = ucl_object_find_key (cur, "time");
  702. if (sym && weight && freq && tim) {
  703. printf (fmt_buf, i,
  704. ucl_object_tostring (sym),
  705. ucl_object_todouble (weight),
  706. (gint)ucl_object_toint (freq),
  707. ucl_object_todouble (tim));
  708. }
  709. i++;
  710. }
  711. printf (" %s \n", dash_buf);
  712. }
  713. static void
  714. rspamc_stat_actions (ucl_object_t *obj, GString *out, gint64 scanned)
  715. {
  716. const ucl_object_t *actions = ucl_object_find_key (obj, "actions"), *cur;
  717. ucl_object_iter_t iter = NULL;
  718. gint64 spam, ham;
  719. if (actions && ucl_object_type (actions) == UCL_OBJECT) {
  720. while ((cur = ucl_iterate_object (actions, &iter, true)) != NULL) {
  721. gint64 cnt = ucl_object_toint (cur);
  722. rspamd_printf_gstring (out, "Messages with action %s: %L"
  723. ", %.2f%%\n", ucl_object_key (cur), cnt,
  724. ((gdouble)cnt / (gdouble)scanned) * 100.);
  725. }
  726. }
  727. spam = ucl_object_toint (ucl_object_find_key (obj, "spam_count"));
  728. ham = ucl_object_toint (ucl_object_find_key (obj, "ham_count"));
  729. rspamd_printf_gstring (out, "Messages treated as spam: %L, %.2f%%\n", spam,
  730. ((gdouble)spam / (gdouble)scanned) * 100.);
  731. rspamd_printf_gstring (out, "Messages treated as ham: %L, %.2f%%\n", ham,
  732. ((gdouble)ham / (gdouble)scanned) * 100.);
  733. }
  734. static void
  735. rspamc_stat_statfile (const ucl_object_t *obj, GString *out)
  736. {
  737. gint64 version, size, blocks, used_blocks, nlanguages, nusers;
  738. const gchar *label, *symbol, *type;
  739. version = ucl_object_toint (ucl_object_find_key (obj, "revision"));
  740. size = ucl_object_toint (ucl_object_find_key (obj, "size"));
  741. blocks = ucl_object_toint (ucl_object_find_key (obj, "total"));
  742. used_blocks = ucl_object_toint (ucl_object_find_key (obj, "used"));
  743. label = ucl_object_tostring (ucl_object_find_key (obj, "label"));
  744. symbol = ucl_object_tostring (ucl_object_find_key (obj, "symbol"));
  745. type = ucl_object_tostring (ucl_object_find_key (obj, "type"));
  746. nlanguages = ucl_object_toint (ucl_object_find_key (obj, "languages"));
  747. nusers = ucl_object_toint (ucl_object_find_key (obj, "users"));
  748. if (label) {
  749. rspamd_printf_gstring (out, "Statfile: %s <%s> type: %s; ", symbol,
  750. label, type);
  751. }
  752. else {
  753. rspamd_printf_gstring (out, "Statfile: %s type: %s; ", symbol, type);
  754. }
  755. rspamd_printf_gstring (out, "length: %hL; free blocks: %hL; total blocks: %hL; "
  756. "free: %.2f%%; learned: %L; users: %L; languages: %L\n",
  757. size,
  758. blocks - used_blocks, blocks,
  759. blocks > 0 ? (blocks - used_blocks) * 100.0 / (gdouble)blocks : 0,
  760. version,
  761. nusers, nlanguages);
  762. }
  763. static void
  764. rspamc_stat_output (FILE *out, ucl_object_t *obj)
  765. {
  766. GString *out_str;
  767. ucl_object_iter_t iter = NULL;
  768. const ucl_object_t *st, *cur;
  769. gint64 scanned;
  770. out_str = g_string_sized_new (BUFSIZ);
  771. scanned = ucl_object_toint (ucl_object_find_key (obj, "scanned"));
  772. rspamd_printf_gstring (out_str, "Messages scanned: %L\n",
  773. scanned);
  774. if (scanned > 0) {
  775. rspamc_stat_actions (obj, out_str, scanned);
  776. }
  777. rspamd_printf_gstring (out_str, "Messages learned: %L\n",
  778. ucl_object_toint (ucl_object_find_key (obj, "learned")));
  779. rspamd_printf_gstring (out_str, "Connections count: %L\n",
  780. ucl_object_toint (ucl_object_find_key (obj, "connections")));
  781. rspamd_printf_gstring (out_str, "Control connections count: %L\n",
  782. ucl_object_toint (ucl_object_find_key (obj, "control_connections")));
  783. /* Pools */
  784. rspamd_printf_gstring (out_str, "Pools allocated: %L\n",
  785. ucl_object_toint (ucl_object_find_key (obj, "pools_allocated")));
  786. rspamd_printf_gstring (out_str, "Pools freed: %L\n",
  787. ucl_object_toint (ucl_object_find_key (obj, "pools_freed")));
  788. rspamd_printf_gstring (out_str, "Bytes allocated: %HL\n",
  789. ucl_object_toint (ucl_object_find_key (obj, "bytes_allocated")));
  790. rspamd_printf_gstring (out_str, "Memory chunks allocated: %L\n",
  791. ucl_object_toint (ucl_object_find_key (obj, "chunks_allocated")));
  792. rspamd_printf_gstring (out_str, "Shared chunks allocated: %L\n",
  793. ucl_object_toint (ucl_object_find_key (obj, "shared_chunks_allocated")));
  794. rspamd_printf_gstring (out_str, "Chunks freed: %L\n",
  795. ucl_object_toint (ucl_object_find_key (obj, "chunks_freed")));
  796. rspamd_printf_gstring (out_str, "Oversized chunks: %L\n",
  797. ucl_object_toint (ucl_object_find_key (obj, "chunks_oversized")));
  798. /* Fuzzy */
  799. rspamd_printf_gstring (out_str, "Fuzzy hashes stored: %L\n",
  800. ucl_object_toint (ucl_object_find_key (obj, "fuzzy_stored")));
  801. rspamd_printf_gstring (out_str, "Fuzzy hashes expired: %L\n",
  802. ucl_object_toint (ucl_object_find_key (obj, "fuzzy_expired")));
  803. st = ucl_object_find_key (obj, "fuzzy_checked");
  804. if (st != NULL && ucl_object_type (st) == UCL_ARRAY) {
  805. rspamd_printf_gstring (out_str, "Fuzzy hashes checked: ");
  806. iter = NULL;
  807. while ((cur = ucl_iterate_object (st, &iter, true)) != NULL) {
  808. rspamd_printf_gstring (out_str, "%hL ", ucl_object_toint (cur));
  809. }
  810. rspamd_printf_gstring (out_str, "\n");
  811. }
  812. st = ucl_object_find_key (obj, "fuzzy_found");
  813. if (st != NULL && ucl_object_type (st) == UCL_ARRAY) {
  814. rspamd_printf_gstring (out_str, "Fuzzy hashes found: ");
  815. iter = NULL;
  816. while ((cur = ucl_iterate_object (st, &iter, true)) != NULL) {
  817. rspamd_printf_gstring (out_str, "%hL ", ucl_object_toint (cur));
  818. }
  819. rspamd_printf_gstring (out_str, "\n");
  820. }
  821. st = ucl_object_find_key (obj, "statfiles");
  822. if (st != NULL && ucl_object_type (st) == UCL_ARRAY) {
  823. iter = NULL;
  824. while ((cur = ucl_iterate_object (st, &iter, true)) != NULL) {
  825. rspamc_stat_statfile (cur, out_str);
  826. }
  827. }
  828. rspamd_printf_gstring (out_str, "Total learns: %L\n",
  829. ucl_object_toint (ucl_object_find_key (obj, "total_learns")));
  830. rspamd_fprintf (out, "%v", out_str);
  831. }
  832. static void
  833. rspamc_output_headers (FILE *out, struct rspamd_http_message *msg)
  834. {
  835. struct rspamd_http_header *h;
  836. LL_FOREACH (msg->headers, h)
  837. {
  838. rspamd_fprintf (out, "%T: %T\n", h->name, h->value);
  839. }
  840. rspamd_fprintf (out, "\n");
  841. }
  842. static gboolean
  843. rspamd_action_from_str (const gchar *data, gint *result)
  844. {
  845. if (g_ascii_strncasecmp (data, "reject", sizeof ("reject") - 1) == 0) {
  846. *result = METRIC_ACTION_REJECT;
  847. }
  848. else if (g_ascii_strncasecmp (data, "greylist",
  849. sizeof ("greylist") - 1) == 0) {
  850. *result = METRIC_ACTION_GREYLIST;
  851. }
  852. else if (g_ascii_strncasecmp (data, "add_header", sizeof ("add_header") -
  853. 1) == 0) {
  854. *result = METRIC_ACTION_ADD_HEADER;
  855. }
  856. else if (g_ascii_strncasecmp (data, "rewrite_subject",
  857. sizeof ("rewrite_subject") - 1) == 0) {
  858. *result = METRIC_ACTION_REWRITE_SUBJECT;
  859. }
  860. else if (g_ascii_strncasecmp (data, "add header", sizeof ("add header") -
  861. 1) == 0) {
  862. *result = METRIC_ACTION_ADD_HEADER;
  863. }
  864. else if (g_ascii_strncasecmp (data, "rewrite subject",
  865. sizeof ("rewrite subject") - 1) == 0) {
  866. *result = METRIC_ACTION_REWRITE_SUBJECT;
  867. }
  868. else if (g_ascii_strncasecmp (data, "soft_reject",
  869. sizeof ("soft_reject") - 1) == 0) {
  870. *result = METRIC_ACTION_SOFT_REJECT;
  871. }
  872. else if (g_ascii_strncasecmp (data, "soft reject",
  873. sizeof ("soft reject") - 1) == 0) {
  874. *result = METRIC_ACTION_SOFT_REJECT;
  875. }
  876. else if (g_ascii_strncasecmp (data, "no_action",
  877. sizeof ("soft_reject") - 1) == 0) {
  878. *result = METRIC_ACTION_NOACTION;
  879. }
  880. else if (g_ascii_strncasecmp (data, "no action",
  881. sizeof ("soft reject") - 1) == 0) {
  882. *result = METRIC_ACTION_NOACTION;
  883. }
  884. else {
  885. return FALSE;
  886. }
  887. return TRUE;
  888. }
  889. static void
  890. rspamc_mime_output (FILE *out, ucl_object_t *result, GString *input,
  891. gdouble time, GError *err)
  892. {
  893. const ucl_object_t *cur, *metric, *res;
  894. ucl_object_iter_t it = NULL;
  895. const gchar *action = "no action";
  896. gchar scorebuf[32];
  897. GString *symbuf, *folded_symbuf, *added_headers;
  898. gint act = 0;
  899. goffset headers_pos;
  900. gdouble score = 0.0, required_score = 0.0;
  901. gboolean is_spam = FALSE;
  902. gchar *json_header, *json_header_encoded, *sc;
  903. headers_pos = rspamd_string_find_eoh (input);
  904. if (headers_pos == -1) {
  905. rspamd_fprintf (stderr,"cannot find end of headers position");
  906. return;
  907. }
  908. added_headers = g_string_sized_new (127);
  909. if (result) {
  910. metric = ucl_object_find_key (result, "default");
  911. if (metric != NULL) {
  912. res = ucl_object_find_key (metric, "action");
  913. if (res) {
  914. action = ucl_object_tostring (res);
  915. }
  916. res = ucl_object_find_key (metric, "score");
  917. if (res) {
  918. score = ucl_object_todouble (res);
  919. }
  920. res = ucl_object_find_key (metric, "required_score");
  921. if (res) {
  922. required_score = ucl_object_todouble (res);
  923. }
  924. }
  925. rspamd_action_from_str (action, &act);
  926. if (act < METRIC_ACTION_GREYLIST) {
  927. is_spam = TRUE;
  928. }
  929. rspamd_printf_gstring (added_headers, "X-Spam-Scanner: %s\r\n",
  930. "rspamc " RVERSION);
  931. rspamd_printf_gstring (added_headers, "X-Spam-Scan-Time: %.3f\r\n",
  932. time);
  933. if (is_spam) {
  934. rspamd_printf_gstring (added_headers, "X-Spam: yes\r\n");
  935. }
  936. rspamd_printf_gstring (added_headers, "X-Spam-Action: %s\r\n",
  937. action);
  938. rspamd_printf_gstring (added_headers, "X-Spam-Score: %.2f / %.2f\r\n",
  939. score, required_score);
  940. /* SA style stars header */
  941. for (sc = scorebuf; sc < scorebuf + sizeof (scorebuf) - 1 && score > 0;
  942. sc ++, score -= 1.0) {
  943. *sc = '*';
  944. }
  945. *sc = '\0';
  946. rspamd_printf_gstring (added_headers, "X-Spam-Level: %s\r\n",
  947. scorebuf);
  948. /* Short description of all symbols */
  949. symbuf = g_string_sized_new (64);
  950. while ((cur = ucl_iterate_object (metric, &it, true)) != NULL) {
  951. if (ucl_object_type (cur) == UCL_OBJECT) {
  952. rspamd_printf_gstring (symbuf, "%s,", ucl_object_key (cur));
  953. }
  954. }
  955. /* Trim the last comma */
  956. if (symbuf->str[symbuf->len - 1] == ',') {
  957. g_string_erase (symbuf, symbuf->len - 1, 1);
  958. }
  959. folded_symbuf = rspamd_header_value_fold ("X-Spam-Symbols",
  960. symbuf->str,
  961. 0);
  962. rspamd_printf_gstring (added_headers, "X-Spam-Symbols: %v\r\n",
  963. folded_symbuf);
  964. g_string_free (folded_symbuf, TRUE);
  965. g_string_free (symbuf, TRUE);
  966. if (json || raw) {
  967. /* We also append json data as a specific header */
  968. if (json) {
  969. json_header = ucl_object_emit (result, UCL_EMIT_JSON);
  970. }
  971. else {
  972. json_header = ucl_object_emit (result, UCL_EMIT_CONFIG);
  973. }
  974. json_header_encoded = rspamd_encode_base64_fold (json_header,
  975. strlen (json_header), 60, NULL);
  976. free (json_header);
  977. rspamd_printf_gstring (added_headers,
  978. "X-Spam-Result: %s\r\n",
  979. json_header_encoded);
  980. g_free (json_header_encoded);
  981. }
  982. ucl_object_unref (result);
  983. }
  984. else {
  985. rspamd_printf_gstring (added_headers, "X-Spam-Scanner: %s\r\n",
  986. "rspamc " RVERSION);
  987. rspamd_printf_gstring (added_headers, "X-Spam-Scan-Time: %.3f\r\n",
  988. time);
  989. rspamd_printf_gstring (added_headers, "X-Spam-Error: %e\r\n",
  990. err);
  991. }
  992. /* Write message */
  993. if (rspamd_fprintf (out, "%*s", (gint)headers_pos, input->str)
  994. == headers_pos) {
  995. if (rspamd_fprintf (out, "%v", added_headers)
  996. == (gint)added_headers->len) {
  997. rspamd_fprintf (out, "%s", input->str + headers_pos);
  998. }
  999. }
  1000. g_string_free (added_headers, TRUE);
  1001. }
  1002. static void
  1003. rspamc_client_execute_cmd (struct rspamc_command *cmd, ucl_object_t *result,
  1004. GString *input, gdouble time, GError *err)
  1005. {
  1006. gchar **eargv;
  1007. gint eargc, infd, outfd, errfd;
  1008. GError *exec_err = NULL;
  1009. GPid cld;
  1010. FILE *out;
  1011. gchar *ucl_out;
  1012. if (!g_shell_parse_argv (execute, &eargc, &eargv, &err)) {
  1013. rspamd_fprintf (stderr, "Cannot execute %s: %e", execute, err);
  1014. g_error_free (err);
  1015. return;
  1016. }
  1017. if (!g_spawn_async_with_pipes (NULL, eargv, NULL,
  1018. G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &cld,
  1019. &infd, &outfd, &errfd, &exec_err)) {
  1020. rspamd_fprintf (stderr, "Cannot execute %s: %e", execute, exec_err);
  1021. g_error_free (exec_err);
  1022. exit (EXIT_FAILURE);
  1023. }
  1024. else {
  1025. children = g_list_prepend (children, GSIZE_TO_POINTER (cld));
  1026. out = fdopen (infd, "w");
  1027. if (cmd->cmd == RSPAMC_COMMAND_SYMBOLS && mime_output && input) {
  1028. rspamc_mime_output (out, result, input, time, err);
  1029. }
  1030. else if (result) {
  1031. if (raw || cmd->command_output_func == NULL) {
  1032. if (json) {
  1033. ucl_out = ucl_object_emit (result, UCL_EMIT_JSON);
  1034. }
  1035. else {
  1036. ucl_out = ucl_object_emit (result, UCL_EMIT_CONFIG);
  1037. }
  1038. rspamd_fprintf (out, "%s", ucl_out);
  1039. free (ucl_out);
  1040. }
  1041. else {
  1042. cmd->command_output_func (out, result);
  1043. }
  1044. ucl_object_unref (result);
  1045. }
  1046. else {
  1047. rspamd_fprintf (out, "%e\n", err);
  1048. }
  1049. fflush (out);
  1050. fclose (out);
  1051. }
  1052. g_strfreev (eargv);
  1053. }
  1054. static void
  1055. rspamc_client_cb (struct rspamd_client_connection *conn,
  1056. struct rspamd_http_message *msg,
  1057. const gchar *name, ucl_object_t *result, GString *input,
  1058. gpointer ud, GError *err)
  1059. {
  1060. gchar *ucl_out;
  1061. struct rspamc_callback_data *cbdata = (struct rspamc_callback_data *)ud;
  1062. struct rspamc_command *cmd;
  1063. FILE *out = stdout;
  1064. gdouble finish = rspamd_get_ticks (), diff;
  1065. cmd = cbdata->cmd;
  1066. diff = finish - cbdata->start;
  1067. if (execute) {
  1068. /* Pass all to the external command */
  1069. rspamc_client_execute_cmd (cmd, result, input, diff, err);
  1070. }
  1071. else {
  1072. if (cmd->cmd == RSPAMC_COMMAND_SYMBOLS && mime_output && input) {
  1073. rspamc_mime_output (out, result, input, diff, err);
  1074. }
  1075. else {
  1076. if (cmd->need_input) {
  1077. rspamd_fprintf (out, "Results for file: %s (%.3f seconds)\n",
  1078. cbdata->filename, diff);
  1079. }
  1080. else {
  1081. rspamd_fprintf (out, "Results for command: %s (%.3f seconds)\n",
  1082. cmd->name, diff);
  1083. }
  1084. if (result != NULL) {
  1085. if (headers && msg != NULL) {
  1086. rspamc_output_headers (out, msg);
  1087. }
  1088. if (raw || cmd->command_output_func == NULL) {
  1089. if (json) {
  1090. ucl_out = ucl_object_emit (result, UCL_EMIT_JSON);
  1091. }
  1092. else {
  1093. ucl_out = ucl_object_emit (result, UCL_EMIT_CONFIG);
  1094. }
  1095. rspamd_fprintf (out, "%s", ucl_out);
  1096. free (ucl_out);
  1097. }
  1098. else {
  1099. cmd->command_output_func (out, result);
  1100. }
  1101. ucl_object_unref (result);
  1102. }
  1103. else if (err != NULL) {
  1104. rspamd_fprintf (out, "%s\n", err->message);
  1105. if (json && msg != NULL && msg->body != NULL) {
  1106. /* We can also output the resulting json */
  1107. rspamd_fprintf (out, "%V\n", msg->body);
  1108. }
  1109. }
  1110. }
  1111. rspamd_fprintf (out, "\n");
  1112. fflush (out);
  1113. }
  1114. rspamd_client_destroy (conn);
  1115. g_free (cbdata->filename);
  1116. g_slice_free1 (sizeof (struct rspamc_callback_data), cbdata);
  1117. }
  1118. static void
  1119. rspamc_process_input (struct event_base *ev_base, struct rspamc_command *cmd,
  1120. FILE *in, const gchar *name, GQueue *attrs)
  1121. {
  1122. struct rspamd_client_connection *conn;
  1123. gchar **connectv;
  1124. guint16 port;
  1125. GError *err = NULL;
  1126. struct rspamc_callback_data *cbdata;
  1127. connectv = g_strsplit_set (connect_str, ":", -1);
  1128. if (connectv == NULL || connectv[0] == NULL) {
  1129. fprintf (stderr, "bad connect string: %s\n", connect_str);
  1130. exit (EXIT_FAILURE);
  1131. }
  1132. if (connectv[1] != NULL) {
  1133. port = strtoul (connectv[1], NULL, 10);
  1134. }
  1135. else if (*connectv[0] != '/') {
  1136. port = cmd->is_controller ? DEFAULT_CONTROL_PORT : DEFAULT_PORT;
  1137. }
  1138. else {
  1139. /* Unix socket */
  1140. port = 0;
  1141. }
  1142. conn = rspamd_client_init (ev_base, connectv[0], port, timeout, key);
  1143. g_strfreev (connectv);
  1144. if (conn != NULL) {
  1145. cbdata = g_slice_alloc (sizeof (struct rspamc_callback_data));
  1146. cbdata->cmd = cmd;
  1147. cbdata->filename = g_strdup (name);
  1148. cbdata->start = rspamd_get_ticks ();
  1149. if (cmd->need_input) {
  1150. rspamd_client_command (conn, cmd->path, attrs, in, rspamc_client_cb,
  1151. cbdata, &err);
  1152. }
  1153. else {
  1154. rspamd_client_command (conn,
  1155. cmd->path,
  1156. attrs,
  1157. NULL,
  1158. rspamc_client_cb,
  1159. cbdata,
  1160. &err);
  1161. }
  1162. }
  1163. }
  1164. static void
  1165. rspamc_process_dir (struct event_base *ev_base, struct rspamc_command *cmd,
  1166. const gchar *name, GQueue *attrs)
  1167. {
  1168. DIR *d;
  1169. gint cur_req = 0;
  1170. struct dirent *ent;
  1171. #if defined(__sun)
  1172. struct stat sb;
  1173. #endif
  1174. FILE *in;
  1175. char filebuf[PATH_MAX];
  1176. d = opendir (name);
  1177. if (d != NULL) {
  1178. while ((ent = readdir (d))) {
  1179. rspamd_snprintf (filebuf, sizeof (filebuf), "%s%c%s",
  1180. name, G_DIR_SEPARATOR, ent->d_name);
  1181. #if defined(__sun)
  1182. if (stat (filebuf, &sb)) continue;
  1183. if (S_ISREG (sb.st_mode)) {
  1184. #else
  1185. if (ent->d_type == DT_REG || ent->d_type == DT_UNKNOWN) {
  1186. #endif
  1187. if (access (filebuf, R_OK) != -1) {
  1188. in = fopen (filebuf, "r");
  1189. if (in == NULL) {
  1190. fprintf (stderr, "cannot open file %s\n", filebuf);
  1191. exit (EXIT_FAILURE);
  1192. }
  1193. rspamc_process_input (ev_base, cmd, in, filebuf, attrs);
  1194. cur_req++;
  1195. fclose (in);
  1196. if (cur_req >= max_requests) {
  1197. cur_req = 0;
  1198. /* Wait for completion */
  1199. event_base_loop (ev_base, 0);
  1200. }
  1201. }
  1202. }
  1203. }
  1204. }
  1205. else {
  1206. fprintf (stderr, "cannot open directory %s\n", name);
  1207. exit (EXIT_FAILURE);
  1208. }
  1209. closedir (d);
  1210. event_base_loop (ev_base, 0);
  1211. }
  1212. gint
  1213. main (gint argc, gchar **argv, gchar **env)
  1214. {
  1215. gint i, start_argc, cur_req = 0, res, ret;
  1216. GQueue *kwattrs;
  1217. GList *cur;
  1218. GPid cld;
  1219. struct rspamc_command *cmd;
  1220. FILE *in = NULL;
  1221. struct event_base *ev_base;
  1222. struct stat st;
  1223. struct sigaction sigpipe_act;
  1224. kwattrs = g_queue_new ();
  1225. read_cmd_line (&argc, &argv);
  1226. tty = isatty (STDOUT_FILENO);
  1227. if (print_commands) {
  1228. print_commands_list ();
  1229. exit (EXIT_SUCCESS);
  1230. }
  1231. rspamd_init_libs ();
  1232. ev_base = event_base_new ();
  1233. /* Ignore sigpipe */
  1234. sigemptyset (&sigpipe_act.sa_mask);
  1235. sigaddset (&sigpipe_act.sa_mask, SIGPIPE);
  1236. sigpipe_act.sa_handler = SIG_IGN;
  1237. sigpipe_act.sa_flags = 0;
  1238. sigaction (SIGPIPE, &sigpipe_act, NULL);
  1239. /* Now read other args from argc and argv */
  1240. if (argc == 1) {
  1241. start_argc = argc;
  1242. in = stdin;
  1243. cmd = check_rspamc_command ("symbols");
  1244. }
  1245. else if (argc == 2) {
  1246. /* One argument is whether command or filename */
  1247. if ((cmd = check_rspamc_command (argv[1])) != NULL) {
  1248. start_argc = argc;
  1249. in = stdin;
  1250. }
  1251. else {
  1252. cmd = check_rspamc_command ("symbols"); /* Symbols command */
  1253. start_argc = 1;
  1254. }
  1255. }
  1256. else {
  1257. if ((cmd = check_rspamc_command (argv[1])) != NULL) {
  1258. /* In case of command read arguments starting from 2 */
  1259. if (cmd->cmd == RSPAMC_COMMAND_ADD_SYMBOL || cmd->cmd ==
  1260. RSPAMC_COMMAND_ADD_ACTION) {
  1261. if (argc < 4 || argc > 5) {
  1262. fprintf (stderr, "invalid arguments\n");
  1263. exit (EXIT_FAILURE);
  1264. }
  1265. if (argc == 5) {
  1266. ADD_CLIENT_HEADER (kwattrs, "metric", argv[2]);
  1267. ADD_CLIENT_HEADER (kwattrs, "name", argv[3]);
  1268. ADD_CLIENT_HEADER (kwattrs, "value", argv[4]);
  1269. }
  1270. else {
  1271. ADD_CLIENT_HEADER (kwattrs, "name", argv[2]);
  1272. ADD_CLIENT_HEADER (kwattrs, "value", argv[3]);
  1273. }
  1274. start_argc = argc;
  1275. }
  1276. else {
  1277. start_argc = 2;
  1278. }
  1279. }
  1280. else {
  1281. cmd = check_rspamc_command ("symbols");
  1282. start_argc = 1;
  1283. }
  1284. }
  1285. add_options (kwattrs);
  1286. if (start_argc == argc) {
  1287. /* Do command without input or with stdin */
  1288. rspamc_process_input (ev_base, cmd, in, "stdin", kwattrs);
  1289. }
  1290. else {
  1291. for (i = start_argc; i < argc; i++) {
  1292. if (stat (argv[i], &st) == -1) {
  1293. fprintf (stderr, "cannot stat file %s\n", argv[i]);
  1294. exit (EXIT_FAILURE);
  1295. }
  1296. if (S_ISDIR (st.st_mode)) {
  1297. /* Directories are processed with a separate limit */
  1298. rspamc_process_dir (ev_base, cmd, argv[i], kwattrs);
  1299. cur_req = 0;
  1300. }
  1301. else {
  1302. in = fopen (argv[i], "r");
  1303. if (in == NULL) {
  1304. fprintf (stderr, "cannot open file %s\n", argv[i]);
  1305. exit (EXIT_FAILURE);
  1306. }
  1307. rspamc_process_input (ev_base, cmd, in, argv[i], kwattrs);
  1308. cur_req++;
  1309. fclose (in);
  1310. }
  1311. if (cur_req >= max_requests) {
  1312. cur_req = 0;
  1313. /* Wait for completion */
  1314. event_base_loop (ev_base, 0);
  1315. }
  1316. }
  1317. }
  1318. event_base_loop (ev_base, 0);
  1319. g_queue_free_full (kwattrs, g_free);
  1320. /* Wait for children processes */
  1321. cur = g_list_first (children);
  1322. ret = 0;
  1323. while (cur) {
  1324. cld = GPOINTER_TO_SIZE (cur->data);
  1325. if (waitpid (cld, &res, 0) == -1) {
  1326. fprintf (stderr, "Cannot wait for %d: %s", (gint)cld,
  1327. strerror (errno));
  1328. ret = errno;
  1329. }
  1330. if (ret == 0) {
  1331. /* Check return code */
  1332. if (WIFSIGNALED (res)) {
  1333. ret = WTERMSIG (res);
  1334. }
  1335. else if (WIFEXITED (res)) {
  1336. ret = WEXITSTATUS (res);
  1337. }
  1338. }
  1339. cur = g_list_next (cur);
  1340. }
  1341. if (children != NULL) {
  1342. g_list_free (children);
  1343. }
  1344. return ret;
  1345. }