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 "libutil/util.h"
18 #include "libserver/http/http_connection.h"
19 #include "libserver/http/http_private.h"
20 #include "libserver/cfg_file.h"
21 #include "rspamdclient.h"
22 #include "utlist.h"
23 #include "unix-std.h"
24 #ifdef HAVE_SYS_WAIT_H
25 #include <sys/wait.h>
26 #endif
27
28 #define DEFAULT_PORT 11333
29 #define DEFAULT_CONTROL_PORT 11334
30
31 static gchar *connect_str = "localhost";
32 static gchar *password = NULL;
33 static gchar *ip = NULL;
34 static gchar *from = NULL;
35 static gchar *deliver_to = NULL;
36 static gchar **rcpts = NULL;
37 static gchar *user = NULL;
38 static gchar *helo = NULL;
39 static gchar *hostname = NULL;
40 static gchar *classifier = NULL;
41 static gchar *local_addr = NULL;
42 static gchar *execute = NULL;
43 static gchar *sort = NULL;
44 static gchar **http_headers = NULL;
45 static gchar **exclude_patterns = NULL;
46 static gint weight = 0;
47 static gint flag = 0;
48 static gchar *fuzzy_symbol = NULL;
49 static gchar *dictionary = NULL;
50 static gint max_requests = 8;
51 static gdouble timeout = 10.0;
52 static gboolean pass_all;
53 static gboolean tty = FALSE;
54 static gboolean verbose = FALSE;
55 static gboolean print_commands = FALSE;
56 static gboolean json = FALSE;
57 static gboolean compact = FALSE;
58 static gboolean headers = FALSE;
59 static gboolean raw = FALSE;
60 static gboolean extended_urls = FALSE;
61 static gboolean mime_output = FALSE;
62 static gboolean empty_input = FALSE;
63 static gboolean compressed = FALSE;
64 static gboolean profile = FALSE;
65 static gboolean skip_images = FALSE;
66 static gboolean skip_attachments = FALSE;
67 static gchar *key = NULL;
68 static gchar *user_agent = "rspamc";
69 static GList *children;
70 static GPatternSpec **exclude_compiled = NULL;
71 static struct rspamd_http_context *http_ctx;
72
73 static gint retcode = EXIT_SUCCESS;
74
75 #define ADD_CLIENT_HEADER(o, n, v) do { \
76 struct rspamd_http_client_header *nh; \
77 nh = g_malloc (sizeof (*nh)); \
78 nh->name = g_strdup (n); \
79 nh->value = g_strdup (v); \
80 g_queue_push_tail ((o), nh); \
81 } while (0)
82
83 #define ADD_CLIENT_FLAG(str, n) do { \
84 g_string_append ((str), n ","); \
85 } while (0)
86
87 static gboolean rspamc_password_callback (const gchar *option_name,
88 const gchar *value,
89 gpointer data,
90 GError **error);
91
92 static GOptionEntry entries[] =
93 {
94 { "connect", 'h', 0, G_OPTION_ARG_STRING, &connect_str,
95 "Specify host and port", NULL },
96 { "password", 'P', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
97 &rspamc_password_callback, "Specify control password", NULL },
98 { "classifier", 'c', 0, G_OPTION_ARG_STRING, &classifier,
99 "Classifier to learn spam or ham", NULL },
100 { "weight", 'w', 0, G_OPTION_ARG_INT, &weight,
101 "Weight for fuzzy operations", NULL },
102 { "flag", 'f', 0, G_OPTION_ARG_INT, &flag, "Flag for fuzzy operations",
103 NULL },
104 { "pass-all", 'p', 0, G_OPTION_ARG_NONE, &pass_all, "Pass all filters",
105 NULL },
106 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "More verbose output",
107 NULL },
108 { "ip", 'i', 0, G_OPTION_ARG_STRING, &ip,
109 "Emulate that message was received from specified ip address",
110 NULL },
111 { "user", 'u', 0, G_OPTION_ARG_STRING, &user,
112 "Emulate that message was received from specified authenticated user", NULL },
113 { "deliver", 'd', 0, G_OPTION_ARG_STRING, &deliver_to,
114 "Emulate that message is delivered to specified user (for LDA/statistics)", NULL },
115 { "from", 'F', 0, G_OPTION_ARG_STRING, &from,
116 "Emulate that message has specified SMTP FROM address", NULL },
117 { "rcpt", 'r', 0, G_OPTION_ARG_STRING_ARRAY, &rcpts,
118 "Emulate that message has specified SMTP RCPT address", NULL },
119 { "helo", 0, 0, G_OPTION_ARG_STRING, &helo,
120 "Imitate SMTP HELO passing from MTA", NULL },
121 { "hostname", 0, 0, G_OPTION_ARG_STRING, &hostname,
122 "Imitate hostname passing from MTA", NULL },
123 { "timeout", 't', 0, G_OPTION_ARG_DOUBLE, &timeout,
124 "Time in seconds to wait for a reply", NULL },
125 { "bind", 'b', 0, G_OPTION_ARG_STRING, &local_addr,
126 "Bind to specified ip address", NULL },
127 { "commands", 0, 0, G_OPTION_ARG_NONE, &print_commands,
128 "List available commands", NULL },
129 { "json", 'j', 0, G_OPTION_ARG_NONE, &json, "Output json reply", NULL },
130 { "compact", '\0', 0, G_OPTION_ARG_NONE, &compact, "Output compact json reply", NULL},
131 { "headers", 0, 0, G_OPTION_ARG_NONE, &headers, "Output HTTP headers",
132 NULL },
133 { "raw", 0, 0, G_OPTION_ARG_NONE, &raw, "Output raw reply from rspamd",
134 NULL },
135 { "ucl", 0, 0, G_OPTION_ARG_NONE, &raw, "Output ucl reply from rspamd",
136 NULL },
137 { "max-requests", 'n', 0, G_OPTION_ARG_INT, &max_requests,
138 "Maximum count of parallel requests to rspamd", NULL },
139 { "extended-urls", 0, 0, G_OPTION_ARG_NONE, &extended_urls,
140 "Output urls in extended format", NULL },
141 { "key", 0, 0, G_OPTION_ARG_STRING, &key,
142 "Use specified pubkey to encrypt request", NULL },
143 { "exec", 'e', 0, G_OPTION_ARG_STRING, &execute,
144 "Execute the specified command and pass output to it", NULL },
145 { "mime", 'm', 0, G_OPTION_ARG_NONE, &mime_output,
146 "Write mime body of message with headers instead of just a scan's result", NULL },
147 {"header", 0, 0, G_OPTION_ARG_STRING_ARRAY, &http_headers,
148 "Add custom HTTP header to query (can be repeated)", NULL},
149 {"exclude", 0, 0, G_OPTION_ARG_STRING_ARRAY, &exclude_patterns,
150 "Exclude specific glob patterns in file names (can be repeated)", NULL},
151 {"sort", 0, 0, G_OPTION_ARG_STRING, &sort,
152 "Sort output in a specific order (name, weight, frequency, hits)", NULL},
153 { "empty", 'E', 0, G_OPTION_ARG_NONE, &empty_input,
154 "Allow empty input instead of reading from stdin", NULL },
155 { "fuzzy-symbol", 'S', 0, G_OPTION_ARG_STRING, &fuzzy_symbol,
156 "Learn the specified fuzzy symbol", NULL },
157 { "compressed", 'z', 0, G_OPTION_ARG_NONE, &compressed,
158 "Enable zstd compression", NULL },
159 { "profile", '\0', 0, G_OPTION_ARG_NONE, &profile,
160 "Profile symbols execution time", NULL },
161 { "dictionary", 'D', 0, G_OPTION_ARG_FILENAME, &dictionary,
162 "Use dictionary to compress data", NULL },
163 { "skip-images", '\0', 0, G_OPTION_ARG_NONE, &skip_images,
164 "Skip images when learning/unlearning fuzzy", NULL },
165 { "skip-attachments", '\0', 0, G_OPTION_ARG_NONE, &skip_attachments,
166 "Skip attachments when learning/unlearning fuzzy", NULL },
167 { "user-agent", 'U', 0, G_OPTION_ARG_STRING, &user_agent,
168 "Use specific User-Agent instead of \"rspamc\"", NULL },
169 { NULL, 0, 0, G_OPTION_ARG_NONE, NULL, NULL, NULL }
170 };
171
172 static void rspamc_symbols_output (FILE *out, ucl_object_t *obj);
173 static void rspamc_uptime_output (FILE *out, ucl_object_t *obj);
174 static void rspamc_counters_output (FILE *out, ucl_object_t *obj);
175 static void rspamc_stat_output (FILE *out, ucl_object_t *obj);
176
177 enum rspamc_command_type {
178 RSPAMC_COMMAND_UNKNOWN = 0,
179 RSPAMC_COMMAND_CHECK,
180 RSPAMC_COMMAND_SYMBOLS,
181 RSPAMC_COMMAND_LEARN_SPAM,
182 RSPAMC_COMMAND_LEARN_HAM,
183 RSPAMC_COMMAND_FUZZY_ADD,
184 RSPAMC_COMMAND_FUZZY_DEL,
185 RSPAMC_COMMAND_FUZZY_DELHASH,
186 RSPAMC_COMMAND_STAT,
187 RSPAMC_COMMAND_STAT_RESET,
188 RSPAMC_COMMAND_COUNTERS,
189 RSPAMC_COMMAND_UPTIME,
190 RSPAMC_COMMAND_ADD_SYMBOL,
191 RSPAMC_COMMAND_ADD_ACTION
192 };
193
194 struct rspamc_command {
195 enum rspamc_command_type cmd;
196 const char *name;
197 const char *description;
198 const char *path;
199 gboolean is_controller;
200 gboolean is_privileged;
201 gboolean need_input;
202 void (*command_output_func)(FILE *, ucl_object_t *obj);
203 } rspamc_commands[] = {
204 {
205 .cmd = RSPAMC_COMMAND_SYMBOLS,
206 .name = "symbols",
207 .path = "checkv2",
208 .description = "scan message and show symbols (default command)",
209 .is_controller = FALSE,
210 .is_privileged = FALSE,
211 .need_input = TRUE,
212 .command_output_func = rspamc_symbols_output
213 },
214 {
215 .cmd = RSPAMC_COMMAND_LEARN_SPAM,
216 .name = "learn_spam",
217 .path = "learnspam",
218 .description = "learn message as spam",
219 .is_controller = TRUE,
220 .is_privileged = TRUE,
221 .need_input = TRUE,
222 .command_output_func = NULL
223 },
224 {
225 .cmd = RSPAMC_COMMAND_LEARN_HAM,
226 .name = "learn_ham",
227 .path = "learnham",
228 .description = "learn message as ham",
229 .is_controller = TRUE,
230 .is_privileged = TRUE,
231 .need_input = TRUE,
232 .command_output_func = NULL
233 },
234 {
235 .cmd = RSPAMC_COMMAND_FUZZY_ADD,
236 .name = "fuzzy_add",
237 .path = "fuzzyadd",
238 .description =
239 "add hashes from a message to the fuzzy storage (check -f and -w options for this command)",
240 .is_controller = TRUE,
241 .is_privileged = TRUE,
242 .need_input = TRUE,
243 .command_output_func = NULL
244 },
245 {
246 .cmd = RSPAMC_COMMAND_FUZZY_DEL,
247 .name = "fuzzy_del",
248 .path = "fuzzydel",
249 .description =
250 "delete hashes from a message from the fuzzy storage (check -f option for this command)",
251 .is_controller = TRUE,
252 .is_privileged = TRUE,
253 .need_input = TRUE,
254 .command_output_func = NULL
255 },
256 {
257 .cmd = RSPAMC_COMMAND_FUZZY_DELHASH,
258 .name = "fuzzy_delhash",
259 .path = "fuzzydelhash",
260 .description =
261 "delete a hash from fuzzy storage (check -f option for this command)",
262 .is_controller = TRUE,
263 .is_privileged = TRUE,
264 .need_input = FALSE,
265 .command_output_func = NULL
266 },
267 {
268 .cmd = RSPAMC_COMMAND_STAT,
269 .name = "stat",
270 .path = "stat",
271 .description = "show rspamd statistics",
272 .is_controller = TRUE,
273 .is_privileged = FALSE,
274 .need_input = FALSE,
275 .command_output_func = rspamc_stat_output,
276 },
277 {
278 .cmd = RSPAMC_COMMAND_STAT_RESET,
279 .name = "stat_reset",
280 .path = "statreset",
281 .description = "show and reset rspamd statistics (useful for graphs)",
282 .is_controller = TRUE,
283 .is_privileged = TRUE,
284 .need_input = FALSE,
285 .command_output_func = rspamc_stat_output
286 },
287 {
288 .cmd = RSPAMC_COMMAND_COUNTERS,
289 .name = "counters",
290 .path = "counters",
291 .description = "display rspamd symbols statistics",
292 .is_controller = TRUE,
293 .is_privileged = FALSE,
294 .need_input = FALSE,
295 .command_output_func = rspamc_counters_output
296 },
297 {
298 .cmd = RSPAMC_COMMAND_UPTIME,
299 .name = "uptime",
300 .path = "auth",
301 .description = "show rspamd uptime",
302 .is_controller = TRUE,
303 .is_privileged = FALSE,
304 .need_input = FALSE,
305 .command_output_func = rspamc_uptime_output
306 },
307 {
308 .cmd = RSPAMC_COMMAND_ADD_SYMBOL,
309 .name = "add_symbol",
310 .path = "addsymbol",
311 .description = "add or modify symbol settings in rspamd",
312 .is_controller = TRUE,
313 .is_privileged = TRUE,
314 .need_input = FALSE,
315 .command_output_func = NULL
316 },
317 {
318 .cmd = RSPAMC_COMMAND_ADD_ACTION,
319 .name = "add_action",
320 .path = "addaction",
321 .description = "add or modify action settings",
322 .is_controller = TRUE,
323 .is_privileged = TRUE,
324 .need_input = FALSE,
325 .command_output_func = NULL
326 }
327 };
328
329 struct rspamc_callback_data {
330 struct rspamc_command *cmd;
331 gchar *filename;
332 };
333
334 gboolean
rspamc_password_callback(const gchar * option_name,const gchar * value,gpointer data,GError ** error)335 rspamc_password_callback (const gchar *option_name,
336 const gchar *value,
337 gpointer data,
338 GError **error)
339 {
340 guint plen = 8192;
341 guint8 *map, *end;
342 gsize sz;
343
344 if (value != NULL) {
345 if (value[0] == '/' || value[0] == '.') {
346 /* Try to open file */
347 map = rspamd_file_xmap (value, PROT_READ, &sz, 0);
348
349 if (map == NULL) {
350 /* Just use it as a string */
351 password = g_strdup (value);
352 }
353 else {
354 /* Strip trailing spaces */
355 g_assert (sz > 0);
356 end = map + sz - 1;
357
358 while (g_ascii_isspace (*end) && end > map) {
359 end --;
360 }
361
362 end ++;
363 password = g_malloc (end - map + 1);
364 rspamd_strlcpy (password, map, end - map + 1);
365 munmap (map, sz);
366 }
367 }
368 else {
369 password = g_strdup (value);
370 }
371 }
372 else {
373 /* Read password from console */
374 password = g_malloc0 (plen);
375 plen = rspamd_read_passphrase (password, plen, 0, NULL);
376 }
377
378 if (plen == 0) {
379 rspamd_fprintf (stderr, "Invalid password\n");
380 exit (EXIT_FAILURE);
381 }
382
383 return TRUE;
384 }
385
386 /*
387 * Parse command line
388 */
389 static void
read_cmd_line(gint * argc,gchar *** argv)390 read_cmd_line (gint *argc, gchar ***argv)
391 {
392 GError *error = NULL;
393 GOptionContext *context;
394
395 /* Prepare parser */
396 context = g_option_context_new ("- run rspamc client");
397 g_option_context_set_summary (context,
398 "Summary:\n Rspamd client version " RVERSION "\n Release id: " RID);
399 g_option_context_add_main_entries (context, entries, NULL);
400
401 /* Parse options */
402 if (!g_option_context_parse (context, argc, argv, &error)) {
403 fprintf (stderr, "option parsing failed: %s\n", error->message);
404 g_option_context_free (context);
405 exit (EXIT_FAILURE);
406 }
407
408 if (json || compact) {
409 raw = TRUE;
410 }
411 /* Argc and argv are shifted after this function */
412 g_option_context_free (context);
413 }
414
415 static gboolean
rspamd_action_from_str_rspamc(const gchar * data,gint * result)416 rspamd_action_from_str_rspamc (const gchar *data, gint *result)
417 {
418 if (strcmp (data, "reject") == 0) {
419 *result = METRIC_ACTION_REJECT;
420 }
421 else if (strcmp (data, "greylist") == 0) {
422 *result = METRIC_ACTION_GREYLIST;
423 }
424 else if (strcmp (data, "add_header") == 0) {
425 *result = METRIC_ACTION_ADD_HEADER;
426 }
427 else if (strcmp (data, "rewrite_subject") == 0) {
428 *result = METRIC_ACTION_REWRITE_SUBJECT;
429 }
430 else if (strcmp (data, "add header") == 0) {
431 *result = METRIC_ACTION_ADD_HEADER;
432 }
433 else if (strcmp (data, "rewrite subject") == 0) {
434 *result = METRIC_ACTION_REWRITE_SUBJECT;
435 }
436 else if (strcmp (data, "soft_reject") == 0) {
437 *result = METRIC_ACTION_SOFT_REJECT;
438 }
439 else if (strcmp (data, "soft reject") == 0) {
440 *result = METRIC_ACTION_SOFT_REJECT;
441 }
442 else if (strcmp (data, "no_action") == 0) {
443 *result = METRIC_ACTION_NOACTION;
444 }
445 else if (strcmp (data, "no action") == 0) {
446 *result = METRIC_ACTION_NOACTION;
447 }
448 else {
449 return FALSE;
450 }
451 return TRUE;
452 }
453
454 /*
455 * Check rspamc command from string (used for arguments parsing)
456 */
457 static struct rspamc_command *
check_rspamc_command(const gchar * cmd)458 check_rspamc_command (const gchar *cmd)
459 {
460 enum rspamc_command_type ct = 0;
461 guint i;
462
463 if (g_ascii_strcasecmp (cmd, "SYMBOLS") == 0 ||
464 g_ascii_strcasecmp (cmd, "CHECK") == 0 ||
465 g_ascii_strcasecmp (cmd, "REPORT") == 0) {
466 /* These all are symbols, don't use other commands */
467 ct = RSPAMC_COMMAND_SYMBOLS;
468 }
469 else if (g_ascii_strcasecmp (cmd, "LEARN_SPAM") == 0) {
470 ct = RSPAMC_COMMAND_LEARN_SPAM;
471 }
472 else if (g_ascii_strcasecmp (cmd, "LEARN_HAM") == 0) {
473 ct = RSPAMC_COMMAND_LEARN_HAM;
474 }
475 else if (g_ascii_strcasecmp (cmd, "FUZZY_ADD") == 0) {
476 ct = RSPAMC_COMMAND_FUZZY_ADD;
477 }
478 else if (g_ascii_strcasecmp (cmd, "FUZZY_DEL") == 0) {
479 ct = RSPAMC_COMMAND_FUZZY_DEL;
480 }
481 else if (g_ascii_strcasecmp (cmd, "FUZZY_DELHASH") == 0) {
482 ct = RSPAMC_COMMAND_FUZZY_DELHASH;
483 }
484 else if (g_ascii_strcasecmp (cmd, "STAT") == 0) {
485 ct = RSPAMC_COMMAND_STAT;
486 }
487 else if (g_ascii_strcasecmp (cmd, "STAT_RESET") == 0) {
488 ct = RSPAMC_COMMAND_STAT_RESET;
489 }
490 else if (g_ascii_strcasecmp (cmd, "COUNTERS") == 0) {
491 ct = RSPAMC_COMMAND_COUNTERS;
492 }
493 else if (g_ascii_strcasecmp (cmd, "UPTIME") == 0) {
494 ct = RSPAMC_COMMAND_UPTIME;
495 }
496 else if (g_ascii_strcasecmp (cmd, "ADD_SYMBOL") == 0) {
497 ct = RSPAMC_COMMAND_ADD_SYMBOL;
498 }
499 else if (g_ascii_strcasecmp (cmd, "ADD_ACTION") == 0) {
500 ct = RSPAMC_COMMAND_ADD_ACTION;
501 }
502
503 for (i = 0; i < G_N_ELEMENTS (rspamc_commands); i++) {
504 if (rspamc_commands[i].cmd == ct) {
505 return &rspamc_commands[i];
506 }
507 }
508
509 return NULL;
510 }
511
512 static void
print_commands_list(void)513 print_commands_list (void)
514 {
515 guint i;
516 guint cmd_len = 0;
517 gchar fmt_str[32];
518
519 rspamd_fprintf (stdout, "Rspamc commands summary:\n");
520
521 for (i = 0; i < G_N_ELEMENTS (rspamc_commands); i++) {
522 gsize clen = strlen (rspamc_commands[i].name);
523
524 if (clen > cmd_len) {
525 cmd_len = clen;
526 }
527 }
528
529 rspamd_snprintf (fmt_str, sizeof (fmt_str), " %%%ds (%%7s%%1s)\t%%s\n",
530 cmd_len);
531
532 for (i = 0; i < G_N_ELEMENTS (rspamc_commands); i++) {
533 fprintf (stdout,
534 fmt_str,
535 rspamc_commands[i].name,
536 rspamc_commands[i].is_controller ? "control" : "normal",
537 rspamc_commands[i].is_privileged ? "*" : "",
538 rspamc_commands[i].description);
539 }
540
541 rspamd_fprintf (stdout,
542 "\n* is for privileged commands that may need password (see -P option)\n");
543 rspamd_fprintf (stdout,
544 "control commands use port 11334 while normal use 11333 by default (see -h option)\n");
545 }
546
547 static void
add_options(GQueue * opts)548 add_options (GQueue *opts)
549 {
550 GString *numbuf;
551 gchar **hdr, **rcpt;
552 GString *flagbuf = g_string_new (NULL);
553
554 if (ip != NULL) {
555 rspamd_inet_addr_t *addr = NULL;
556
557 if (!rspamd_parse_inet_address (&addr, ip, strlen (ip),
558 RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
559 /* Try to resolve */
560 struct addrinfo hints, *res, *cur;
561 gint r;
562
563 memset (&hints, 0, sizeof (hints));
564 hints.ai_socktype = SOCK_STREAM; /* Type of the socket */
565 #ifdef AI_IDN
566 hints.ai_flags = AI_NUMERICSERV|AI_IDN;
567 #else
568 hints.ai_flags = AI_NUMERICSERV;
569 #endif
570 hints.ai_family = AF_UNSPEC;
571
572 if ((r = getaddrinfo (ip, "25", &hints, &res)) == 0) {
573
574 cur = res;
575 while (cur) {
576 addr = rspamd_inet_address_from_sa (cur->ai_addr,
577 cur->ai_addrlen);
578
579 if (addr != NULL) {
580 ip = g_strdup (rspamd_inet_address_to_string (addr));
581 rspamd_inet_address_free (addr);
582 break;
583 }
584
585 cur = cur->ai_next;
586 }
587
588 freeaddrinfo (res);
589 }
590 else {
591 rspamd_fprintf (stderr, "address resolution for %s failed: %s\n",
592 ip,
593 gai_strerror (r));
594 }
595 }
596 else {
597 rspamd_inet_address_free (addr);
598 }
599
600 ADD_CLIENT_HEADER (opts, "Ip", ip);
601 }
602
603 if (from != NULL) {
604 ADD_CLIENT_HEADER (opts, "From", from);
605 }
606
607 if (user != NULL) {
608 ADD_CLIENT_HEADER (opts, "User", user);
609 }
610
611 if (rcpts != NULL) {
612
613 for (rcpt = rcpts; *rcpt != NULL; rcpt ++) {
614 ADD_CLIENT_HEADER (opts, "Rcpt", *rcpt);
615 }
616 }
617
618 if (deliver_to != NULL) {
619 ADD_CLIENT_HEADER (opts, "Deliver-To", deliver_to);
620 }
621
622 if (helo != NULL) {
623 ADD_CLIENT_HEADER (opts, "Helo", helo);
624 }
625
626 if (hostname != NULL) {
627 ADD_CLIENT_HEADER (opts, "Hostname", hostname);
628 }
629
630 if (password != NULL) {
631 ADD_CLIENT_HEADER (opts, "Password", password);
632 }
633
634 if (pass_all) {
635 ADD_CLIENT_FLAG (flagbuf, "pass_all");
636 }
637
638 if (classifier) {
639 ADD_CLIENT_HEADER (opts, "Classifier", classifier);
640 }
641
642 if (weight != 0) {
643 numbuf = g_string_sized_new (8);
644 rspamd_printf_gstring (numbuf, "%d", weight);
645 ADD_CLIENT_HEADER (opts, "Weight", numbuf->str);
646 g_string_free (numbuf, TRUE);
647 }
648
649 if (fuzzy_symbol != NULL) {
650 ADD_CLIENT_HEADER (opts, "Symbol", fuzzy_symbol);
651 }
652
653 if (flag != 0) {
654 numbuf = g_string_sized_new (8);
655 rspamd_printf_gstring (numbuf, "%d", flag);
656 ADD_CLIENT_HEADER (opts, "Flag", numbuf->str);
657 g_string_free (numbuf, TRUE);
658 }
659
660 if (extended_urls) {
661 ADD_CLIENT_HEADER (opts, "URL-Format", "extended");
662 }
663
664 if (profile) {
665 ADD_CLIENT_FLAG (flagbuf, "profile");
666 }
667
668 ADD_CLIENT_FLAG (flagbuf, "body_block");
669
670 if (skip_images) {
671 ADD_CLIENT_HEADER (opts, "Skip-Images", "true");
672 }
673
674 if (skip_attachments) {
675 ADD_CLIENT_HEADER (opts, "Skip-Attachments", "true");
676 }
677
678 hdr = http_headers;
679
680 while (hdr != NULL && *hdr != NULL) {
681 gchar **kv = g_strsplit_set (*hdr, ":=", 2);
682
683 if (kv == NULL || kv[1] == NULL) {
684 ADD_CLIENT_HEADER (opts, *hdr, "");
685 }
686 else {
687 ADD_CLIENT_HEADER (opts, kv[0], kv[1]);
688 }
689
690 if (kv) {
691 g_strfreev (kv);
692 }
693
694 hdr ++;
695 }
696
697 if (flagbuf->len > 0) {
698 goffset last = flagbuf->len - 1;
699
700 if (flagbuf->str[last] == ',') {
701 flagbuf->str[last] = '\0';
702 flagbuf->len --;
703 }
704
705 ADD_CLIENT_HEADER (opts, "Flags", flagbuf->str);
706 }
707
708 g_string_free (flagbuf, TRUE);
709 }
710
711 static void
rspamc_symbol_output(FILE * out,const ucl_object_t * obj)712 rspamc_symbol_output (FILE *out, const ucl_object_t *obj)
713 {
714 const ucl_object_t *val, *cur;
715 ucl_object_iter_t it = NULL;
716 gboolean first = TRUE;
717
718 rspamd_fprintf (out, "Symbol: %s ", ucl_object_key (obj));
719 val = ucl_object_lookup (obj, "score");
720
721 if (val != NULL) {
722 rspamd_fprintf (out, "(%.2f)", ucl_object_todouble (val));
723 }
724 val = ucl_object_lookup (obj, "options");
725 if (val != NULL && val->type == UCL_ARRAY) {
726 rspamd_fprintf (out, "[");
727
728 while ((cur = ucl_object_iterate (val, &it, TRUE)) != NULL) {
729 if (first) {
730 rspamd_fprintf (out, "%s", ucl_object_tostring (cur));
731 first = FALSE;
732 }
733 else {
734 rspamd_fprintf (out, ", %s", ucl_object_tostring (cur));
735 }
736 }
737 rspamd_fprintf (out, "]");
738 }
739 rspamd_fprintf (out, "\n");
740 }
741
742 static gint
rspamc_symbols_sort_func(gconstpointer a,gconstpointer b)743 rspamc_symbols_sort_func (gconstpointer a, gconstpointer b)
744 {
745 ucl_object_t * const *ua = a, * const *ub = b;
746
747 return strcmp (ucl_object_key (*ua), ucl_object_key (*ub));
748 }
749
750 #define PRINT_PROTOCOL_STRING(ucl_name, output_message) do { \
751 elt = ucl_object_lookup (obj, (ucl_name)); \
752 if (elt) { \
753 rspamd_fprintf (out, output_message ": %s\n", ucl_object_tostring (elt)); \
754 } \
755 } while (0)
756
757 static void
rspamc_metric_output(FILE * out,const ucl_object_t * obj)758 rspamc_metric_output (FILE *out, const ucl_object_t *obj)
759 {
760 ucl_object_iter_t it = NULL;
761 const ucl_object_t *cur, *elt;
762 gdouble score = 0, required_score = 0;
763 gint got_scores = 0, action = METRIC_ACTION_MAX;
764 GPtrArray *sym_ptr;
765 guint i;
766
767 sym_ptr = g_ptr_array_new ();
768 rspamd_fprintf (out, "[Metric: default]\n");
769
770 elt = ucl_object_lookup (obj, "required_score");
771
772 if (elt) {
773 required_score = ucl_object_todouble (elt);
774 got_scores++;
775 }
776
777 elt = ucl_object_lookup (obj, "score");
778
779 if (elt) {
780 score = ucl_object_todouble (elt);
781 got_scores++;
782 }
783
784 PRINT_PROTOCOL_STRING ("action", "Action");
785 /* Defined by previous macro */
786 if (elt && rspamd_action_from_str_rspamc (ucl_object_tostring (elt), &action)) {
787 rspamd_fprintf (out, "Spam: %s\n", action < METRIC_ACTION_GREYLIST ?
788 "true" : "false");
789 }
790
791 PRINT_PROTOCOL_STRING ("subject", "Subject");
792
793 if (got_scores == 2) {
794 rspamd_fprintf (out,
795 "Score: %.2f / %.2f\n",
796 score,
797 required_score);
798 }
799
800 elt = ucl_object_lookup (obj, "symbols");
801
802 while (elt && (cur = ucl_object_iterate (elt, &it, true)) != NULL) {
803 if (cur->type == UCL_OBJECT) {
804 g_ptr_array_add (sym_ptr, (void *)cur);
805 }
806 }
807
808 g_ptr_array_sort (sym_ptr, rspamc_symbols_sort_func);
809
810 for (i = 0; i < sym_ptr->len; i ++) {
811 cur = (const ucl_object_t *)g_ptr_array_index (sym_ptr, i);
812 rspamc_symbol_output (out, cur);
813 }
814
815 g_ptr_array_free (sym_ptr, TRUE);
816 }
817
818 static gint
rspamc_profile_sort_func(gconstpointer a,gconstpointer b)819 rspamc_profile_sort_func (gconstpointer a, gconstpointer b)
820 {
821 ucl_object_t * const *ua = a, * const *ub = b;
822
823 return ucl_object_compare (*ua, *ub);
824 }
825
826 static void
rspamc_profile_output(FILE * out,const ucl_object_t * obj)827 rspamc_profile_output (FILE *out, const ucl_object_t *obj)
828 {
829 ucl_object_iter_t it = NULL;
830 const ucl_object_t *cur;
831 guint i;
832 GPtrArray *ar;
833
834 ar = g_ptr_array_sized_new (obj->len);
835
836 while ((cur = ucl_object_iterate (obj, &it, true)) != NULL) {
837 g_ptr_array_add (ar, (void *)cur);
838 }
839
840 g_ptr_array_sort (ar, rspamc_profile_sort_func);
841
842 for (i = 0; i < ar->len; i ++) {
843 cur = (const ucl_object_t *)g_ptr_array_index (ar, i);
844 rspamd_fprintf (out, "\t%s: %.3f usec\n",
845 ucl_object_key (cur), ucl_object_todouble (cur));
846 }
847
848 g_ptr_array_free (ar, TRUE);
849 }
850
851 static void
rspamc_symbols_output(FILE * out,ucl_object_t * obj)852 rspamc_symbols_output (FILE *out, ucl_object_t *obj)
853 {
854 ucl_object_iter_t mit = NULL;
855 const ucl_object_t *cmesg, *elt;
856 gchar *emitted;
857
858 rspamc_metric_output (out, obj);
859
860 PRINT_PROTOCOL_STRING ("message-id", "Message-ID");
861 PRINT_PROTOCOL_STRING ("queue-id", "Queue-ID");
862
863 elt = ucl_object_lookup (obj, "urls");
864
865 if (elt) {
866 if (!extended_urls || compact) {
867 emitted = ucl_object_emit (elt, UCL_EMIT_JSON_COMPACT);
868 }
869 else {
870 emitted = ucl_object_emit (elt, UCL_EMIT_JSON);
871 }
872
873 rspamd_fprintf (out, "Urls: %s\n", emitted);
874 free (emitted);
875 }
876
877 elt = ucl_object_lookup (obj, "emails");
878
879 if (elt) {
880 if (!extended_urls || compact) {
881 emitted = ucl_object_emit (elt, UCL_EMIT_JSON_COMPACT);
882 }
883 else {
884 emitted = ucl_object_emit (elt, UCL_EMIT_JSON);
885 }
886
887 rspamd_fprintf (out, "Emails: %s\n", emitted);
888 free (emitted);
889 }
890
891 PRINT_PROTOCOL_STRING ("error", "Scan error");
892
893 elt = ucl_object_lookup (obj, "messages");
894 if (elt && elt->type == UCL_OBJECT) {
895 mit = NULL;
896 while ((cmesg = ucl_object_iterate (elt, &mit, true)) != NULL) {
897 rspamd_fprintf (out, "Message - %s: %s\n",
898 ucl_object_key (cmesg), ucl_object_tostring (cmesg));
899 }
900 }
901
902 elt = ucl_object_lookup (obj, "dkim-signature");
903 if (elt && elt->type == UCL_STRING) {
904 rspamd_fprintf (out, "DKIM-Signature: %s\n", ucl_object_tostring (elt));
905 } else if (elt && elt->type == UCL_ARRAY) {
906 mit = NULL;
907 while ((cmesg = ucl_object_iterate (elt, &mit, true)) != NULL) {
908 rspamd_fprintf (out, "DKIM-Signature: %s\n", ucl_object_tostring (cmesg));
909 }
910 }
911
912 elt = ucl_object_lookup (obj, "profile");
913
914 if (elt) {
915 rspamd_fprintf (out, "Profile data:\n");
916 rspamc_profile_output (out, elt);
917 }
918 }
919
920 static void
rspamc_uptime_output(FILE * out,ucl_object_t * obj)921 rspamc_uptime_output (FILE *out, ucl_object_t *obj)
922 {
923 const ucl_object_t *elt;
924 int64_t seconds, days, hours, minutes;
925
926 elt = ucl_object_lookup (obj, "version");
927 if (elt != NULL) {
928 rspamd_fprintf (out, "Rspamd version: %s\n", ucl_object_tostring (
929 elt));
930 }
931
932 elt = ucl_object_lookup (obj, "uptime");
933 if (elt != NULL) {
934 rspamd_printf ("Uptime: ");
935 seconds = ucl_object_toint (elt);
936 if (seconds >= 2 * 3600) {
937 days = seconds / 86400;
938 hours = seconds / 3600 - days * 24;
939 minutes = seconds / 60 - hours * 60 - days * 1440;
940 rspamd_printf ("%L day%s %L hour%s %L minute%s\n", days,
941 days > 1 ? "s" : "", hours, hours > 1 ? "s" : "",
942 minutes, minutes > 1 ? "s" : "");
943 }
944 /* If uptime is less than 1 minute print only seconds */
945 else if (seconds / 60 == 0) {
946 rspamd_printf ("%L second%s\n", seconds,
947 (gint)seconds > 1 ? "s" : "");
948 }
949 /* Else print the minutes and seconds. */
950 else {
951 hours = seconds / 3600;
952 minutes = seconds / 60 - hours * 60;
953 seconds -= hours * 3600 + minutes * 60;
954 rspamd_printf ("%L hour %L minute%s %L second%s\n", hours,
955 minutes, minutes > 1 ? "s" : "",
956 seconds, seconds > 1 ? "s" : "");
957 }
958 }
959 }
960
961 static gint
rspamc_counters_sort(const ucl_object_t ** o1,const ucl_object_t ** o2)962 rspamc_counters_sort (const ucl_object_t **o1, const ucl_object_t **o2)
963 {
964 gint order1 = 0, order2 = 0, c;
965 const ucl_object_t *elt1, *elt2;
966 gboolean inverse = FALSE;
967 gchar **args;
968
969 if (sort != NULL) {
970 args = g_strsplit_set (sort, ":", 2);
971 if (args && args[0]) {
972 if (args[1] && g_ascii_strcasecmp (args[1], "desc") == 0) {
973 inverse = TRUE;
974 }
975
976 if (g_ascii_strcasecmp (args[0], "name") == 0) {
977 elt1 = ucl_object_lookup (*o1, "symbol");
978 elt2 = ucl_object_lookup (*o2, "symbol");
979
980 if (elt1 && elt2) {
981 c = strcmp (ucl_object_tostring (elt1),
982 ucl_object_tostring (elt2));
983
984 order1 = c > 0 ? 1 : 0;
985 order2 = c < 0 ? 1 : 0;
986 }
987 }
988 else if (g_ascii_strcasecmp (args[0], "weight") == 0) {
989 elt1 = ucl_object_lookup (*o1, "weight");
990 elt2 = ucl_object_lookup (*o2, "weight");
991
992 if (elt1 && elt2) {
993 order1 = ucl_object_todouble (elt1) * 1000.0;
994 order2 = ucl_object_todouble (elt2) * 1000.0;
995 }
996 }
997 else if (g_ascii_strcasecmp (args[0], "frequency") == 0) {
998 elt1 = ucl_object_lookup (*o1, "frequency");
999 elt2 = ucl_object_lookup (*o2, "frequency");
1000
1001 if (elt1 && elt2) {
1002 order1 = ucl_object_todouble (elt1) * 100000;
1003 order2 = ucl_object_todouble (elt2) * 100000;
1004 }
1005 }
1006 else if (g_ascii_strcasecmp (args[0], "time") == 0) {
1007 elt1 = ucl_object_lookup (*o1, "time");
1008 elt2 = ucl_object_lookup (*o2, "time");
1009
1010 if (elt1 && elt2) {
1011 order1 = ucl_object_todouble (elt1) * 1000000;
1012 order2 = ucl_object_todouble (elt2) * 1000000;
1013 }
1014 }
1015 else if (g_ascii_strcasecmp (args[0], "hits") == 0) {
1016 elt1 = ucl_object_lookup (*o1, "hits");
1017 elt2 = ucl_object_lookup (*o2, "hits");
1018
1019 if (elt1 && elt2) {
1020 order1 = ucl_object_toint (elt1);
1021 order2 = ucl_object_toint (elt2);
1022 }
1023 }
1024 }
1025
1026 g_strfreev (args);
1027 }
1028
1029 return (inverse ? (order2 - order1) : (order1 - order2));
1030 }
1031
1032 static void
rspamc_counters_output(FILE * out,ucl_object_t * obj)1033 rspamc_counters_output (FILE *out, ucl_object_t *obj)
1034 {
1035 const ucl_object_t *cur, *sym, *weight, *freq, *freq_dev, *nhits;
1036 ucl_object_iter_t iter = NULL;
1037 gchar fmt_buf[64], dash_buf[82], sym_buf[82];
1038 static const gint dashes = 44;
1039
1040 if (obj->type != UCL_ARRAY) {
1041 rspamd_printf ("Bad output\n");
1042 return;
1043 }
1044
1045 /* Sort symbols by their order */
1046 if (sort != NULL) {
1047 ucl_object_array_sort (obj, rspamc_counters_sort);
1048 }
1049
1050 /* Find maximum width of symbol's name */
1051 gint max_len = sizeof("Symbol") - 1;
1052 while ((cur = ucl_object_iterate (obj, &iter, true)) != NULL) {
1053 sym = ucl_object_lookup (cur, "symbol");
1054 if (sym != NULL) {
1055 if (sym->len > max_len) {
1056 max_len = sym->len;
1057 }
1058 }
1059 }
1060
1061 max_len = MIN (sizeof (dash_buf) - dashes - 1, max_len);
1062 rspamd_snprintf (fmt_buf, sizeof (fmt_buf),
1063 "| %%3s | %%%ds | %%7s | %%13s | %%7s |\n", max_len);
1064 memset (dash_buf, '-', dashes + max_len);
1065 dash_buf[dashes + max_len] = '\0';
1066
1067 printf ("Symbols cache\n");
1068 printf (" %s \n", dash_buf);
1069 if (tty) {
1070 printf ("\033[1m");
1071 }
1072 printf (fmt_buf, "Pri", "Symbol", "Weight", "Frequency", "Hits");
1073 printf (" %s \n", dash_buf);
1074 printf (fmt_buf, "", "", "", "hits/min", "");
1075 if (tty) {
1076 printf ("\033[0m");
1077 }
1078 rspamd_snprintf (fmt_buf, sizeof (fmt_buf),
1079 "| %%3d | %%%ds | %%7.1f | %%6.3f(%%5.3f) | %%7ju |\n", max_len);
1080
1081 iter = NULL;
1082 gint i = 0;
1083 while ((cur = ucl_object_iterate (obj, &iter, true)) != NULL) {
1084 printf (" %s \n", dash_buf);
1085 sym = ucl_object_lookup (cur, "symbol");
1086 weight = ucl_object_lookup (cur, "weight");
1087 freq = ucl_object_lookup (cur, "frequency");
1088 freq_dev = ucl_object_lookup (cur, "frequency_stddev");
1089 nhits = ucl_object_lookup (cur, "hits");
1090
1091 if (sym && weight && freq && nhits) {
1092 const gchar *sym_name;
1093
1094 if (sym->len > max_len) {
1095 rspamd_snprintf (sym_buf, sizeof (sym_buf), "%*s...",
1096 (max_len - 3), ucl_object_tostring (sym));
1097 sym_name = sym_buf;
1098 }
1099 else {
1100 sym_name = ucl_object_tostring (sym);
1101 }
1102
1103 printf (fmt_buf, i,
1104 sym_name,
1105 ucl_object_todouble (weight),
1106 ucl_object_todouble (freq) * 60.0,
1107 ucl_object_todouble (freq_dev) * 60.0,
1108 (uintmax_t)ucl_object_toint (nhits));
1109 }
1110 i++;
1111 }
1112 printf (" %s \n", dash_buf);
1113 }
1114
1115 static void
rspamc_stat_actions(ucl_object_t * obj,GString * out,gint64 scanned)1116 rspamc_stat_actions (ucl_object_t *obj, GString *out, gint64 scanned)
1117 {
1118 const ucl_object_t *actions = ucl_object_lookup (obj, "actions"), *cur;
1119 ucl_object_iter_t iter = NULL;
1120 gint64 spam, ham;
1121
1122 if (actions && ucl_object_type (actions) == UCL_OBJECT) {
1123 while ((cur = ucl_object_iterate (actions, &iter, true)) != NULL) {
1124 gint64 cnt = ucl_object_toint (cur);
1125 rspamd_printf_gstring (out, "Messages with action %s: %L"
1126 ", %.2f%%\n", ucl_object_key (cur), cnt,
1127 ((gdouble)cnt / (gdouble)scanned) * 100.);
1128 }
1129 }
1130
1131 spam = ucl_object_toint (ucl_object_lookup (obj, "spam_count"));
1132 ham = ucl_object_toint (ucl_object_lookup (obj, "ham_count"));
1133 rspamd_printf_gstring (out, "Messages treated as spam: %L, %.2f%%\n", spam,
1134 ((gdouble)spam / (gdouble)scanned) * 100.);
1135 rspamd_printf_gstring (out, "Messages treated as ham: %L, %.2f%%\n", ham,
1136 ((gdouble)ham / (gdouble)scanned) * 100.);
1137 }
1138
1139 static void
rspamc_stat_statfile(const ucl_object_t * obj,GString * out)1140 rspamc_stat_statfile (const ucl_object_t *obj, GString *out)
1141 {
1142 gint64 version, size, blocks, used_blocks, nlanguages, nusers;
1143 const gchar *label, *symbol, *type;
1144
1145 version = ucl_object_toint (ucl_object_lookup (obj, "revision"));
1146 size = ucl_object_toint (ucl_object_lookup (obj, "size"));
1147 blocks = ucl_object_toint (ucl_object_lookup (obj, "total"));
1148 used_blocks = ucl_object_toint (ucl_object_lookup (obj, "used"));
1149 label = ucl_object_tostring (ucl_object_lookup (obj, "label"));
1150 symbol = ucl_object_tostring (ucl_object_lookup (obj, "symbol"));
1151 type = ucl_object_tostring (ucl_object_lookup (obj, "type"));
1152 nlanguages = ucl_object_toint (ucl_object_lookup (obj, "languages"));
1153 nusers = ucl_object_toint (ucl_object_lookup (obj, "users"));
1154
1155 if (label) {
1156 rspamd_printf_gstring (out, "Statfile: %s <%s> type: %s; ", symbol,
1157 label, type);
1158 }
1159 else {
1160 rspamd_printf_gstring (out, "Statfile: %s type: %s; ", symbol, type);
1161 }
1162 rspamd_printf_gstring (out, "length: %hL; free blocks: %hL; total blocks: %hL; "
1163 "free: %.2f%%; learned: %L; users: %L; languages: %L\n",
1164 size,
1165 blocks - used_blocks, blocks,
1166 blocks > 0 ? (blocks - used_blocks) * 100.0 / (gdouble)blocks : 0,
1167 version,
1168 nusers, nlanguages);
1169 }
1170
1171 static void
rspamc_stat_output(FILE * out,ucl_object_t * obj)1172 rspamc_stat_output (FILE *out, ucl_object_t *obj)
1173 {
1174 GString *out_str;
1175 ucl_object_iter_t iter = NULL;
1176 const ucl_object_t *st, *cur;
1177 gint64 scanned;
1178
1179 out_str = g_string_sized_new (BUFSIZ);
1180
1181 scanned = ucl_object_toint (ucl_object_lookup (obj, "scanned"));
1182 rspamd_printf_gstring (out_str, "Messages scanned: %L\n",
1183 scanned);
1184
1185 if (scanned > 0) {
1186 rspamc_stat_actions (obj, out_str, scanned);
1187 }
1188
1189 rspamd_printf_gstring (out_str, "Messages learned: %L\n",
1190 ucl_object_toint (ucl_object_lookup (obj, "learned")));
1191 rspamd_printf_gstring (out_str, "Connections count: %L\n",
1192 ucl_object_toint (ucl_object_lookup (obj, "connections")));
1193 rspamd_printf_gstring (out_str, "Control connections count: %L\n",
1194 ucl_object_toint (ucl_object_lookup (obj, "control_connections")));
1195 /* Pools */
1196 rspamd_printf_gstring (out_str, "Pools allocated: %L\n",
1197 ucl_object_toint (ucl_object_lookup (obj, "pools_allocated")));
1198 rspamd_printf_gstring (out_str, "Pools freed: %L\n",
1199 ucl_object_toint (ucl_object_lookup (obj, "pools_freed")));
1200 rspamd_printf_gstring (out_str, "Bytes allocated: %HL\n",
1201 ucl_object_toint (ucl_object_lookup (obj, "bytes_allocated")));
1202 rspamd_printf_gstring (out_str, "Memory chunks allocated: %L\n",
1203 ucl_object_toint (ucl_object_lookup (obj, "chunks_allocated")));
1204 rspamd_printf_gstring (out_str, "Shared chunks allocated: %L\n",
1205 ucl_object_toint (ucl_object_lookup (obj, "shared_chunks_allocated")));
1206 rspamd_printf_gstring (out_str, "Chunks freed: %L\n",
1207 ucl_object_toint (ucl_object_lookup (obj, "chunks_freed")));
1208 rspamd_printf_gstring (out_str, "Oversized chunks: %L\n",
1209 ucl_object_toint (ucl_object_lookup (obj, "chunks_oversized")));
1210 /* Fuzzy */
1211
1212 st = ucl_object_lookup (obj, "fuzzy_hashes");
1213 if (st) {
1214 ucl_object_iter_t it = NULL;
1215 const ucl_object_t *cur;
1216 gint64 stored = 0;
1217
1218 while ((cur = ucl_iterate_object (st, &it, true)) != NULL) {
1219 rspamd_printf_gstring (out_str, "Fuzzy hashes in storage \"%s\": %L\n",
1220 ucl_object_key (cur),
1221 ucl_object_toint (cur));
1222 stored += ucl_object_toint (cur);
1223 }
1224
1225 rspamd_printf_gstring (out_str, "Fuzzy hashes stored: %L\n",
1226 stored);
1227 }
1228
1229 st = ucl_object_lookup (obj, "fuzzy_checked");
1230 if (st != NULL && ucl_object_type (st) == UCL_ARRAY) {
1231 rspamd_printf_gstring (out_str, "Fuzzy hashes checked: ");
1232 iter = NULL;
1233
1234 while ((cur = ucl_object_iterate (st, &iter, true)) != NULL) {
1235 rspamd_printf_gstring (out_str, "%hL ", ucl_object_toint (cur));
1236 }
1237
1238 rspamd_printf_gstring (out_str, "\n");
1239 }
1240
1241 st = ucl_object_lookup (obj, "fuzzy_found");
1242 if (st != NULL && ucl_object_type (st) == UCL_ARRAY) {
1243 rspamd_printf_gstring (out_str, "Fuzzy hashes found: ");
1244 iter = NULL;
1245
1246 while ((cur = ucl_object_iterate (st, &iter, true)) != NULL) {
1247 rspamd_printf_gstring (out_str, "%hL ", ucl_object_toint (cur));
1248 }
1249
1250 rspamd_printf_gstring (out_str, "\n");
1251 }
1252
1253 st = ucl_object_lookup (obj, "statfiles");
1254 if (st != NULL && ucl_object_type (st) == UCL_ARRAY) {
1255 iter = NULL;
1256
1257 while ((cur = ucl_object_iterate (st, &iter, true)) != NULL) {
1258 rspamc_stat_statfile (cur, out_str);
1259 }
1260 }
1261 rspamd_printf_gstring (out_str, "Total learns: %L\n",
1262 ucl_object_toint (ucl_object_lookup (obj, "total_learns")));
1263
1264 rspamd_fprintf (out, "%v", out_str);
1265 }
1266
1267 static void
rspamc_output_headers(FILE * out,struct rspamd_http_message * msg)1268 rspamc_output_headers (FILE *out, struct rspamd_http_message *msg)
1269 {
1270 struct rspamd_http_header *h;
1271
1272 kh_foreach_value (msg->headers, h, {
1273 rspamd_fprintf (out, "%T: %T\n", &h->name, &h->value);
1274 });
1275
1276 rspamd_fprintf (out, "\n");
1277 }
1278
1279 static void
rspamc_mime_output(FILE * out,ucl_object_t * result,GString * input,gdouble time,GError * err)1280 rspamc_mime_output (FILE *out, ucl_object_t *result, GString *input,
1281 gdouble time, GError *err)
1282 {
1283 const ucl_object_t *cur, *res, *syms;
1284 ucl_object_iter_t it = NULL;
1285 const gchar *action = "no action", *line_end = "\r\n", *p;
1286 gchar scorebuf[32];
1287 GString *symbuf, *folded_symbuf, *added_headers;
1288 gint act = 0;
1289 goffset headers_pos;
1290 gdouble score = 0.0, required_score = 0.0;
1291 gboolean is_spam = FALSE;
1292 gchar *json_header, *json_header_encoded, *sc;
1293 enum rspamd_newlines_type nl_type = RSPAMD_TASK_NEWLINES_CRLF;
1294
1295 headers_pos = rspamd_string_find_eoh (input, NULL);
1296
1297 if (headers_pos == -1) {
1298 rspamd_fprintf (stderr,"cannot find end of headers position");
1299 return;
1300 }
1301
1302 p = input->str + headers_pos;
1303
1304 if (headers_pos > 1 && *(p - 1) == '\n') {
1305 if (headers_pos > 2 && *(p - 2) == '\r') {
1306 line_end = "\r\n";
1307 nl_type = RSPAMD_TASK_NEWLINES_CRLF;
1308 }
1309 else {
1310 line_end = "\n";
1311 nl_type = RSPAMD_TASK_NEWLINES_LF;
1312 }
1313 }
1314 else if (headers_pos > 1 && *(p - 1) == '\r') {
1315 line_end = "\r";
1316 nl_type = RSPAMD_TASK_NEWLINES_CR;
1317 }
1318
1319 added_headers = g_string_sized_new (127);
1320
1321 if (result) {
1322 res = ucl_object_lookup (result, "action");
1323
1324 if (res) {
1325 action = ucl_object_tostring (res);
1326 }
1327
1328 res = ucl_object_lookup (result, "score");
1329 if (res) {
1330 score = ucl_object_todouble (res);
1331 }
1332
1333 res = ucl_object_lookup (result, "required_score");
1334 if (res) {
1335 required_score = ucl_object_todouble (res);
1336 }
1337
1338 rspamd_action_from_str_rspamc (action, &act);
1339
1340 if (act < METRIC_ACTION_GREYLIST) {
1341 is_spam = TRUE;
1342 }
1343
1344 rspamd_printf_gstring (added_headers, "X-Spam-Scanner: %s%s",
1345 "rspamc " RVERSION, line_end);
1346 rspamd_printf_gstring (added_headers, "X-Spam-Scan-Time: %.3f%s",
1347 time, line_end);
1348
1349 /*
1350 * TODO: add rmilter_headers support here
1351 */
1352 if (is_spam) {
1353 rspamd_printf_gstring (added_headers, "X-Spam: yes%s", line_end);
1354 }
1355
1356 rspamd_printf_gstring (added_headers, "X-Spam-Action: %s%s",
1357 action, line_end);
1358 rspamd_printf_gstring (added_headers, "X-Spam-Score: %.2f / %.2f%s",
1359 score, required_score, line_end);
1360
1361 /* SA style stars header */
1362 for (sc = scorebuf; sc < scorebuf + sizeof (scorebuf) - 1 && score > 0;
1363 sc ++, score -= 1.0) {
1364 *sc = '*';
1365 }
1366
1367 *sc = '\0';
1368 rspamd_printf_gstring (added_headers, "X-Spam-Level: %s%s",
1369 scorebuf, line_end);
1370
1371 /* Short description of all symbols */
1372 symbuf = g_string_sized_new (64);
1373 syms = ucl_object_lookup (result, "symbols");
1374
1375 while (syms && (cur = ucl_object_iterate (syms, &it, true)) != NULL) {
1376
1377 if (ucl_object_type (cur) == UCL_OBJECT) {
1378 rspamd_printf_gstring (symbuf, "%s,", ucl_object_key (cur));
1379 }
1380 }
1381 /* Trim the last comma */
1382 if (symbuf->str[symbuf->len - 1] == ',') {
1383 g_string_erase (symbuf, symbuf->len - 1, 1);
1384 }
1385
1386 folded_symbuf = rspamd_header_value_fold ("X-Spam-Symbols",
1387 symbuf->str,
1388 0, nl_type, ",");
1389 rspamd_printf_gstring (added_headers, "X-Spam-Symbols: %v%s",
1390 folded_symbuf, line_end);
1391
1392 g_string_free (folded_symbuf, TRUE);
1393 g_string_free (symbuf, TRUE);
1394
1395 res = ucl_object_lookup (result, "dkim-signature");
1396 if (res && res->type == UCL_STRING) {
1397 rspamd_printf_gstring (added_headers, "DKIM-Signature: %s%s",
1398 ucl_object_tostring (res), line_end);
1399 } else if (res && res->type == UCL_ARRAY) {
1400 it = NULL;
1401 while ((cur = ucl_object_iterate (res, &it, true)) != NULL) {
1402 rspamd_printf_gstring (added_headers, "DKIM-Signature: %s%s",
1403 ucl_object_tostring (cur), line_end);
1404 }
1405 }
1406
1407 if (json || raw || compact) {
1408 /* We also append json data as a specific header */
1409 if (json) {
1410 json_header = ucl_object_emit (result,
1411 compact ? UCL_EMIT_JSON_COMPACT : UCL_EMIT_JSON);
1412 }
1413 else {
1414 json_header = ucl_object_emit (result,
1415 compact ? UCL_EMIT_JSON_COMPACT : UCL_EMIT_CONFIG);
1416 }
1417
1418 json_header_encoded = rspamd_encode_base64_fold (json_header,
1419 strlen (json_header), 60, NULL, nl_type);
1420 free (json_header);
1421 rspamd_printf_gstring (added_headers,
1422 "X-Spam-Result: %s%s",
1423 json_header_encoded, line_end);
1424 g_free (json_header_encoded);
1425 }
1426
1427 ucl_object_unref (result);
1428 }
1429 else {
1430 rspamd_printf_gstring (added_headers, "X-Spam-Scanner: %s%s",
1431 "rspamc " RVERSION, line_end);
1432 rspamd_printf_gstring (added_headers, "X-Spam-Scan-Time: %.3f%s",
1433 time, line_end);
1434 rspamd_printf_gstring (added_headers, "X-Spam-Error: %e%s",
1435 err, line_end);
1436 }
1437
1438 /* Write message */
1439 if (rspamd_fprintf (out, "%*s", (gint)headers_pos, input->str)
1440 == headers_pos) {
1441 if (rspamd_fprintf (out, "%v", added_headers)
1442 == (gint)added_headers->len) {
1443 rspamd_fprintf (out, "%s", input->str + headers_pos);
1444 }
1445 }
1446
1447 g_string_free (added_headers, TRUE);
1448 }
1449
1450 static void
rspamc_client_execute_cmd(struct rspamc_command * cmd,ucl_object_t * result,GString * input,gdouble time,GError * err)1451 rspamc_client_execute_cmd (struct rspamc_command *cmd, ucl_object_t *result,
1452 GString *input, gdouble time, GError *err)
1453 {
1454 gchar **eargv;
1455 gint eargc, infd, outfd, errfd;
1456 GError *exec_err = NULL;
1457 GPid cld;
1458 FILE *out;
1459 gchar *ucl_out;
1460
1461 if (!g_shell_parse_argv (execute, &eargc, &eargv, &err)) {
1462 rspamd_fprintf (stderr, "Cannot execute %s: %e", execute, err);
1463 g_error_free (err);
1464
1465 return;
1466 }
1467
1468 if (!g_spawn_async_with_pipes (NULL, eargv, NULL,
1469 G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &cld,
1470 &infd, &outfd, &errfd, &exec_err)) {
1471
1472 rspamd_fprintf (stderr, "Cannot execute %s: %e", execute, exec_err);
1473 g_error_free (exec_err);
1474
1475 exit (EXIT_FAILURE);
1476 }
1477 else {
1478 children = g_list_prepend (children, GSIZE_TO_POINTER (cld));
1479 out = fdopen (infd, "w");
1480
1481 if (cmd->cmd == RSPAMC_COMMAND_SYMBOLS && mime_output && input) {
1482 rspamc_mime_output (out, result, input, time, err);
1483 }
1484 else if (result) {
1485 if (raw || cmd->command_output_func == NULL) {
1486 if (json) {
1487 ucl_out = ucl_object_emit (result,
1488 compact ? UCL_EMIT_JSON_COMPACT : UCL_EMIT_JSON);
1489 }
1490 else {
1491 ucl_out = ucl_object_emit (result,
1492 compact ? UCL_EMIT_JSON_COMPACT : UCL_EMIT_CONFIG);
1493 }
1494 rspamd_fprintf (out, "%s", ucl_out);
1495 free (ucl_out);
1496 }
1497 else {
1498 cmd->command_output_func (out, result);
1499 }
1500
1501 ucl_object_unref (result);
1502 }
1503 else {
1504 rspamd_fprintf (out, "%e\n", err);
1505 }
1506
1507 fflush (out);
1508
1509 fclose (out);
1510 }
1511
1512 g_strfreev (eargv);
1513 }
1514
1515 static void
rspamc_client_cb(struct rspamd_client_connection * conn,struct rspamd_http_message * msg,const gchar * name,ucl_object_t * result,GString * input,gpointer ud,gdouble start_time,gdouble send_time,const gchar * body,gsize bodylen,GError * err)1516 rspamc_client_cb (struct rspamd_client_connection *conn,
1517 struct rspamd_http_message *msg,
1518 const gchar *name, ucl_object_t *result, GString *input,
1519 gpointer ud, gdouble start_time, gdouble send_time,
1520 const gchar *body, gsize bodylen,
1521 GError *err)
1522 {
1523 gchar *ucl_out;
1524 struct rspamc_callback_data *cbdata = (struct rspamc_callback_data *)ud;
1525 struct rspamc_command *cmd;
1526 FILE *out = stdout;
1527 gdouble finish = rspamd_get_ticks (FALSE), diff;
1528
1529 cmd = cbdata->cmd;
1530
1531 if (send_time > 0) {
1532 diff = finish - send_time;
1533 }
1534 else {
1535 diff = finish - start_time;
1536 }
1537
1538 if (execute) {
1539 /* Pass all to the external command */
1540 rspamc_client_execute_cmd (cmd, result, input, diff, err);
1541 }
1542 else {
1543
1544 if (cmd->cmd == RSPAMC_COMMAND_SYMBOLS && mime_output && input) {
1545 if (body) {
1546 GString tmp;
1547
1548 tmp.str = (char *)body;
1549 tmp.len = bodylen;
1550 rspamc_mime_output (out, result, &tmp, diff, err);
1551 }
1552 else {
1553 rspamc_mime_output (out, result, input, diff, err);
1554 }
1555 }
1556 else {
1557 if (cmd->need_input && !json) {
1558 if (!compact) {
1559 rspamd_fprintf (out, "Results for file: %s (%.3f seconds)\n",
1560 cbdata->filename, diff);
1561 }
1562 }
1563 else {
1564 if (!compact && !json) {
1565 rspamd_fprintf (out, "Results for command: %s (%.3f seconds)\n",
1566 cmd->name, diff);
1567 }
1568 }
1569
1570 if (result != NULL) {
1571 if (headers && msg != NULL) {
1572 rspamc_output_headers (out, msg);
1573 }
1574 if (raw || cmd->command_output_func == NULL) {
1575 if (cmd->need_input) {
1576 ucl_object_insert_key (result,
1577 ucl_object_fromstring (cbdata->filename),
1578 "filename", 0,
1579 false);
1580 }
1581
1582 ucl_object_insert_key (result,
1583 ucl_object_fromdouble (diff),
1584 "scan_time", 0,
1585 false);
1586
1587 if (json) {
1588 ucl_out = ucl_object_emit (result,
1589 compact ? UCL_EMIT_JSON_COMPACT : UCL_EMIT_JSON);
1590 }
1591 else {
1592 ucl_out = ucl_object_emit (result,
1593 compact ? UCL_EMIT_JSON_COMPACT : UCL_EMIT_CONFIG);
1594 }
1595
1596 rspamd_fprintf (out, "%s", ucl_out);
1597 free (ucl_out);
1598 }
1599 else {
1600 cmd->command_output_func (out, result);
1601 }
1602
1603 if (body) {
1604 rspamd_fprintf (out, "\nNew body:\n%*s\n", (int)bodylen,
1605 body);
1606 }
1607
1608 ucl_object_unref (result);
1609 }
1610 else if (err != NULL) {
1611 rspamd_fprintf (out, "%s\n", err->message);
1612
1613 if (json && msg != NULL) {
1614 const gchar *raw;
1615 gsize rawlen;
1616
1617 raw = rspamd_http_message_get_body (msg, &rawlen);
1618
1619 if (raw) {
1620 /* We can also output the resulting json */
1621 rspamd_fprintf (out, "%*s\n", (gint)(rawlen - bodylen),
1622 raw);
1623 }
1624 }
1625 }
1626 rspamd_fprintf (out, "\n");
1627 }
1628
1629 fflush (out);
1630 }
1631
1632 rspamd_client_destroy (conn);
1633 g_free (cbdata->filename);
1634 g_free (cbdata);
1635
1636 if (err) {
1637 retcode = EXIT_FAILURE;
1638 }
1639 }
1640
1641 static void
rspamc_process_input(struct ev_loop * ev_base,struct rspamc_command * cmd,FILE * in,const gchar * name,GQueue * attrs)1642 rspamc_process_input (struct ev_loop *ev_base, struct rspamc_command *cmd,
1643 FILE *in, const gchar *name, GQueue *attrs)
1644 {
1645 struct rspamd_client_connection *conn;
1646 gchar *hostbuf = NULL, *p;
1647 guint16 port;
1648 GError *err = NULL;
1649 struct rspamc_callback_data *cbdata;
1650
1651 if (connect_str[0] == '[') {
1652 p = strrchr (connect_str, ']');
1653
1654 if (p != NULL) {
1655 hostbuf = g_malloc (p - connect_str);
1656 rspamd_strlcpy (hostbuf, connect_str + 1, p - connect_str);
1657 p ++;
1658 }
1659 else {
1660 p = connect_str;
1661 }
1662 }
1663 else {
1664 p = connect_str;
1665 }
1666
1667 p = strrchr (p, ':');
1668
1669 if (!hostbuf) {
1670 if (p != NULL) {
1671 hostbuf = g_malloc (p - connect_str + 1);
1672 rspamd_strlcpy (hostbuf, connect_str, p - connect_str + 1);
1673 }
1674 else {
1675 hostbuf = g_strdup (connect_str);
1676 }
1677 }
1678
1679 if (p != NULL) {
1680 port = strtoul (p + 1, NULL, 10);
1681 }
1682 else {
1683 /*
1684 * If we connect to localhost, 127.0.0.1 or ::1, then try controller
1685 * port first
1686 */
1687
1688 if (strcmp (hostbuf, "localhost") == 0 ||
1689 strcmp (hostbuf, "127.0.0.1") == 0 ||
1690 strcmp (hostbuf, "::1") == 0 ||
1691 strcmp (hostbuf, "[::1]") == 0) {
1692 port = DEFAULT_CONTROL_PORT;
1693 }
1694 else {
1695 port = cmd->is_controller ? DEFAULT_CONTROL_PORT : DEFAULT_PORT;
1696 }
1697
1698 }
1699
1700 conn = rspamd_client_init (http_ctx, ev_base, hostbuf, port, timeout, key);
1701
1702 if (conn != NULL) {
1703 cbdata = g_malloc0 (sizeof (struct rspamc_callback_data));
1704 cbdata->cmd = cmd;
1705
1706 if (name) {
1707 cbdata->filename = g_strdup (name);
1708 }
1709
1710 if (cmd->need_input) {
1711 rspamd_client_command (conn, cmd->path, attrs, in, rspamc_client_cb,
1712 cbdata, compressed, dictionary, cbdata->filename, &err);
1713 }
1714 else {
1715 rspamd_client_command (conn,
1716 cmd->path,
1717 attrs,
1718 NULL,
1719 rspamc_client_cb,
1720 cbdata,
1721 compressed,
1722 dictionary,
1723 cbdata->filename,
1724 &err);
1725 }
1726 }
1727 else {
1728 rspamd_fprintf (stderr, "cannot connect to %s: %s\n", connect_str,
1729 strerror (errno));
1730 exit (EXIT_FAILURE);
1731 }
1732
1733 g_free (hostbuf);
1734 }
1735
1736 static gsize
rspamd_dirent_size(DIR * dirp)1737 rspamd_dirent_size (DIR * dirp)
1738 {
1739 goffset name_max;
1740 gsize name_end;
1741
1742 #if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) \
1743 && defined(_PC_NAME_MAX)
1744 name_max = fpathconf (dirfd (dirp), _PC_NAME_MAX);
1745
1746
1747 # if defined(NAME_MAX)
1748 if (name_max == -1) {
1749 name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
1750 }
1751 # else
1752 if (name_max == -1) {
1753 return (size_t)(-1);
1754 }
1755 # endif
1756 #else
1757 # if defined(NAME_MAX)
1758 name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
1759 # else
1760 # error "buffer size for readdir_r cannot be determined"
1761 # endif
1762 #endif
1763
1764 name_end = G_STRUCT_OFFSET (struct dirent, d_name) + name_max + 1;
1765
1766 return (name_end > sizeof (struct dirent) ? name_end : sizeof(struct dirent));
1767 }
1768
1769 static void
rspamc_process_dir(struct ev_loop * ev_base,struct rspamc_command * cmd,const gchar * name,GQueue * attrs)1770 rspamc_process_dir (struct ev_loop *ev_base, struct rspamc_command *cmd,
1771 const gchar *name, GQueue *attrs)
1772 {
1773 DIR *d;
1774 GPatternSpec **ex;
1775 struct dirent *pentry;
1776 gint cur_req = 0, r;
1777 gchar fpath[PATH_MAX];
1778 FILE *in;
1779 struct stat st;
1780 gboolean is_reg, is_dir, skip;
1781
1782 d = opendir (name);
1783
1784 if (d != NULL) {
1785 while ((pentry = readdir (d))!= NULL) {
1786
1787 if (pentry->d_name[0] == '.') {
1788 continue;
1789 }
1790
1791 r = rspamd_snprintf (fpath, sizeof (fpath), "%s%c%s",
1792 name, G_DIR_SEPARATOR,
1793 pentry->d_name);
1794
1795 /* Check exclude */
1796 ex = exclude_compiled;
1797 skip = FALSE;
1798 while (ex != NULL && *ex != NULL) {
1799 if (g_pattern_match (*ex, r, fpath, NULL)) {
1800 skip = TRUE;
1801 break;
1802 }
1803
1804 ex ++;
1805 }
1806
1807 if (skip) {
1808 continue;
1809 }
1810
1811 is_reg = FALSE;
1812 is_dir = FALSE;
1813
1814 #if (defined(_DIRENT_HAVE_D_TYPE) || defined(__APPLE__)) && defined(DT_UNKNOWN)
1815 if (pentry->d_type == DT_UNKNOWN) {
1816 /* Fallback to lstat */
1817 if (lstat (fpath, &st) == -1) {
1818 rspamd_fprintf (stderr, "cannot stat file %s: %s\n",
1819 fpath, strerror (errno));
1820 continue;
1821 }
1822
1823 is_dir = S_ISDIR (st.st_mode);
1824 is_reg = S_ISREG (st.st_mode);
1825 }
1826 else {
1827 if (pentry->d_type == DT_REG) {
1828 is_reg = TRUE;
1829 }
1830 else if (pentry->d_type == DT_DIR) {
1831 is_dir = TRUE;
1832 }
1833 }
1834 #else
1835 if (lstat (fpath, &st) == -1) {
1836 rspamd_fprintf (stderr, "cannot stat file %s: %s\n",
1837 fpath, strerror (errno));
1838 continue;
1839 }
1840
1841 is_dir = S_ISDIR (st.st_mode);
1842 is_reg = S_ISREG (st.st_mode);
1843 #endif
1844 if (is_dir) {
1845 rspamc_process_dir (ev_base, cmd, fpath, attrs);
1846 continue;
1847 }
1848 else if (is_reg) {
1849 in = fopen (fpath, "r");
1850 if (in == NULL) {
1851 rspamd_fprintf (stderr, "cannot open file %s: %s\n",
1852 fpath, strerror (errno));
1853 continue;
1854 }
1855
1856 rspamc_process_input (ev_base, cmd, in, fpath, attrs);
1857 cur_req++;
1858 fclose (in);
1859
1860 if (cur_req >= max_requests) {
1861 cur_req = 0;
1862 /* Wait for completion */
1863 ev_loop (ev_base, 0);
1864 }
1865 }
1866 }
1867 }
1868 else {
1869 fprintf (stderr, "cannot open directory %s: %s\n", name, strerror (errno));
1870 exit (EXIT_FAILURE);
1871 }
1872
1873 closedir (d);
1874 ev_loop (ev_base, 0);
1875 }
1876
1877
1878 static void
rspamc_kwattr_free(gpointer p)1879 rspamc_kwattr_free (gpointer p)
1880 {
1881 struct rspamd_http_client_header *h = (struct rspamd_http_client_header *)p;
1882
1883 g_free (h->value);
1884 g_free (h->name);
1885 g_free (h);
1886 }
1887
1888 gint
main(gint argc,gchar ** argv,gchar ** env)1889 main (gint argc, gchar **argv, gchar **env)
1890 {
1891 gint i, start_argc, cur_req = 0, res, ret, npatterns;
1892 GQueue *kwattrs;
1893 GList *cur;
1894 GPid cld;
1895 struct rspamc_command *cmd;
1896 FILE *in = NULL;
1897 struct ev_loop *event_loop;
1898 struct stat st;
1899 struct sigaction sigpipe_act;
1900 gchar **exclude_pattern;
1901
1902 kwattrs = g_queue_new ();
1903
1904 read_cmd_line (&argc, &argv);
1905
1906 tty = isatty (STDOUT_FILENO);
1907
1908 if (print_commands) {
1909 print_commands_list ();
1910 exit (EXIT_SUCCESS);
1911 }
1912
1913 /* Deal with exclude patterns */
1914 exclude_pattern = exclude_patterns;
1915 npatterns = 0;
1916
1917 while (exclude_pattern && *exclude_pattern) {
1918 exclude_pattern ++;
1919 npatterns ++;
1920 }
1921
1922 if (npatterns > 0) {
1923 exclude_compiled = g_malloc0 (sizeof (*exclude_compiled) * (npatterns + 1));
1924
1925 for (i = 0; i < npatterns; i ++) {
1926 exclude_compiled[i] = g_pattern_spec_new (exclude_patterns[i]);
1927
1928 if (exclude_compiled[i] == NULL) {
1929 rspamd_fprintf (stderr, "Invalid glob pattern: %s\n",
1930 exclude_patterns[i]);
1931 exit (EXIT_FAILURE);
1932 }
1933 }
1934 }
1935
1936 struct rspamd_external_libs_ctx *libs = rspamd_init_libs ();
1937 event_loop = ev_loop_new (EVBACKEND_ALL);
1938
1939 struct rspamd_http_context_cfg http_config;
1940
1941 memset (&http_config, 0, sizeof (http_config));
1942 http_config.kp_cache_size_client = 32;
1943 http_config.kp_cache_size_server = 0;
1944 http_config.user_agent = user_agent;
1945 http_ctx = rspamd_http_context_create_config (&http_config,
1946 event_loop, NULL);
1947
1948 /* Ignore sigpipe */
1949 sigemptyset (&sigpipe_act.sa_mask);
1950 sigaddset (&sigpipe_act.sa_mask, SIGPIPE);
1951 sigpipe_act.sa_handler = SIG_IGN;
1952 sigpipe_act.sa_flags = 0;
1953 sigaction (SIGPIPE, &sigpipe_act, NULL);
1954
1955 /* Now read other args from argc and argv */
1956 if (argc == 1) {
1957 start_argc = argc;
1958 in = stdin;
1959 cmd = check_rspamc_command ("symbols");
1960 }
1961 else if (argc == 2) {
1962 /* One argument is whether command or filename */
1963 if ((cmd = check_rspamc_command (argv[1])) != NULL) {
1964 start_argc = argc;
1965 in = stdin;
1966 }
1967 else {
1968 cmd = check_rspamc_command ("symbols"); /* Symbols command */
1969 start_argc = 1;
1970 }
1971 }
1972 else {
1973 if ((cmd = check_rspamc_command (argv[1])) != NULL) {
1974 /* In case of command read arguments starting from 2 */
1975 if (cmd->cmd == RSPAMC_COMMAND_ADD_SYMBOL || cmd->cmd ==
1976 RSPAMC_COMMAND_ADD_ACTION) {
1977 if (argc < 4 || argc > 5) {
1978 fprintf (stderr, "invalid arguments\n");
1979 exit (EXIT_FAILURE);
1980 }
1981 if (argc == 5) {
1982 ADD_CLIENT_HEADER (kwattrs, "metric", argv[2]);
1983 ADD_CLIENT_HEADER (kwattrs, "name", argv[3]);
1984 ADD_CLIENT_HEADER (kwattrs, "value", argv[4]);
1985 }
1986 else {
1987 ADD_CLIENT_HEADER (kwattrs, "name", argv[2]);
1988 ADD_CLIENT_HEADER (kwattrs, "value", argv[3]);
1989 }
1990 start_argc = argc;
1991 }
1992 else {
1993 start_argc = 2;
1994 }
1995 }
1996 else {
1997 cmd = check_rspamc_command ("symbols");
1998 start_argc = 1;
1999 }
2000 }
2001
2002 add_options (kwattrs);
2003
2004 if (start_argc == argc) {
2005 /* Do command without input or with stdin */
2006 if (empty_input) {
2007 rspamc_process_input (event_loop, cmd, NULL, "empty", kwattrs);
2008 }
2009 else {
2010 rspamc_process_input (event_loop, cmd, in, "stdin", kwattrs);
2011 }
2012 }
2013 else {
2014 for (i = start_argc; i < argc; i++) {
2015 if (cmd->cmd == RSPAMC_COMMAND_FUZZY_DELHASH) {
2016 ADD_CLIENT_HEADER (kwattrs, "Hash", argv[i]);
2017 }
2018 else {
2019 if (stat (argv[i], &st) == -1) {
2020 fprintf (stderr, "cannot stat file %s\n", argv[i]);
2021 exit (EXIT_FAILURE);
2022 }
2023 if (S_ISDIR (st.st_mode)) {
2024 /* Directories are processed with a separate limit */
2025 rspamc_process_dir (event_loop, cmd, argv[i], kwattrs);
2026 cur_req = 0;
2027 }
2028 else {
2029 in = fopen (argv[i], "r");
2030 if (in == NULL) {
2031 fprintf (stderr, "cannot open file %s\n", argv[i]);
2032 exit (EXIT_FAILURE);
2033 }
2034 rspamc_process_input (event_loop, cmd, in, argv[i], kwattrs);
2035 cur_req++;
2036 fclose (in);
2037 }
2038 if (cur_req >= max_requests) {
2039 cur_req = 0;
2040 /* Wait for completion */
2041 ev_loop (event_loop, 0);
2042 }
2043 }
2044 }
2045
2046 if (cmd->cmd == RSPAMC_COMMAND_FUZZY_DELHASH) {
2047 rspamc_process_input (event_loop, cmd, NULL, "hashes", kwattrs);
2048 }
2049 }
2050
2051 ev_loop (event_loop, 0);
2052
2053 g_queue_free_full (kwattrs, rspamc_kwattr_free);
2054
2055 /* Wait for children processes */
2056 cur = children ? g_list_first (children) : NULL;
2057 ret = 0;
2058
2059 while (cur) {
2060 cld = GPOINTER_TO_SIZE (cur->data);
2061
2062 if (waitpid (cld, &res, 0) == -1) {
2063 fprintf (stderr, "Cannot wait for %d: %s", (gint)cld,
2064 strerror (errno));
2065
2066 ret = errno;
2067 }
2068
2069 if (ret == 0) {
2070 /* Check return code */
2071 if (WIFSIGNALED (res)) {
2072 ret = WTERMSIG (res);
2073 }
2074 else if (WIFEXITED (res)) {
2075 ret = WEXITSTATUS (res);
2076 }
2077 }
2078
2079 cur = g_list_next (cur);
2080 }
2081
2082 if (children != NULL) {
2083 g_list_free (children);
2084 }
2085
2086 for (i = 0; i < npatterns; i ++) {
2087 g_pattern_spec_free (exclude_compiled[i]);
2088 }
2089
2090 rspamd_deinit_libs (libs);
2091
2092 /* Mix retcode (return from Rspamd side) and ret (return from subprocess) */
2093 return ret | retcode;
2094 }
2095