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

rspamc.c 53KB

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