#include "lib.h"
#include "array.h"
#include "str.h"
#include "ioloop.h"
#include "str-sanitize.h"
#include "message-date.h"
#include "mail-storage.h"
#include "sieve-common.h"
#include "sieve-address.h"
#include "sieve-message.h"
#include "sieve-smtp.h"
#include "sieve-ext-enotify.h"
#include "rfc2822.h"
#include "uri-mailto.h"
#define NTFY_MAILTO_MAX_RECIPIENTS 8
#define NTFY_MAILTO_MAX_HEADERS 16
#define NTFY_MAILTO_MAX_SUBJECT 256
static bool ntfy_mailto_compile_check_uri
(const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body);
static bool ntfy_mailto_compile_check_from
(const struct sieve_enotify_env *nenv, string_t *from);
static const char *ntfy_mailto_runtime_get_notify_capability
(const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body,
const char *capability);
static bool ntfy_mailto_runtime_check_uri
(const struct sieve_enotify_env *nenv, const char *uri, const char *uri_body);
static bool ntfy_mailto_runtime_check_operands
(const struct sieve_enotify_env *nenv, const char *uri,const char *uri_body,
string_t *message, string_t *from, pool_t context_pool,
void **method_context);
static int ntfy_mailto_action_check_duplicates
(const struct sieve_enotify_env *nenv,
const struct sieve_enotify_action *nact,
const struct sieve_enotify_action *nact_other);
static void ntfy_mailto_action_print
(const struct sieve_enotify_print_env *penv,
const struct sieve_enotify_action *nact);
static bool ntfy_mailto_action_execute
(const struct sieve_enotify_exec_env *nenv,
const struct sieve_enotify_action *nact);
const struct sieve_enotify_method_def mailto_notify = {
"mailto",
NULL, NULL,
ntfy_mailto_compile_check_uri,
NULL,
ntfy_mailto_compile_check_from,
NULL,
ntfy_mailto_runtime_check_uri,
ntfy_mailto_runtime_get_notify_capability,
ntfy_mailto_runtime_check_operands,
NULL,
ntfy_mailto_action_check_duplicates,
ntfy_mailto_action_print,
ntfy_mailto_action_execute
};
static const char *_reserved_headers[] = {
"auto-submitted",
"received",
"message-id",
"data",
"bcc",
"in-reply-to",
"references",
"resent-date",
"resent-from",
"resent-sender",
"resent-to",
"resent-cc",
"resent-bcc",
"resent-msg-id",
"from",
"sender",
NULL
};
static const char *_unique_headers[] = {
"reply-to",
NULL
};
struct ntfy_mailto_context {
struct uri_mailto *uri;
const char *from_normalized;
};
static bool ntfy_mailto_compile_check_uri
(const struct sieve_enotify_env *nenv, const char *uri ATTR_UNUSED,
const char *uri_body)
{
return uri_mailto_validate
(uri_body, _reserved_headers, _unique_headers,
NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, nenv->ehandler);
}
static bool ntfy_mailto_compile_check_from
(const struct sieve_enotify_env *nenv, string_t *from)
{
const char *error;
bool result = FALSE;
T_BEGIN {
result = sieve_address_validate(from, &error);
if ( !result ) {
sieve_enotify_error(nenv,
"specified :from address '%s' is invalid for "
"the mailto method: %s",
str_sanitize(str_c(from), 128), error);
}
} T_END;
return result;
}
static const char *ntfy_mailto_runtime_get_notify_capability
(const struct sieve_enotify_env *nenv ATTR_UNUSED, const char *uri ATTR_UNUSED,
const char *uri_body, const char *capability)
{
if ( !uri_mailto_validate(uri_body, _reserved_headers, _unique_headers,
NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, NULL) ) {
return NULL;
}
if ( strcasecmp(capability, "online") == 0 )
return "maybe";
return NULL;
}
static bool ntfy_mailto_runtime_check_uri
(const struct sieve_enotify_env *nenv ATTR_UNUSED, const char *uri ATTR_UNUSED,
const char *uri_body)
{
return uri_mailto_validate
(uri_body, _reserved_headers, _unique_headers,
NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS, NULL);
}
static bool ntfy_mailto_runtime_check_operands
(const struct sieve_enotify_env *nenv, const char *uri ATTR_UNUSED,
const char *uri_body, string_t *message ATTR_UNUSED, string_t *from,
pool_t context_pool, void **method_context)
{
struct ntfy_mailto_context *mtctx;
struct uri_mailto *parsed_uri;
const char *error, *normalized;
mtctx = p_new(context_pool, struct ntfy_mailto_context, 1);
if ( from != NULL ) {
T_BEGIN {
normalized = sieve_address_normalize(from, &error);
if ( normalized == NULL ) {
sieve_enotify_error(nenv,
"specified :from address '%s' is invalid for "
"the mailto method: %s",
str_sanitize(str_c(from), 128), error);
} else
mtctx->from_normalized = p_strdup(context_pool, normalized);
} T_END;
if ( !normalized ) return FALSE;
}
if ( (parsed_uri=uri_mailto_parse
(uri_body, context_pool, _reserved_headers,
_unique_headers, NTFY_MAILTO_MAX_RECIPIENTS, NTFY_MAILTO_MAX_HEADERS,
nenv->ehandler)) == NULL ) {
return FALSE;
}
mtctx->uri = parsed_uri;
*method_context = (void *) mtctx;
return TRUE;
}
static int ntfy_mailto_action_check_duplicates
(const struct sieve_enotify_env *nenv ATTR_UNUSED,
const struct sieve_enotify_action *nact,
const struct sieve_enotify_action *nact_other)
{
struct ntfy_mailto_context *mtctx =
(struct ntfy_mailto_context *) nact->method_context;
struct ntfy_mailto_context *mtctx_other =
(struct ntfy_mailto_context *) nact_other->method_context;
const struct uri_mailto_recipient *new_rcpts, *old_rcpts;
unsigned int new_count, old_count, i, j;
unsigned int del_start = 0, del_len = 0;
new_rcpts = array_get(&mtctx->uri->recipients, &new_count);
old_rcpts = array_get(&mtctx_other->uri->recipients, &old_count);
for ( i = 0; i < new_count; i++ ) {
for ( j = 0; j < old_count; j++ ) {
if ( sieve_address_compare
(new_rcpts[i].normalized, old_rcpts[j].normalized, TRUE) == 0 )
break;
}
if ( j == old_count ) {
if ( del_len > 0 ) {
array_delete(&mtctx->uri->recipients, del_start, del_len);
i -= del_len;
new_rcpts = array_get(&mtctx->uri->recipients, &new_count);
}
del_len = 0;
} else {
if ( del_len == 0 )
del_start = i;
del_len++;
}
}
if ( del_len > 0 ) {
array_delete(&mtctx->uri->recipients, del_start, del_len);
}
return ( array_count(&mtctx->uri->recipients) > 0 ? 0 : 1 );
}
static void ntfy_mailto_action_print
(const struct sieve_enotify_print_env *penv,
const struct sieve_enotify_action *nact)
{
unsigned int count, i;
const struct uri_mailto_recipient *recipients;
const struct uri_mailto_header_field *headers;
struct ntfy_mailto_context *mtctx =
(struct ntfy_mailto_context *) nact->method_context;
sieve_enotify_method_printf
(penv, " => importance : %d\n", nact->importance);
if ( nact->message != NULL )
sieve_enotify_method_printf
(penv, " => subject : %s\n", nact->message);
else if ( mtctx->uri->subject != NULL )
sieve_enotify_method_printf
(penv, " => subject : %s\n", mtctx->uri->subject);
if ( nact->from != NULL )
sieve_enotify_method_printf
(penv, " => from : %s\n", nact->from);
sieve_enotify_method_printf(penv, " => recipients :\n" );
recipients = array_get(&mtctx->uri->recipients, &count);
if ( count == 0 ) {
sieve_enotify_method_printf(penv, " NONE, action has no effect\n");
} else {
for ( i = 0; i < count; i++ ) {
if ( recipients[i].carbon_copy )
sieve_enotify_method_printf
(penv, " + Cc: %s\n", recipients[i].full);
else
sieve_enotify_method_printf
(penv, " + To: %s\n", recipients[i].full);
}
}
headers = array_get(&mtctx->uri->headers, &count);
if ( count > 0 ) {
sieve_enotify_method_printf(penv, " => headers :\n" );
for ( i = 0; i < count; i++ ) {
sieve_enotify_method_printf(penv, " + %s: %s\n",
headers[i].name, headers[i].body);
}
}
if ( mtctx->uri->body != NULL )
sieve_enotify_method_printf
(penv, " => body : \n--\n%s\n--\n", mtctx->uri->body);
sieve_enotify_method_printf(penv, "\n");
}
static bool _contains_8bit(const char *msg)
{
const unsigned char *s = (const unsigned char *)msg;
for (; *s != '\0'; s++) {
if ((*s & 0x80) != 0)
return TRUE;
}
return FALSE;
}
static bool ntfy_mailto_send
(const struct sieve_enotify_exec_env *nenv,
const struct sieve_enotify_action *nact, const char *recipient)
{
const struct sieve_message_data *msgdata = nenv->msgdata;
const struct sieve_script_env *senv = nenv->scriptenv;
struct ntfy_mailto_context *mtctx =
(struct ntfy_mailto_context *) nact->method_context;
const char *from = NULL, *from_smtp = NULL;
const char *subject = mtctx->uri->subject;
const char *body = mtctx->uri->body;
string_t *to, *cc;
const struct uri_mailto_recipient *recipients;
void *smtp_handle;
unsigned int count, i;
FILE *f;
const char *outmsgid;
recipients = array_get(&mtctx->uri->recipients, &count);
if ( count == 0 ) {
sieve_enotify_warning(nenv,
"notify mailto uri specifies no recipients; action has no effect");
return TRUE;
}
if ( !sieve_smtp_available(senv) ) {
sieve_enotify_global_warning(nenv,
"notify mailto method has no means to send mail");
return TRUE;
}
if ( nact->from == NULL ) {
from = t_strdup_printf("Postmaster <%s>", senv->postmaster_address);
} else {
from = nact->from;
}
if ( sieve_message_get_sender(nenv->msgctx) != NULL ) {
if ( mtctx->from_normalized == NULL ) {
from_smtp = senv->postmaster_address;
} else {
from_smtp = mtctx->from_normalized;
}
}
if ( nact->message != NULL ) {
subject = str_sanitize(nact->message, NTFY_MAILTO_MAX_SUBJECT);
} else if ( subject == NULL ) {
const char *const *hsubject;
if ( mail_get_headers_utf8
(msgdata->mail, "subject", &hsubject) >= 0 )
subject = str_sanitize(t_strdup_printf("Notification: %s", hsubject[0]),
NTFY_MAILTO_MAX_SUBJECT);
else
subject = "Notification: (no subject)";
}
to = NULL;
cc = NULL;
for ( i = 0; i < count; i++ ) {
if ( recipients[i].carbon_copy ) {
if ( cc == NULL ) {
cc = t_str_new(256);
str_append(cc, recipients[i].full);
} else {
str_append(cc, ", ");
str_append(cc, recipients[i].full);
}
} else {
if ( to == NULL ) {
to = t_str_new(256);
str_append(to, recipients[i].full);
} else {
str_append(to, ", ");
str_append(to, recipients[i].full);
}
}
}
for ( i = 0; i < count; i++ ) {
const struct uri_mailto_header_field *headers;
unsigned int h, hcount;
smtp_handle = sieve_smtp_open
(senv, recipients[i].normalized, from_smtp, &f);
outmsgid = sieve_message_get_new_id(senv);
rfc2822_header_field_write(f, "X-Sieve", SIEVE_IMPLEMENTATION);
rfc2822_header_field_write(f, "Message-ID", outmsgid);
rfc2822_header_field_write(f, "Date", message_date_create(ioloop_time));
rfc2822_header_field_utf8_printf(f, "Subject", "%s", subject);
rfc2822_header_field_utf8_printf(f, "From", "%s", from);
if ( to != NULL )
rfc2822_header_field_utf8_printf(f, "To", "%s", str_c(to));
if ( cc != NULL )
rfc2822_header_field_utf8_printf(f, "Cc", "%s", str_c(cc));
rfc2822_header_field_printf(f, "Auto-Submitted",
"auto-notified; owner-email=\"%s\"", recipient);
rfc2822_header_field_write(f, "Precedence", "bulk");
switch ( nact->importance ) {
case 1:
rfc2822_header_field_write(f, "X-Priority", "1 (Highest)");
rfc2822_header_field_write(f, "Importance", "High");
break;
case 3:
rfc2822_header_field_write(f, "X-Priority", "5 (Lowest)");
rfc2822_header_field_write(f, "Importance", "Low");
break;
case 2:
default:
rfc2822_header_field_write(f, "X-Priority", "3 (Normal)");
rfc2822_header_field_write(f, "Importance", "Normal");
break;
}
headers = array_get(&mtctx->uri->headers, &hcount);
for ( h = 0; h < hcount; h++ ) {
const char *name = rfc2822_header_field_name_sanitize(headers[h].name);
rfc2822_header_field_write(f, name, headers[h].body);
}
if ( body != NULL ) {
if (_contains_8bit(body)) {
rfc2822_header_field_write(f, "MIME-Version", "1.0");
rfc2822_header_field_write
(f, "Content-Type", "text/plain; charset=UTF-8");
rfc2822_header_field_write(f, "Content-Transfer-Encoding", "8bit");
}
fprintf(f, "\r\n");
fprintf(f, "%s\r\n", body);
} else {
fprintf(f, "\r\n");
fprintf(f, "Notification of new message.\r\n");
}
if ( sieve_smtp_close(senv, smtp_handle) ) {
sieve_enotify_global_info(nenv,
"sent mail notification to <%s>",
str_sanitize(recipients[i].normalized, 80));
} else {
sieve_enotify_global_error(nenv,
"failed to send mail notification to <%s> "
"(refer to system log for more information)",
str_sanitize(recipients[i].normalized, 80));
}
}
return TRUE;
}
static bool ntfy_mailto_action_execute
(const struct sieve_enotify_exec_env *nenv,
const struct sieve_enotify_action *nact)
{
const char *const *headers;
const char *sender = sieve_message_get_sender(nenv->msgctx);
const char *recipient = sieve_message_get_final_recipient(nenv->msgctx);
if ( recipient == NULL ) {
sieve_enotify_global_warning(nenv,
"notify mailto action aborted: envelope recipient is <>");
return TRUE;
}
if ( mail_get_headers
(nenv->msgdata->mail, "auto-submitted", &headers) >= 0 ) {
const char *const *hdsp = headers;
while ( *hdsp != NULL ) {
if ( strcasecmp(*hdsp, "no") != 0 ) {
sieve_enotify_global_info(nenv,
"not sending notification for auto-submitted message from <%s>",
str_sanitize(sender, 128));
return TRUE;
}
hdsp++;
}
}
return ntfy_mailto_send(nenv, nact, recipient);
}