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