PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/libmu_sieve/extensions/vacation.c

#
C | 712 lines | 575 code | 84 blank | 53 comment | 83 complexity | e3bb04702bc61c82d2eb5dc67224da89 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-3.0, CC-BY-SA-3.0
  1. /* GNU Mailutils -- a suite of utilities for electronic mail
  2. Copyright (C) 2005, 2007, 2009-2012, 2014 Free Software Foundation,
  3. Inc.
  4. This library is free software; you can redistribute it and/or
  5. modify it under the terms of the GNU Lesser General Public
  6. License as published by the Free Software Foundation; either
  7. version 3 of the License, or (at your option) any later version.
  8. This library is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General
  13. Public License along with this library. If not, see
  14. <http://www.gnu.org/licenses/>. */
  15. /* Syntax: vacation [:days <ndays: number>]
  16. [:subject <subject: string>]
  17. [:aliases <address-list: list>]
  18. [:addresses <noreply-address-list: list>]
  19. [:reply_regex <expr: string>]
  20. [:reply_prefix <prefix: string>]
  21. <reply text: string>
  22. */
  23. #ifdef HAVE_CONFIG_H
  24. # include <config.h>
  25. #endif
  26. #include <stdio.h>
  27. #include <unistd.h>
  28. #include <stdlib.h>
  29. #include <sys/types.h>
  30. #include <string.h>
  31. #include <signal.h>
  32. #include <regex.h>
  33. #include <mailutils/sieve.h>
  34. #include <mailutils/mu_auth.h>
  35. /* Build a mime response message from original message MSG. TEXT
  36. is the message text.
  37. */
  38. static int
  39. build_mime (mu_sieve_machine_t mach, mu_list_t tags, mu_mime_t *pmime,
  40. mu_message_t msg, const char *text)
  41. {
  42. mu_mime_t mime = NULL;
  43. mu_message_t newmsg;
  44. mu_stream_t stream, input;
  45. mu_header_t hdr;
  46. mu_body_t body;
  47. const char *header =
  48. "Content-Type: text/plain;charset=" MU_SIEVE_CHARSET "\n"
  49. "Content-Transfer-Encoding: 8bit\n\n";
  50. int rc;
  51. mu_mime_create (&mime, NULL, 0);
  52. mu_message_create (&newmsg, NULL);
  53. mu_message_get_body (newmsg, &body);
  54. if ((rc = mu_static_memory_stream_create (&input, text, strlen (text))))
  55. {
  56. mu_sieve_error (mach,
  57. _("cannot create temporary stream: %s"),
  58. mu_strerror (rc));
  59. mu_mime_destroy (&mime);
  60. mu_message_destroy (&newmsg, NULL);
  61. return 1;
  62. }
  63. if (mu_sieve_tag_lookup (tags, "mime", NULL))
  64. {
  65. mu_stream_t fstr;
  66. rc = mu_filter_create (&fstr, input, "base64",
  67. MU_FILTER_ENCODE,
  68. MU_STREAM_READ);
  69. mu_stream_unref (input);
  70. if (rc == 0)
  71. {
  72. header = "Content-Type: text/plain;charset=" MU_SIEVE_CHARSET "\n"
  73. "Content-Transfer-Encoding: base64\n\n";
  74. input = fstr;
  75. }
  76. }
  77. rc = mu_body_get_streamref (body, &stream);
  78. if (rc)
  79. {
  80. mu_sieve_error (mach,
  81. _("cannot get input body stream: %s"),
  82. mu_strerror (rc));
  83. mu_mime_destroy (&mime);
  84. mu_message_destroy (&newmsg, NULL);
  85. mu_stream_destroy (&input);
  86. return 1;
  87. }
  88. rc = mu_stream_copy (stream, input, 0, NULL);
  89. if (rc)
  90. {
  91. mu_sieve_error (mach,
  92. _("stream copy failed: %s"),
  93. mu_strerror (rc));
  94. mu_mime_destroy (&mime);
  95. mu_message_destroy (&newmsg, NULL);
  96. mu_stream_destroy (&input);
  97. mu_stream_destroy (&stream);
  98. return 1;
  99. }
  100. mu_stream_destroy (&input);
  101. mu_header_create (&hdr, header, strlen (header));
  102. mu_message_set_header (newmsg, hdr, NULL);
  103. mu_mime_add_part (mime, newmsg);
  104. mu_message_unref (newmsg);
  105. *pmime = mime;
  106. return 0;
  107. }
  108. /* Produce diagnostic output. */
  109. static int
  110. diag (mu_sieve_machine_t mach)
  111. {
  112. if (mu_sieve_get_debug_level (mach) & MU_SIEVE_DEBUG_TRACE)
  113. {
  114. mu_sieve_debug (mach, "VACATION");
  115. }
  116. mu_sieve_log_action (mach, "VACATION", NULL);
  117. return mu_sieve_is_dry_run (mach);
  118. }
  119. struct addr_data
  120. {
  121. mu_address_t addr;
  122. char *my_address;
  123. };
  124. static int
  125. _compare (void *item, void *data)
  126. {
  127. struct addr_data *ad = data;
  128. int rc = mu_address_contains_email (ad->addr, item);
  129. if (rc)
  130. ad->my_address = item;
  131. return rc;
  132. }
  133. /* Check whether an alias from ADDRESSES is part of To: or Cc: headers
  134. of the originating mail. Return non-zero if so and store a pointer
  135. to the matching address to *MY_ADDRESS. */
  136. static int
  137. match_addresses (mu_header_t hdr, mu_sieve_value_t *addresses,
  138. char **my_address)
  139. {
  140. int match = 0;
  141. const char *str;
  142. struct addr_data ad;
  143. ad.my_address = NULL;
  144. if (mu_header_sget_value (hdr, MU_HEADER_TO, &str) == 0)
  145. {
  146. if (!mu_address_create (&ad.addr, str))
  147. {
  148. match += mu_sieve_vlist_do (addresses, _compare, &ad);
  149. mu_address_destroy (&ad.addr);
  150. }
  151. }
  152. if (!match && mu_header_sget_value (hdr, MU_HEADER_CC, &str) == 0)
  153. {
  154. if (!mu_address_create (&ad.addr, str))
  155. {
  156. match += mu_sieve_vlist_do (addresses, _compare, &ad);
  157. mu_address_destroy (&ad.addr);
  158. }
  159. }
  160. *my_address = ad.my_address;
  161. return match;
  162. }
  163. struct regex_data
  164. {
  165. mu_sieve_machine_t mach;
  166. char *email;
  167. };
  168. static int
  169. regex_comparator (void *item, void *data)
  170. {
  171. regex_t preg;
  172. int rc;
  173. struct regex_data *d = data;
  174. if (regcomp (&preg, item,
  175. REG_EXTENDED | REG_NOSUB | REG_NEWLINE | REG_ICASE))
  176. {
  177. mu_sieve_error (d->mach,
  178. _("%lu: cannot compile regular expression \"%s\""),
  179. (unsigned long) mu_sieve_get_message_num (d->mach),
  180. (char*) item);
  181. return 0;
  182. }
  183. rc = regexec (&preg, d->email, 0, NULL, 0) == 0;
  184. regfree (&preg);
  185. return rc;
  186. }
  187. /* Decide whether EMAIL address should not be responded to.
  188. */
  189. static int
  190. noreply_address_p (mu_sieve_machine_t mach, mu_list_t tags, char *email)
  191. {
  192. int i, rc = 0;
  193. mu_sieve_value_t *arg;
  194. struct regex_data rd;
  195. static char *noreply_sender[] = {
  196. ".*-REQUEST@.*",
  197. ".*-RELAY@.*",
  198. ".*-OWNER@.*",
  199. "^OWNER-.*",
  200. "^postmaster@.*",
  201. "^UUCP@.*",
  202. "^MAILER@.*",
  203. "^MAILER-DAEMON@.*",
  204. NULL
  205. };
  206. rd.mach = mach;
  207. rd.email = email;
  208. for (i = 0; rc == 0 && noreply_sender[i]; i++)
  209. rc = regex_comparator (noreply_sender[i], &rd);
  210. if (!rc && mu_sieve_tag_lookup (tags, "addresses", &arg))
  211. rc = mu_sieve_vlist_do (arg, regex_comparator, &rd);
  212. return rc;
  213. }
  214. /* Return T if letter precedence is 'bulk' or 'junk' */
  215. static int
  216. bulk_precedence_p (mu_header_t hdr)
  217. {
  218. int rc = 0;
  219. const char *str;
  220. if (mu_header_sget_value (hdr, MU_HEADER_PRECEDENCE, &str) == 0)
  221. {
  222. rc = mu_c_strcasecmp (str, "bulk") == 0
  223. || mu_c_strcasecmp (str, "junk") == 0;
  224. }
  225. return rc;
  226. }
  227. #define DAYS_DEFAULT 7
  228. #define DAYS_MAX 60
  229. static int
  230. test_and_update_prop (mu_property_t prop, const char *from,
  231. time_t now, unsigned int days,
  232. mu_sieve_machine_t mach)
  233. {
  234. const char *result;
  235. char *timebuf;
  236. time_t last;
  237. int rc = mu_property_sget_value (prop, from, &result);
  238. switch (rc)
  239. {
  240. case MU_ERR_NOENT:
  241. break;
  242. case 0:
  243. if (days == 0)
  244. return 1;
  245. last = (time_t) strtoul (result, NULL, 0);
  246. if (last + (24 * 60 * 60 * days) > now)
  247. return 1;
  248. break;
  249. default:
  250. mu_sieve_error (mach, "%lu: mu_property_sget_value: %s",
  251. (unsigned long) mu_sieve_get_message_num (mach),
  252. mu_strerror (rc));
  253. return -1;
  254. }
  255. rc = mu_asprintf (&timebuf, "%lu", (unsigned long) now);
  256. if (rc)
  257. {
  258. mu_sieve_error (mach, "%lu: mu_asprintf: %s",
  259. (unsigned long) mu_sieve_get_message_num (mach),
  260. mu_strerror (rc));
  261. return -1;
  262. }
  263. rc = mu_property_set_value (prop, from, timebuf, 1);
  264. free (timebuf);
  265. if (rc)
  266. {
  267. mu_sieve_error (mach, "%lu: mu_property_set_value: %s",
  268. (unsigned long) mu_sieve_get_message_num (mach),
  269. mu_strerror (rc));
  270. return -1;
  271. }
  272. rc = mu_property_save (prop);
  273. if (rc)
  274. {
  275. mu_sieve_error (mach, "%lu: mu_property_save: %s",
  276. (unsigned long) mu_sieve_get_message_num (mach),
  277. mu_strerror (rc));
  278. return -1;
  279. }
  280. return 0;
  281. }
  282. /* Check and update the vacation database. Return 0 if the mail should
  283. be answered, 0 if it should not, and throw exception if an error
  284. occurs. */
  285. static int
  286. check_db (mu_sieve_machine_t mach, mu_list_t tags, char *from)
  287. {
  288. mu_property_t prop;
  289. char *file;
  290. mu_sieve_value_t *arg;
  291. unsigned int days;
  292. int rc;
  293. mu_stream_t str;
  294. mu_locker_t locker;
  295. if (mu_sieve_tag_lookup (tags, "days", &arg))
  296. {
  297. days = arg->v.number;
  298. if (days > DAYS_MAX)
  299. days = DAYS_MAX;
  300. }
  301. else
  302. days = DAYS_DEFAULT;
  303. file = mu_tilde_expansion ("~/.vacation", MU_HIERARCHY_DELIMITER, NULL);
  304. if (!file)
  305. {
  306. mu_sieve_error (mach, _("%lu: cannot build db file name"),
  307. (unsigned long) mu_sieve_get_message_num (mach));
  308. mu_sieve_abort (mach);
  309. }
  310. rc = mu_locker_create (&locker, file, 0);
  311. if (rc)
  312. {
  313. mu_sieve_error (mach, _("%lu: cannot lock %s: %s"),
  314. (unsigned long) mu_sieve_get_message_num (mach),
  315. file,
  316. mu_strerror (rc));
  317. free (file);
  318. mu_sieve_abort (mach);
  319. }
  320. rc = mu_file_stream_create (&str, file, MU_STREAM_RDWR|MU_STREAM_CREAT);
  321. if (rc)
  322. {
  323. mu_sieve_error (mach, "%lu: mu_file_stream_create(%s): %s",
  324. (unsigned long) mu_sieve_get_message_num (mach),
  325. file,
  326. mu_strerror (rc));
  327. mu_locker_destroy (&locker);
  328. free (file);
  329. mu_sieve_abort (mach);
  330. }
  331. free (file);
  332. rc = mu_property_create_init (&prop, mu_assoc_property_init, str);
  333. if (rc)
  334. {
  335. mu_sieve_error (mach, "%lu: mu_property_create_init: %s",
  336. (unsigned long) mu_sieve_get_message_num (mach),
  337. mu_strerror (rc));
  338. mu_locker_destroy (&locker);
  339. mu_sieve_abort (mach);
  340. }
  341. rc = mu_locker_lock (locker);
  342. if (rc)
  343. {
  344. mu_sieve_error (mach, "%lu: cannot lock vacation database: %s",
  345. (unsigned long) mu_sieve_get_message_num (mach),
  346. mu_strerror (rc));
  347. mu_property_destroy (&prop);
  348. mu_sieve_abort (mach);
  349. }
  350. rc = test_and_update_prop (prop, from, time (NULL), days, mach);
  351. mu_property_destroy (&prop);
  352. mu_locker_unlock (locker);
  353. mu_locker_destroy (&locker);
  354. if (rc == -1)
  355. mu_sieve_abort (mach);
  356. return rc;
  357. }
  358. /* Add a reply prefix to the subject. *PSUBJECT points to the
  359. original subject, which must be allocated using malloc. Before
  360. returning its value is freed and replaced with the new one.
  361. Default reply prefix is "Re: ", unless overridden by
  362. "reply_prefix" tag.
  363. */
  364. static void
  365. re_subject (mu_sieve_machine_t mach, mu_list_t tags, char **psubject)
  366. {
  367. char *subject;
  368. mu_sieve_value_t *arg;
  369. char *prefix = "Re";
  370. if (mu_sieve_tag_lookup (tags, "reply_prefix", &arg))
  371. {
  372. prefix = arg->v.string;
  373. }
  374. subject = malloc (strlen (*psubject) + strlen (prefix) + 3);
  375. if (!subject)
  376. {
  377. mu_sieve_error (mach,
  378. _("%lu: not enough memory"),
  379. (unsigned long) mu_sieve_get_message_num (mach));
  380. return;
  381. }
  382. strcpy (subject, prefix);
  383. strcat (subject, ": ");
  384. strcat (subject, *psubject);
  385. free (*psubject);
  386. *psubject = subject;
  387. }
  388. /* Process reply subject header.
  389. If :subject is set, its value is used.
  390. Otherwise, if original subject matches reply_regex, it is used verbatim.
  391. Otherwise, reply_prefix is prepended to it. */
  392. static void
  393. vacation_subject (mu_sieve_machine_t mach, mu_list_t tags,
  394. mu_message_t msg, mu_header_t newhdr)
  395. {
  396. mu_sieve_value_t *arg;
  397. char *value;
  398. char *subject;
  399. int subject_allocated = 0;
  400. mu_header_t hdr;
  401. if (mu_sieve_tag_lookup (tags, "subject", &arg))
  402. subject = arg->v.string;
  403. else if (mu_message_get_header (msg, &hdr) == 0
  404. && mu_header_aget_value_unfold (hdr, MU_HEADER_SUBJECT,
  405. &value) == 0)
  406. {
  407. char *p;
  408. int rc = mu_rfc2047_decode (MU_SIEVE_CHARSET, value, &p);
  409. subject_allocated = 1;
  410. if (rc)
  411. {
  412. subject = value;
  413. value = NULL;
  414. }
  415. else
  416. {
  417. subject = p;
  418. }
  419. if (mu_sieve_tag_lookup (tags, "reply_regex", &arg))
  420. {
  421. char *err = NULL;
  422. rc = mu_unre_set_regex (arg->v.string, 0, &err);
  423. if (rc)
  424. {
  425. mu_sieve_error (mach,
  426. _("%lu: cannot compile reply prefix regexp: %s: %s"),
  427. (unsigned long) mu_sieve_get_message_num (mach),
  428. mu_strerror (rc),
  429. err ? err : "");
  430. }
  431. }
  432. if (mu_unre_subject (subject, NULL))
  433. re_subject (mach, tags, &subject);
  434. free (value);
  435. }
  436. else
  437. subject = "Re: Your mail";
  438. if (mu_rfc2047_encode (MU_SIEVE_CHARSET, "quoted-printable",
  439. subject, &value))
  440. mu_header_set_value (newhdr, MU_HEADER_SUBJECT, subject, 0);
  441. else
  442. {
  443. mu_header_set_value (newhdr, MU_HEADER_SUBJECT, value, 0);
  444. free (value);
  445. }
  446. if (subject_allocated)
  447. free (subject);
  448. }
  449. /* Generate and send the reply message */
  450. static int
  451. vacation_reply (mu_sieve_machine_t mach, mu_list_t tags, mu_message_t msg,
  452. char *text, char *to, char *from)
  453. {
  454. mu_mime_t mime = NULL;
  455. mu_message_t newmsg;
  456. mu_header_t newhdr;
  457. mu_address_t to_addr = NULL, from_addr = NULL;
  458. char *value;
  459. mu_mailer_t mailer;
  460. int rc;
  461. if (mu_sieve_tag_lookup (tags, "file", NULL))
  462. {
  463. mu_stream_t instr;
  464. rc = mu_mapfile_stream_create (&instr, text, MU_STREAM_READ);
  465. if (rc)
  466. {
  467. mu_sieve_error (mach,
  468. _("%lu: cannot open message file %s: %s"),
  469. (unsigned long) mu_sieve_get_message_num (mach),
  470. text,
  471. mu_strerror (rc));
  472. return -1;
  473. }
  474. rc = mu_stream_to_message (instr, &newmsg);
  475. mu_stream_unref (instr);
  476. if (rc)
  477. {
  478. mu_sieve_error (mach,
  479. _("%lu: cannot read message from file %s: %s"),
  480. (unsigned long) mu_sieve_get_message_num (mach),
  481. text,
  482. mu_strerror (rc));
  483. return -1;
  484. }
  485. }
  486. else
  487. {
  488. if (build_mime (mach, tags, &mime, msg, text))
  489. return -1;
  490. mu_mime_get_message (mime, &newmsg);
  491. mu_message_unref (newmsg);
  492. mu_message_get_header (newmsg, &newhdr);
  493. }
  494. rc = mu_address_create (&to_addr, to);
  495. if (rc)
  496. {
  497. mu_sieve_error (mach,
  498. _("%lu: cannot create recipient address <%s>: %s"),
  499. (unsigned long) mu_sieve_get_message_num (mach),
  500. from, mu_strerror (rc));
  501. }
  502. else
  503. {
  504. mu_header_set_value (newhdr, MU_HEADER_TO, to, 0);
  505. vacation_subject (mach, tags, msg, newhdr);
  506. if (from)
  507. {
  508. if (mu_address_create (&from_addr, from))
  509. from_addr = NULL;
  510. }
  511. else
  512. {
  513. from_addr = NULL;
  514. }
  515. if (mu_rfc2822_in_reply_to (msg, &value) == 0)
  516. {
  517. mu_header_set_value (newhdr, MU_HEADER_IN_REPLY_TO, value, 1);
  518. free (value);
  519. }
  520. if (mu_rfc2822_references (msg, &value) == 0)
  521. {
  522. mu_header_set_value (newhdr, MU_HEADER_REFERENCES, value, 1);
  523. free (value);
  524. }
  525. mailer = mu_sieve_get_mailer (mach);
  526. if (mailer)
  527. {
  528. rc = mu_mailer_send_message (mailer, newmsg, from_addr, to_addr);
  529. }
  530. else
  531. rc = MU_ERR_FAILURE;
  532. }
  533. mu_address_destroy (&to_addr);
  534. mu_address_destroy (&from_addr);
  535. mu_mime_destroy (&mime);
  536. return rc;
  537. }
  538. int
  539. sieve_action_vacation (mu_sieve_machine_t mach, mu_list_t args, mu_list_t tags)
  540. {
  541. int rc;
  542. char *text, *from;
  543. mu_sieve_value_t *val;
  544. mu_message_t msg;
  545. mu_header_t hdr;
  546. char *my_address = mu_sieve_get_daemon_email (mach);
  547. if (diag (mach))
  548. return 0;
  549. val = mu_sieve_value_get (args, 0);
  550. if (!val)
  551. {
  552. mu_sieve_error (mach, _("cannot get text!"));
  553. mu_sieve_abort (mach);
  554. }
  555. else
  556. text = val->v.string;
  557. msg = mu_sieve_get_message (mach);
  558. mu_message_get_header (msg, &hdr);
  559. if (mu_sieve_tag_lookup (tags, "sender", &val))
  560. {
  561. /* Debugging hook: :sender sets fake reply address */
  562. from = strdup (val->v.string);
  563. if (!from)
  564. {
  565. mu_sieve_error (mach, "%lu: %s",
  566. (unsigned long) mu_sieve_get_message_num (mach),
  567. mu_strerror (ENOMEM));
  568. mu_sieve_abort (mach);
  569. }
  570. }
  571. else if (mu_sieve_get_message_sender (msg, &from))
  572. {
  573. mu_sieve_error (mach,
  574. _("%lu: cannot get sender address"),
  575. (unsigned long) mu_sieve_get_message_num (mach));
  576. mu_sieve_abort (mach);
  577. }
  578. if (mu_sieve_tag_lookup (tags, "aliases", &val)
  579. && match_addresses (hdr, val, &my_address) == 0)
  580. return 0;
  581. if (noreply_address_p (mach, tags, from)
  582. || bulk_precedence_p (hdr)
  583. || check_db (mach, tags, from))
  584. {
  585. free (from);
  586. return 0;
  587. }
  588. rc = vacation_reply (mach, tags, msg, text, from, my_address);
  589. free (from);
  590. if (rc == -1)
  591. mu_sieve_abort (mach);
  592. return rc;
  593. }
  594. /* Tagged arguments: */
  595. static mu_sieve_tag_def_t vacation_tags[] = {
  596. {"days", SVT_NUMBER},
  597. {"subject", SVT_STRING},
  598. {"aliases", SVT_STRING_LIST},
  599. {"addresses", SVT_STRING_LIST},
  600. {"reply_regex", SVT_STRING},
  601. {"reply_prefix", SVT_STRING},
  602. {"sender", SVT_STRING},
  603. {"mime", SVT_VOID},
  604. {"file", SVT_VOID},
  605. {NULL}
  606. };
  607. static mu_sieve_tag_group_t vacation_tag_groups[] = {
  608. {vacation_tags, NULL},
  609. {NULL}
  610. };
  611. /* Required arguments: */
  612. static mu_sieve_data_type vacation_args[] = {
  613. SVT_STRING, /* message text */
  614. SVT_VOID
  615. };
  616. int SIEVE_EXPORT (vacation, init) (mu_sieve_machine_t mach)
  617. {
  618. return mu_sieve_register_action (mach, "vacation", sieve_action_vacation,
  619. vacation_args, vacation_tag_groups, 1);
  620. }