/src/pigeonhole/src/lib-sieve/plugins/enotify/mailto/ntfy-mailto.c
Line | Count | Source |
1 | | /* Copyright (c) Pigeonhole authors, see top-level COPYING file */ |
2 | | |
3 | | /* Notify method mailto |
4 | | * -------------------- |
5 | | * |
6 | | * Authors: Stephan Bosch |
7 | | * Specification: RFC 5436 |
8 | | * Implementation: full |
9 | | * Status: testing |
10 | | * |
11 | | */ |
12 | | |
13 | | /* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and |
14 | | draft-duerst-mailto-bis-05.txt. Should fully migrate to new specification |
15 | | when it matures. This requires modifications to the address parser (no |
16 | | whitespace allowed within the address itself) and UTF-8 support will be |
17 | | required in the URL. |
18 | | */ |
19 | | |
20 | | #include "lib.h" |
21 | | #include "array.h" |
22 | | #include "str.h" |
23 | | #include "ioloop.h" |
24 | | #include "str-sanitize.h" |
25 | | #include "ostream.h" |
26 | | #include "settings.h" |
27 | | #include "message-date.h" |
28 | | #include "mail-storage.h" |
29 | | |
30 | | #include "sieve-common.h" |
31 | | #include "sieve-limits.h" |
32 | | #include "sieve-address.h" |
33 | | #include "sieve-address-source.h" |
34 | | #include "sieve-message.h" |
35 | | #include "sieve-smtp.h" |
36 | | |
37 | | #include "sieve-ext-enotify.h" |
38 | | |
39 | | #include "rfc2822.h" |
40 | | |
41 | | #include "uri-mailto.h" |
42 | | #include "ntfy-mailto-settings.h" |
43 | | |
44 | | /* |
45 | | * Mailto notification method |
46 | | */ |
47 | | |
48 | | static int |
49 | | ntfy_mailto_load(const struct sieve_enotify_method *nmth, void **context_r); |
50 | | static void |
51 | | ntfy_mailto_unload(const struct sieve_enotify_method *nmth); |
52 | | |
53 | | static bool |
54 | | ntfy_mailto_compile_check_uri(const struct sieve_enotify_env *nenv, |
55 | | const char *uri, const char *uri_body); |
56 | | static bool |
57 | | ntfy_mailto_compile_check_from(const struct sieve_enotify_env *nenv, |
58 | | string_t *from); |
59 | | |
60 | | static const char * |
61 | | ntfy_mailto_runtime_get_notify_capability(const struct sieve_enotify_env *nenv, |
62 | | const char *uri, const char *uri_body, |
63 | | const char *capability); |
64 | | static bool |
65 | | ntfy_mailto_runtime_check_uri(const struct sieve_enotify_env *nenv, |
66 | | const char *uri, const char *uri_body); |
67 | | static bool |
68 | | ntfy_mailto_runtime_check_operands(const struct sieve_enotify_env *nenv, |
69 | | const char *uri, const char *uri_body, |
70 | | string_t *message, string_t *from, |
71 | | pool_t context_pool, void **method_context); |
72 | | |
73 | | static int |
74 | | ntfy_mailto_action_check_duplicates( |
75 | | const struct sieve_enotify_env *nenv, |
76 | | const struct sieve_enotify_action *nact, |
77 | | const struct sieve_enotify_action *nact_other); |
78 | | |
79 | | static void |
80 | | ntfy_mailto_action_print(const struct sieve_enotify_print_env *penv, |
81 | | const struct sieve_enotify_action *nact); |
82 | | |
83 | | static int |
84 | | ntfy_mailto_action_execute(const struct sieve_enotify_exec_env *nenv, |
85 | | const struct sieve_enotify_action *nact); |
86 | | |
87 | | const struct sieve_enotify_method_def mailto_notify = { |
88 | | "mailto", |
89 | | ntfy_mailto_load, |
90 | | ntfy_mailto_unload, |
91 | | ntfy_mailto_compile_check_uri, |
92 | | NULL, |
93 | | ntfy_mailto_compile_check_from, |
94 | | NULL, |
95 | | ntfy_mailto_runtime_check_uri, |
96 | | ntfy_mailto_runtime_get_notify_capability, |
97 | | ntfy_mailto_runtime_check_operands, |
98 | | NULL, |
99 | | ntfy_mailto_action_check_duplicates, |
100 | | ntfy_mailto_action_print, |
101 | | ntfy_mailto_action_execute, |
102 | | }; |
103 | | |
104 | | /* |
105 | | * Reserved and unique headers |
106 | | */ |
107 | | |
108 | | static const char *_reserved_headers[] = { |
109 | | "auto-submitted", |
110 | | "received", |
111 | | "message-id", |
112 | | "data", |
113 | | "bcc", |
114 | | "in-reply-to", |
115 | | "references", |
116 | | "resent-date", |
117 | | "resent-from", |
118 | | "resent-sender", |
119 | | "resent-to", |
120 | | "resent-cc", |
121 | | "resent-bcc", |
122 | | "resent-msg-id", |
123 | | "from", |
124 | | "sender", |
125 | | NULL |
126 | | }; |
127 | | |
128 | | static const char *_unique_headers[] = { |
129 | | "reply-to", |
130 | | NULL |
131 | | }; |
132 | | |
133 | | /* |
134 | | * Method context data |
135 | | */ |
136 | | |
137 | | struct ntfy_mailto_action_context { |
138 | | struct uri_mailto *uri; |
139 | | const struct smtp_address *from_address; |
140 | | }; |
141 | | |
142 | | /* |
143 | | * Method registration |
144 | | */ |
145 | | |
146 | | struct ntfy_mailto_context { |
147 | | const struct ntfy_mailto_settings *set; |
148 | | }; |
149 | | |
150 | | static inline const struct ntfy_mailto_settings * |
151 | | ntfy_mailto_get_settings(const struct sieve_enotify_env *nenv) |
152 | 0 | { |
153 | 0 | const struct ntfy_mailto_context *mtctx = nenv->method->context; |
154 | |
|
155 | 0 | return mtctx->set; |
156 | 0 | } |
157 | | |
158 | | static int |
159 | | ntfy_mailto_load(const struct sieve_enotify_method *nmth, void **context_r) |
160 | 0 | { |
161 | 0 | struct sieve_instance *svinst = nmth->svinst; |
162 | 0 | const struct ntfy_mailto_settings *set; |
163 | 0 | struct ntfy_mailto_context *mtctx; |
164 | 0 | const char *error; |
165 | |
|
166 | 0 | if (settings_get(svinst->event, &ntfy_mailto_setting_parser_info, 0, |
167 | 0 | &set, &error) < 0) { |
168 | 0 | e_error(svinst->event, "%s", error); |
169 | 0 | return -1; |
170 | 0 | } |
171 | | |
172 | 0 | mtctx = i_new(struct ntfy_mailto_context, 1); |
173 | 0 | mtctx->set = set; |
174 | |
|
175 | 0 | *context_r = mtctx; |
176 | 0 | return 0; |
177 | 0 | } |
178 | | |
179 | | static void ntfy_mailto_unload(const struct sieve_enotify_method *nmth) |
180 | 0 | { |
181 | 0 | struct ntfy_mailto_context *mtctx = nmth->context; |
182 | |
|
183 | 0 | if (mtctx == NULL) |
184 | 0 | return; |
185 | 0 | settings_free(mtctx->set); |
186 | 0 | i_free(mtctx); |
187 | 0 | } |
188 | | |
189 | | /* |
190 | | * URI parsing |
191 | | */ |
192 | | |
193 | | struct ntfy_mailto_uri_env { |
194 | | const struct sieve_enotify_env *nenv; |
195 | | |
196 | | struct event *event; |
197 | | |
198 | | struct uri_mailto_log uri_log; |
199 | | }; |
200 | | |
201 | | static void ATTR_FORMAT(5, 0) |
202 | | ntfy_mailto_uri_logv(void *context, enum log_type log_type, |
203 | | const char *csrc_filename, unsigned int csrc_linenum, |
204 | | const char *fmt, va_list args) |
205 | 0 | { |
206 | 0 | struct ntfy_mailto_uri_env *nmuenv = context; |
207 | 0 | const struct sieve_enotify_env *nenv = nmuenv->nenv; |
208 | |
|
209 | 0 | sieve_event_logv(nenv->svinst, nenv->ehandler, nmuenv->event, |
210 | 0 | log_type, csrc_filename, csrc_linenum, |
211 | 0 | nenv->location, 0, fmt, args); |
212 | 0 | } |
213 | | |
214 | | static void |
215 | | ntfy_mailto_uri_env_init(struct ntfy_mailto_uri_env *nmuenv, |
216 | | const struct sieve_enotify_env *nenv) |
217 | 0 | { |
218 | 0 | i_zero(nmuenv); |
219 | 0 | nmuenv->nenv = nenv; |
220 | 0 | nmuenv->event = event_create(nenv->event); |
221 | 0 | event_set_append_log_prefix(nmuenv->event, "mailto URI: "); |
222 | |
|
223 | 0 | nmuenv->uri_log.context = nmuenv; |
224 | 0 | nmuenv->uri_log.logv = ntfy_mailto_uri_logv; |
225 | 0 | } |
226 | | |
227 | | static void ntfy_mailto_uri_env_deinit(struct ntfy_mailto_uri_env *nmuenv) |
228 | 0 | { |
229 | 0 | event_unref(&nmuenv->event); |
230 | 0 | } |
231 | | |
232 | | /* |
233 | | * Validation |
234 | | */ |
235 | | |
236 | | |
237 | | static bool |
238 | | ntfy_mailto_compile_check_uri(const struct sieve_enotify_env *nenv, |
239 | | const char *uri ATTR_UNUSED, const char *uri_body) |
240 | 0 | { |
241 | 0 | const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv); |
242 | 0 | struct ntfy_mailto_uri_env nmuenv; |
243 | 0 | bool result; |
244 | |
|
245 | 0 | ntfy_mailto_uri_env_init(&nmuenv, nenv); |
246 | 0 | result = uri_mailto_validate( |
247 | 0 | uri_body, _reserved_headers, _unique_headers, |
248 | 0 | set->max_recipients, set->max_headers, |
249 | 0 | &nmuenv.uri_log); |
250 | 0 | ntfy_mailto_uri_env_deinit(&nmuenv); |
251 | |
|
252 | 0 | return result; |
253 | 0 | } |
254 | | |
255 | | static bool |
256 | | ntfy_mailto_compile_check_from(const struct sieve_enotify_env *nenv, |
257 | | string_t *from) |
258 | 0 | { |
259 | 0 | const char *error; |
260 | 0 | bool result = FALSE; |
261 | |
|
262 | 0 | T_BEGIN { |
263 | 0 | result = sieve_address_validate_str(from, &error); |
264 | 0 | if (!result) { |
265 | 0 | sieve_enotify_error( |
266 | 0 | nenv, |
267 | 0 | "specified :from address '%s' is invalid for " |
268 | 0 | "the mailto method: %s", |
269 | 0 | str_sanitize(str_c(from), 128), error); |
270 | 0 | } |
271 | 0 | } T_END; |
272 | | |
273 | 0 | return result; |
274 | 0 | } |
275 | | |
276 | | /* |
277 | | * Runtime |
278 | | */ |
279 | | |
280 | | struct ntfy_mailto_runtime_env { |
281 | | const struct sieve_enotify_env *nenv; |
282 | | |
283 | | struct event *event; |
284 | | }; |
285 | | |
286 | | static const char * |
287 | | ntfy_mailto_runtime_get_notify_capability( |
288 | | const struct sieve_enotify_env *nenv, |
289 | | const char *uri ATTR_UNUSED, const char *uri_body, |
290 | | const char *capability) |
291 | 0 | { |
292 | 0 | const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv); |
293 | |
|
294 | 0 | if (!uri_mailto_validate(uri_body, _reserved_headers, _unique_headers, |
295 | 0 | set->max_recipients, set->max_headers, NULL)) |
296 | 0 | return NULL; |
297 | | |
298 | 0 | if (strcasecmp(capability, "online") == 0) |
299 | 0 | return "maybe"; |
300 | | |
301 | 0 | return NULL; |
302 | 0 | } |
303 | | |
304 | | static bool |
305 | | ntfy_mailto_runtime_check_uri(const struct sieve_enotify_env *nenv, |
306 | | const char *uri ATTR_UNUSED, const char *uri_body) |
307 | 0 | { |
308 | 0 | const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv); |
309 | |
|
310 | 0 | return uri_mailto_validate(uri_body, _reserved_headers, _unique_headers, |
311 | 0 | set->max_recipients, set->max_headers, NULL); |
312 | 0 | } |
313 | | |
314 | | static bool |
315 | | ntfy_mailto_runtime_check_operands(const struct sieve_enotify_env *nenv, |
316 | | const char *uri ATTR_UNUSED, |
317 | | const char *uri_body, |
318 | | string_t *message ATTR_UNUSED, |
319 | | string_t *from, pool_t context_pool, |
320 | | void **method_context) |
321 | 0 | { |
322 | 0 | const struct ntfy_mailto_settings *set = ntfy_mailto_get_settings(nenv); |
323 | 0 | struct ntfy_mailto_action_context *mtactx; |
324 | 0 | struct uri_mailto *parsed_uri; |
325 | 0 | const struct smtp_address *address; |
326 | 0 | struct ntfy_mailto_uri_env nmuenv; |
327 | 0 | const char *error; |
328 | | |
329 | | /* Need to create context before validation to have arrays present */ |
330 | 0 | mtactx = p_new(context_pool, struct ntfy_mailto_action_context, 1); |
331 | | |
332 | | /* Validate :from */ |
333 | 0 | if (from != NULL) { |
334 | 0 | T_BEGIN { |
335 | 0 | address = sieve_address_parse_str(from, &error); |
336 | 0 | if (address == NULL) { |
337 | 0 | sieve_enotify_error( |
338 | 0 | nenv, |
339 | 0 | "specified :from address '%s' is invalid for " |
340 | 0 | "the mailto method: %s", |
341 | 0 | str_sanitize(str_c(from), 128), error); |
342 | 0 | } else { |
343 | 0 | mtactx->from_address = |
344 | 0 | smtp_address_clone(context_pool, address); |
345 | 0 | } |
346 | 0 | } T_END; |
347 | | |
348 | 0 | if (address == NULL) |
349 | 0 | return FALSE; |
350 | 0 | } |
351 | | |
352 | 0 | ntfy_mailto_uri_env_init(&nmuenv, nenv); |
353 | 0 | parsed_uri = uri_mailto_parse(uri_body, context_pool, |
354 | 0 | _reserved_headers, _unique_headers, |
355 | 0 | set->max_recipients, set->max_headers, |
356 | 0 | &nmuenv.uri_log); |
357 | 0 | ntfy_mailto_uri_env_deinit(&nmuenv); |
358 | |
|
359 | 0 | if (parsed_uri == NULL) |
360 | 0 | return FALSE; |
361 | | |
362 | 0 | mtactx->uri = parsed_uri; |
363 | 0 | *method_context = mtactx; |
364 | 0 | return TRUE; |
365 | 0 | } |
366 | | |
367 | | /* |
368 | | * Action duplicates |
369 | | */ |
370 | | |
371 | | static int |
372 | | ntfy_mailto_action_check_duplicates( |
373 | | const struct sieve_enotify_env *nenv ATTR_UNUSED, |
374 | | const struct sieve_enotify_action *nact, |
375 | | const struct sieve_enotify_action *nact_other) |
376 | 0 | { |
377 | 0 | struct ntfy_mailto_action_context *mtactx = |
378 | 0 | nact->method_context; |
379 | 0 | struct ntfy_mailto_action_context *mtactx_other = |
380 | 0 | nact_other->method_context; |
381 | 0 | const struct uri_mailto_recipient *new_rcpts, *old_rcpts; |
382 | 0 | unsigned int new_count, old_count, i, j; |
383 | 0 | unsigned int del_start = 0, del_len = 0; |
384 | |
|
385 | 0 | new_rcpts = array_get(&mtactx->uri->recipients, &new_count); |
386 | 0 | old_rcpts = array_get(&mtactx_other->uri->recipients, &old_count); |
387 | |
|
388 | 0 | for (i = 0; i < new_count; i++) { |
389 | 0 | for (j = 0; j < old_count; j++) { |
390 | 0 | if (smtp_address_equals(new_rcpts[i].address, |
391 | 0 | old_rcpts[j].address)) |
392 | 0 | break; |
393 | 0 | } |
394 | |
|
395 | 0 | if (j == old_count) { |
396 | | /* Not duplicate */ |
397 | 0 | if (del_len > 0) { |
398 | | /* Perform pending deletion */ |
399 | 0 | array_delete(&mtactx->uri->recipients, |
400 | 0 | del_start, del_len); |
401 | | |
402 | | /* Make sure the loop integrity is maintained */ |
403 | 0 | i -= del_len; |
404 | 0 | new_rcpts = array_get(&mtactx->uri->recipients, |
405 | 0 | &new_count); |
406 | 0 | } |
407 | 0 | del_len = 0; |
408 | 0 | } else { |
409 | | /* Mark deletion */ |
410 | 0 | if (del_len == 0) |
411 | 0 | del_start = i; |
412 | 0 | del_len++; |
413 | 0 | } |
414 | 0 | } |
415 | | |
416 | | /* Perform pending deletion */ |
417 | 0 | if (del_len > 0) |
418 | 0 | array_delete(&mtactx->uri->recipients, del_start, del_len); |
419 | 0 | return (array_count(&mtactx->uri->recipients) > 0 ? 0 : 1); |
420 | 0 | } |
421 | | |
422 | | /* |
423 | | * Action printing |
424 | | */ |
425 | | |
426 | | static void |
427 | | ntfy_mailto_action_print(const struct sieve_enotify_print_env *penv, |
428 | | const struct sieve_enotify_action *nact) |
429 | 0 | { |
430 | 0 | unsigned int count, i; |
431 | 0 | const struct uri_mailto_recipient *recipients; |
432 | 0 | const struct uri_mailto_header_field *headers; |
433 | 0 | struct ntfy_mailto_action_context *mtactx = nact->method_context; |
434 | | |
435 | | /* Print main method parameters */ |
436 | |
|
437 | 0 | sieve_enotify_method_printf(penv, " => importance : %llu\n", |
438 | 0 | (unsigned long long)nact->importance); |
439 | |
|
440 | 0 | if (nact->message != NULL) { |
441 | 0 | sieve_enotify_method_printf( |
442 | 0 | penv, " => subject : %s\n", |
443 | 0 | nact->message); |
444 | 0 | } else if (mtactx->uri->subject != NULL) { |
445 | 0 | sieve_enotify_method_printf( |
446 | 0 | penv, " => subject : %s\n", |
447 | 0 | mtactx->uri->subject); |
448 | 0 | } |
449 | |
|
450 | 0 | if (nact->from != NULL) { |
451 | 0 | sieve_enotify_method_printf( |
452 | 0 | penv, " => from : %s\n", nact->from); |
453 | 0 | } |
454 | | |
455 | | /* Print mailto: recipients */ |
456 | |
|
457 | 0 | sieve_enotify_method_printf(penv, " => recipients :\n"); |
458 | |
|
459 | 0 | recipients = array_get(&mtactx->uri->recipients, &count); |
460 | 0 | if (count == 0) { |
461 | 0 | sieve_enotify_method_printf( |
462 | 0 | penv, " NONE, action has no effect\n"); |
463 | 0 | } else { |
464 | 0 | for (i = 0; i < count; i++) { |
465 | 0 | if (recipients[i].carbon_copy) { |
466 | 0 | sieve_enotify_method_printf( |
467 | 0 | penv, " + Cc: %s\n", |
468 | 0 | recipients[i].full); |
469 | 0 | } else { |
470 | 0 | sieve_enotify_method_printf( |
471 | 0 | penv, " + To: %s\n", |
472 | 0 | recipients[i].full); |
473 | 0 | } |
474 | 0 | } |
475 | 0 | } |
476 | | |
477 | | /* Print accepted headers for notification message */ |
478 | |
|
479 | 0 | headers = array_get(&mtactx->uri->headers, &count); |
480 | 0 | if (count > 0) { |
481 | 0 | sieve_enotify_method_printf(penv, " => headers :\n"); |
482 | 0 | for (i = 0; i < count; i++) { |
483 | 0 | sieve_enotify_method_printf( |
484 | 0 | penv, " + %s: %s\n", |
485 | 0 | headers[i].name, headers[i].body); |
486 | 0 | } |
487 | 0 | } |
488 | | |
489 | | /* Print body for notification message */ |
490 | |
|
491 | 0 | if (mtactx->uri->body != NULL) { |
492 | 0 | sieve_enotify_method_printf( |
493 | 0 | penv, " => body : \n--\n%s\n--\n", |
494 | 0 | mtactx->uri->body); |
495 | 0 | } |
496 | | |
497 | | /* Finish output with an empty line */ |
498 | |
|
499 | 0 | sieve_enotify_method_printf(penv, "\n"); |
500 | 0 | } |
501 | | |
502 | | /* |
503 | | * Action execution |
504 | | */ |
505 | | |
506 | | static bool _contains_8bit(const char *msg) |
507 | 0 | { |
508 | 0 | const unsigned char *s = (const unsigned char *)msg; |
509 | |
|
510 | 0 | for (; *s != '\0'; s++) { |
511 | 0 | if ((*s & 0x80) != 0) |
512 | 0 | return TRUE; |
513 | 0 | } |
514 | 0 | return FALSE; |
515 | 0 | } |
516 | | |
517 | | static int |
518 | | ntfy_mailto_send(const struct sieve_enotify_exec_env *nenv, |
519 | | const struct sieve_enotify_action *nact, |
520 | | const struct smtp_address *owner_email) |
521 | 0 | { |
522 | 0 | struct sieve_instance *svinst = nenv->svinst; |
523 | 0 | const struct sieve_message_data *msgdata = nenv->msgdata; |
524 | 0 | const struct sieve_script_env *senv = nenv->scriptenv; |
525 | 0 | struct ntfy_mailto_action_context *mtactx = nact->method_context; |
526 | 0 | struct ntfy_mailto_context *mtctx = nenv->method->context; |
527 | 0 | struct sieve_address_source env_from = mtctx->set->parsed.envelope_from; |
528 | 0 | const char *from = NULL; |
529 | 0 | const struct smtp_address *from_smtp = NULL; |
530 | 0 | const char *subject = mtactx->uri->subject; |
531 | 0 | const char *body = mtactx->uri->body; |
532 | 0 | string_t *to, *cc, *all; |
533 | 0 | const struct uri_mailto_recipient *recipients; |
534 | 0 | const struct uri_mailto_header_field *headers; |
535 | 0 | struct sieve_smtp_context *sctx; |
536 | 0 | struct ostream *output; |
537 | 0 | string_t *msg; |
538 | 0 | unsigned int count, i, hcount, h; |
539 | 0 | const char *outmsgid, *error; |
540 | 0 | int ret; |
541 | | |
542 | | /* Get recipients */ |
543 | 0 | recipients = array_get(&mtactx->uri->recipients, &count); |
544 | 0 | if (count == 0) { |
545 | 0 | sieve_enotify_warning( |
546 | 0 | nenv, "notify mailto uri specifies no recipients; " |
547 | 0 | "action has no effect"); |
548 | 0 | return 0; |
549 | 0 | } |
550 | | |
551 | | /* Just to be sure */ |
552 | 0 | if (!sieve_smtp_available(senv)) { |
553 | 0 | sieve_enotify_global_warning( |
554 | 0 | nenv, |
555 | 0 | "notify mailto method has no means to send mail"); |
556 | 0 | return 0; |
557 | 0 | } |
558 | | |
559 | | /* Determine which sender to use |
560 | | |
561 | | From RFC 5436, Section 2.3: |
562 | | |
563 | | The ":from" tag overrides the default sender of the notification |
564 | | message. "Sender", here, refers to the value used in the [RFC5322] |
565 | | "From" header. Implementations MAY also use this value in the |
566 | | [RFC5321] "MAIL FROM" command (the "envelope sender"), or they may |
567 | | prefer to establish a mailbox that receives bounces from notification |
568 | | messages. |
569 | | */ |
570 | 0 | if ((nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) { |
571 | 0 | from_smtp = sieve_message_get_sender(nenv->msgctx); |
572 | 0 | if (from_smtp == NULL) { |
573 | | /* "<>" */ |
574 | 0 | i_zero(&env_from); |
575 | 0 | env_from.type = SIEVE_ADDRESS_SOURCE_EXPLICIT; |
576 | 0 | } |
577 | 0 | } |
578 | 0 | from = nact->from; |
579 | 0 | ret = sieve_address_source_get_address( |
580 | 0 | &env_from, svinst, senv, nenv->msgctx, nenv->flags, &from_smtp); |
581 | 0 | if (ret < 0) { |
582 | 0 | from_smtp = NULL; |
583 | 0 | } else if (ret == 0) { |
584 | 0 | if (mtactx->from_address != NULL) |
585 | 0 | from_smtp = mtactx->from_address; |
586 | 0 | else if (svinst->set->parsed.user_email != NULL) |
587 | 0 | from_smtp = svinst->set->parsed.user_email; |
588 | 0 | else { |
589 | 0 | from_smtp = sieve_get_postmaster_smtp(senv); |
590 | 0 | if (from == NULL) |
591 | 0 | from = sieve_get_postmaster_address(senv); |
592 | 0 | } |
593 | 0 | } |
594 | | |
595 | | /* Determine message from address */ |
596 | 0 | if (from == NULL) { |
597 | 0 | if (from_smtp == NULL) |
598 | 0 | from = sieve_get_postmaster_address(senv); |
599 | 0 | else { |
600 | 0 | from = t_strdup_printf("<%s>", |
601 | 0 | smtp_address_encode(from_smtp)); |
602 | 0 | } |
603 | 0 | } |
604 | | |
605 | | /* Determine subject */ |
606 | 0 | if (nact->message != NULL) { |
607 | 0 | subject = str_sanitize_utf8( |
608 | 0 | nact->message, SIEVE_MAX_SUBJECT_HEADER_CODEPOINTS); |
609 | 0 | } else if (subject == NULL) { |
610 | 0 | const char *const *hsubject; |
611 | | |
612 | | /* Fetch subject from original message */ |
613 | 0 | if (mail_get_headers_utf8(msgdata->mail, "subject", |
614 | 0 | &hsubject) > 0) { |
615 | 0 | subject = str_sanitize_utf8( |
616 | 0 | t_strdup_printf("Notification: %s", hsubject[0]), |
617 | 0 | SIEVE_MAX_SUBJECT_HEADER_CODEPOINTS); |
618 | 0 | } else { |
619 | 0 | subject = "Notification: (no subject)"; |
620 | 0 | } |
621 | 0 | } |
622 | | |
623 | | /* Compose To and Cc headers */ |
624 | 0 | to = NULL; |
625 | 0 | cc = NULL; |
626 | 0 | all = t_str_new(256); |
627 | 0 | for (i = 0; i < count; i++) { |
628 | 0 | if (recipients[i].carbon_copy) { |
629 | 0 | if (cc == NULL) { |
630 | 0 | cc = t_str_new(256); |
631 | 0 | str_append(cc, recipients[i].full); |
632 | 0 | } else { |
633 | 0 | str_append(cc, ", "); |
634 | 0 | str_append(cc, recipients[i].full); |
635 | 0 | } |
636 | 0 | } else { |
637 | 0 | if (to == NULL) { |
638 | 0 | to = t_str_new(256); |
639 | 0 | str_append(to, recipients[i].full); |
640 | 0 | } else { |
641 | 0 | str_append(to, ", "); |
642 | 0 | str_append(to, recipients[i].full); |
643 | 0 | } |
644 | 0 | } |
645 | 0 | if (i < 3) { |
646 | 0 | if (i > 0) |
647 | 0 | str_append(all, ", "); |
648 | 0 | str_append( |
649 | 0 | all, smtp_address_encode_path( |
650 | 0 | recipients[i].address)); |
651 | 0 | } else if (i == 3) { |
652 | 0 | str_printfa(all, ", ... (%u total)", count); |
653 | 0 | } |
654 | 0 | } |
655 | |
|
656 | 0 | msg = t_str_new(512); |
657 | 0 | outmsgid = sieve_message_get_new_id(svinst); |
658 | |
|
659 | 0 | rfc2822_header_write(msg, "X-Sieve", SIEVE_IMPLEMENTATION); |
660 | 0 | rfc2822_header_write(msg, "Message-ID", outmsgid); |
661 | 0 | rfc2822_header_write(msg, "Date", message_date_create(ioloop_time)); |
662 | 0 | rfc2822_header_utf8_printf(msg, "Subject", "%s", subject); |
663 | |
|
664 | 0 | rfc2822_header_write_address(msg, "From", from); |
665 | |
|
666 | 0 | if (to != NULL) |
667 | 0 | rfc2822_header_write_address(msg, "To", str_c(to)); |
668 | 0 | if (cc != NULL) |
669 | 0 | rfc2822_header_write_address(msg, "Cc", str_c(cc)); |
670 | |
|
671 | 0 | rfc2822_header_printf(msg, "Auto-Submitted", |
672 | 0 | "auto-notified; owner-email=\"%s\"", |
673 | 0 | smtp_address_encode(owner_email)); |
674 | 0 | rfc2822_header_write(msg, "Precedence", "bulk"); |
675 | | |
676 | | /* Set importance */ |
677 | 0 | switch (nact->importance) { |
678 | 0 | case 1: |
679 | 0 | rfc2822_header_write(msg, "X-Priority", "1 (Highest)"); |
680 | 0 | rfc2822_header_write(msg, "Importance", "High"); |
681 | 0 | break; |
682 | 0 | case 3: |
683 | 0 | rfc2822_header_write(msg, "X-Priority", "5 (Lowest)"); |
684 | 0 | rfc2822_header_write(msg, "Importance", "Low"); |
685 | 0 | break; |
686 | 0 | case 2: |
687 | 0 | default: |
688 | 0 | rfc2822_header_write(msg, "X-Priority", "3 (Normal)"); |
689 | 0 | rfc2822_header_write(msg, "Importance", "Normal"); |
690 | 0 | break; |
691 | 0 | } |
692 | | |
693 | | /* Add custom headers */ |
694 | | |
695 | 0 | headers = array_get(&mtactx->uri->headers, &hcount); |
696 | 0 | for (h = 0; h < hcount; h++) { |
697 | 0 | const char *name = |
698 | 0 | rfc2822_header_field_name_sanitize(headers[h].name); |
699 | |
|
700 | 0 | rfc2822_header_write(msg, name, headers[h].body); |
701 | 0 | } |
702 | | |
703 | | /* Generate message body */ |
704 | |
|
705 | 0 | rfc2822_header_write(msg, "MIME-Version", "1.0"); |
706 | 0 | if (body != NULL) { |
707 | 0 | if (_contains_8bit(body)) { |
708 | 0 | rfc2822_header_write(msg, "Content-Type", |
709 | 0 | "text/plain; charset=utf-8"); |
710 | 0 | rfc2822_header_write(msg, "Content-Transfer-Encoding", |
711 | 0 | "8bit"); |
712 | 0 | } else { |
713 | 0 | rfc2822_header_write(msg, "Content-Type", |
714 | 0 | "text/plain; charset=us-ascii"); |
715 | 0 | rfc2822_header_write(msg, "Content-Transfer-Encoding", |
716 | 0 | "7bit"); |
717 | 0 | } |
718 | 0 | str_printfa(msg, "\r\n%s\r\n", body); |
719 | |
|
720 | 0 | } else { |
721 | 0 | rfc2822_header_write(msg, "Content-Type", |
722 | 0 | "text/plain; charset=US-ASCII"); |
723 | 0 | rfc2822_header_write(msg, "Content-Transfer-Encoding", |
724 | 0 | "7bit"); |
725 | |
|
726 | 0 | str_append(msg, "\r\nNotification of new message.\r\n"); |
727 | 0 | } |
728 | |
|
729 | 0 | sctx = sieve_smtp_start(senv, from_smtp); |
730 | | |
731 | | /* Send message to all recipients */ |
732 | 0 | for (i = 0; i < count; i++) |
733 | 0 | sieve_smtp_add_rcpt(sctx, recipients[i].address); |
734 | |
|
735 | 0 | output = sieve_smtp_send(sctx); |
736 | 0 | o_stream_nsend(output, str_data(msg), str_len(msg)); |
737 | |
|
738 | 0 | if ((ret = sieve_smtp_finish(sctx, &error)) <= 0) { |
739 | 0 | if (ret < 0) { |
740 | 0 | sieve_enotify_global_error( |
741 | 0 | nenv, "failed to send mail notification to %s: " |
742 | 0 | "%s (temporary failure)", |
743 | 0 | str_c(all), str_sanitize(error, 512)); |
744 | 0 | } else { |
745 | 0 | sieve_enotify_global_log_error( |
746 | 0 | nenv, "failed to send mail notification to %s: " |
747 | 0 | "%s (permanent failure)", |
748 | 0 | str_c(all), str_sanitize(error, 512)); |
749 | 0 | } |
750 | 0 | } else { |
751 | 0 | struct event_passthrough *e = |
752 | 0 | sieve_enotify_create_finish_event(nenv)-> |
753 | 0 | add_str("notify_target", str_c(all)); |
754 | |
|
755 | 0 | sieve_enotify_event_log(nenv, e->event(), |
756 | 0 | "sent mail notification to %s", |
757 | 0 | str_c(all)); |
758 | 0 | } |
759 | |
|
760 | 0 | return 0; |
761 | 0 | } |
762 | | |
763 | | static int |
764 | | ntfy_mailto_action_execute(const struct sieve_enotify_exec_env *nenv, |
765 | | const struct sieve_enotify_action *nact) |
766 | 0 | { |
767 | 0 | struct sieve_instance *svinst = nenv->svinst; |
768 | 0 | const struct sieve_script_env *senv = nenv->scriptenv; |
769 | 0 | struct mail *mail = nenv->msgdata->mail; |
770 | 0 | const struct smtp_address *owner_email; |
771 | 0 | const char *const *hdsp; |
772 | 0 | int ret; |
773 | |
|
774 | 0 | owner_email = svinst->set->parsed.user_email; |
775 | 0 | if (owner_email == NULL && |
776 | 0 | (nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) |
777 | 0 | owner_email = sieve_message_get_final_recipient(nenv->msgctx); |
778 | 0 | if (owner_email == NULL) |
779 | 0 | owner_email = sieve_get_postmaster_smtp(senv); |
780 | 0 | i_assert(owner_email != NULL); |
781 | | |
782 | | /* Is the message an automatic reply ? */ |
783 | 0 | ret = mail_get_headers(mail, "auto-submitted", &hdsp); |
784 | 0 | if (ret < 0) { |
785 | 0 | sieve_enotify_critical( |
786 | 0 | nenv, "mailto notification: " |
787 | 0 | "failed to read 'auto-submitted' header field", |
788 | 0 | "mailto notification: " |
789 | 0 | "failed to read 'auto-submitted' header field: %s", |
790 | 0 | mailbox_get_last_internal_error(mail->box, NULL)); |
791 | 0 | return -1; |
792 | 0 | } |
793 | | |
794 | | /* Theoretically multiple headers could exist, so lets make sure */ |
795 | 0 | if (ret > 0) { |
796 | 0 | while (*hdsp != NULL) { |
797 | 0 | if (strcasecmp(*hdsp, "no") != 0) { |
798 | 0 | const struct smtp_address *sender = NULL; |
799 | 0 | const char *from; |
800 | |
|
801 | 0 | if ((nenv->flags & SIEVE_EXECUTE_FLAG_NO_ENVELOPE) == 0) |
802 | 0 | sender = sieve_message_get_sender(nenv->msgctx); |
803 | 0 | from = (sender == NULL ? "" : |
804 | 0 | t_strdup_printf(" from <%s>", |
805 | 0 | smtp_address_encode(sender))); |
806 | |
|
807 | 0 | sieve_enotify_global_info( |
808 | 0 | nenv, "not sending notification " |
809 | 0 | "for auto-submitted message%s", from); |
810 | 0 | return 0; |
811 | 0 | } |
812 | 0 | hdsp++; |
813 | 0 | } |
814 | 0 | } |
815 | | |
816 | 0 | T_BEGIN { |
817 | 0 | ret = ntfy_mailto_send(nenv, nact, owner_email); |
818 | 0 | } T_END; |
819 | | |
820 | 0 | return ret; |
821 | 0 | } |