Mercurial > dovecot > core-2.2
view src/plugins/push-notification/push-notification-driver-ox.c @ 19552:0f22db71df7a
global: freshen copyright
git ls-files | xargs perl -p -i -e 's/(\d+)-201[0-5]/$1-2016/g;s/ (201[0-5]) Dovecot/ $1-2016 Dovecot/'
author | Timo Sirainen <timo.sirainen@dovecot.fi> |
---|---|
date | Wed, 13 Jan 2016 12:24:03 +0200 |
parents | 7a7111a5ce5e |
children | 111ca700d6bd |
line wrap: on
line source
/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "hash.h" #include "http-client.h" #include "http-url.h" #include "ioloop.h" #include "istream.h" #include "settings-parser.h" #include "json-parser.h" #include "mailbox-attribute.h" #include "mail-storage-private.h" #include "str.h" #include "push-notification-drivers.h" #include "push-notification-event-messagenew.h" #include "push-notification-events.h" #include "push-notification-txn-msg.h" #define OX_LOG_LABEL "OX Push Notification: " #define OX_METADATA_KEY \ MAILBOX_ATTRIBUTE_PREFIX_DOVECOT_PVT_SERVER "vendor/vendor.dovecot/http-notify" /* Default values. */ static const char *const default_events[] = { "MessageNew", NULL }; static const char *const default_mboxes[] = { "INBOX", NULL }; #define DEFAULT_CACHE_LIFETIME_SECS 60 #define DEFAULT_TIMEOUT_MSECS 2000 #define DEFAULT_RETRY_COUNT 1 /* This is data that is shared by all plugin users. */ struct push_notification_driver_ox_global { struct http_client *http_client; int refcount; }; static struct push_notification_driver_ox_global *ox_global = NULL; /* This is data specific to an OX driver. */ struct push_notification_driver_ox_config { struct http_url *http_url; unsigned int cached_ox_metadata_lifetime_secs; bool use_unsafe_username; unsigned int http_max_retries; unsigned int http_timeout_msecs; char *cached_ox_metadata; time_t cached_ox_metadata_timestamp; }; /* This is data specific to an OX driver transaction. */ struct push_notification_driver_ox_txn { const char *unsafe_user; }; static void push_notification_driver_ox_init_global(struct mail_user *user, struct push_notification_driver_ox_config *config) { struct http_client_settings http_set; if (ox_global->http_client == NULL) { /* this is going to use the first user's settings, but these are unlikely to change between users so it shouldn't matter much. */ memset(&http_set, 0, sizeof(http_set)); http_set.debug = user->mail_debug; http_set.max_attempts = config->http_max_retries+1; http_set.request_timeout_msecs = config->http_timeout_msecs; ox_global->http_client = http_client_init(&http_set); } } static int push_notification_driver_ox_init(struct push_notification_driver_config *config, struct mail_user *user, pool_t pool, void **context, const char **error_r) { struct push_notification_driver_ox_config *dconfig; const char *error, *tmp; /* Valid config keys: cache_lifetime, url */ tmp = hash_table_lookup(config->config, (const char *)"url"); if (tmp == NULL) { *error_r = OX_LOG_LABEL "Driver requires the url parameter"; return -1; } dconfig = p_new(pool, struct push_notification_driver_ox_config, 1); if (http_url_parse(tmp, NULL, HTTP_URL_ALLOW_USERINFO_PART, pool, &dconfig->http_url, &error) < 0) { *error_r = t_strdup_printf(OX_LOG_LABEL "Failed to parse OX REST URL %s: %s", tmp, error); return -1; } dconfig->use_unsafe_username = hash_table_lookup(config->config, (const char *)"user_from_metadata") != NULL; push_notification_driver_debug(OX_LOG_LABEL, user, "Using URL %s", tmp); tmp = hash_table_lookup(config->config, (const char *)"cache_lifetime"); if (tmp == NULL) dconfig->cached_ox_metadata_lifetime_secs = DEFAULT_CACHE_LIFETIME_SECS; else if (settings_get_time(tmp, &dconfig->cached_ox_metadata_lifetime_secs, &error) < 0) { *error_r = t_strdup_printf(OX_LOG_LABEL "Failed to parse OX cache_lifetime %s: %s", tmp, error); return -1; } tmp = hash_table_lookup(config->config, (const char *)"max_retries"); if ((tmp == NULL) || (str_to_uint(tmp, &dconfig->http_max_retries) < 0)) { dconfig->http_max_retries = DEFAULT_RETRY_COUNT; } tmp = hash_table_lookup(config->config, (const char *)"timeout_msecs"); if ((tmp == NULL) || (str_to_uint(tmp, &dconfig->http_timeout_msecs) < 0)) { dconfig->http_timeout_msecs = DEFAULT_TIMEOUT_MSECS; } push_notification_driver_debug(OX_LOG_LABEL, user, "Using cache lifetime: %u", dconfig->cached_ox_metadata_lifetime_secs); if (ox_global == NULL) { ox_global = i_new(struct push_notification_driver_ox_global, 1); ox_global->refcount = 0; } ++ox_global->refcount; *context = dconfig; return 0; } static const char *push_notification_driver_ox_get_metadata (struct push_notification_driver_txn *dtxn) { struct push_notification_driver_ox_config *dconfig = dtxn->duser->context; struct mail_attribute_value attr; struct mailbox *inbox; struct mailbox_transaction_context *mctx = NULL; struct mail_namespace *ns; bool success = FALSE, use_existing_txn = FALSE; int ret; if ((dconfig->cached_ox_metadata != NULL) && ((dconfig->cached_ox_metadata_timestamp + (time_t)dconfig->cached_ox_metadata_lifetime_secs) > ioloop_time)) { return dconfig->cached_ox_metadata; } /* Get canonical INBOX, where private server-level metadata is stored. * See imap/cmd-getmetadata.c */ if ((dtxn->ptxn->t != NULL) && dtxn->ptxn->mbox->inbox_user) { /* Use the currently open transaction. */ inbox = dtxn->ptxn->mbox; mctx = dtxn->ptxn->t; use_existing_txn = TRUE; } else { ns = mail_namespace_find_inbox(dtxn->ptxn->muser->namespaces); inbox = mailbox_alloc(ns->list, "INBOX", MAILBOX_FLAG_READONLY); if (mailbox_open(inbox) < 0) { i_error(OX_LOG_LABEL "Skipped because unable to open INBOX: %s", mailbox_get_last_error(inbox, NULL)); } else { mctx = mailbox_transaction_begin(inbox, 0); } } if (mctx != NULL) { ret = mailbox_attribute_get(mctx, MAIL_ATTRIBUTE_TYPE_PRIVATE, OX_METADATA_KEY, &attr); if (ret < 0) { i_error(OX_LOG_LABEL "Skipped because unable to get attribute: %s", mailbox_get_last_error(inbox, NULL)); } else if (ret == 0) { push_notification_driver_debug(OX_LOG_LABEL, dtxn->ptxn->muser, "Skipped because not active (/private/"OX_METADATA_KEY" METADATA not set)"); } else { success = TRUE; } if (!use_existing_txn && (mailbox_transaction_commit(&mctx) < 0)) { i_error(OX_LOG_LABEL "Transaction commit failed: %s", mailbox_get_last_error(inbox, NULL)); /* the commit doesn't matter though. */ } } if (!use_existing_txn) { mailbox_free(&inbox); } if (!success) return NULL; i_free(dconfig->cached_ox_metadata); dconfig->cached_ox_metadata = i_strdup(attr.value); dconfig->cached_ox_metadata_timestamp = ioloop_time; return dconfig->cached_ox_metadata; } static bool push_notification_driver_ox_begin_txn (struct push_notification_driver_txn *dtxn) { const char *const *args; struct push_notification_event_messagenew_config *config; const char *key, *mbox_curr, *md_value, *value; bool mbox_found = FALSE; struct push_notification_driver_ox_txn *txn; md_value = push_notification_driver_ox_get_metadata(dtxn); if (md_value == NULL) { return FALSE; } struct mail_user *user = dtxn->ptxn->muser; /* Unused keys: events, expire, folder */ /* TODO: To be implemented later(?) */ const char *const *events = default_events; time_t expire = INT_MAX; const char *const *mboxes = default_mboxes; if (expire < ioloop_time) { push_notification_driver_debug(OX_LOG_LABEL, user, "Skipped due to expiration (%ld < %ld)", (long)expire, (long)ioloop_time); return FALSE; } mbox_curr = mailbox_get_vname(dtxn->ptxn->mbox); for (; *mboxes != NULL; mboxes++) { if (strcmp(mbox_curr, *mboxes) == 0) { mbox_found = TRUE; break; } } if (mbox_found == FALSE) { push_notification_driver_debug(OX_LOG_LABEL, user, "Skipped because %s is not a watched mailbox", mbox_curr); return FALSE; } txn = p_new(dtxn->ptxn->pool, struct push_notification_driver_ox_txn, 1); /* Valid keys: user */ args = t_strsplit_tab(md_value); for (; *args != NULL; args++) { key = *args; value = strchr(key, '='); if (value != NULL) { key = t_strdup_until(key, value++); if (strcmp(key, "user") == 0) { txn->unsafe_user = p_strdup(dtxn->ptxn->pool, value); } } } if (txn->unsafe_user == NULL) { i_error(OX_LOG_LABEL "No user provided in config"); return FALSE; } push_notification_driver_debug(OX_LOG_LABEL, user, "User (%s)", txn->unsafe_user); for (; *events != NULL; events++) { if (strcmp(*events, "MessageNew") == 0) { config = p_new(dtxn->ptxn->pool, struct push_notification_event_messagenew_config, 1); config->flags = PUSH_NOTIFICATION_MESSAGE_HDR_FROM | PUSH_NOTIFICATION_MESSAGE_HDR_SUBJECT | PUSH_NOTIFICATION_MESSAGE_BODY_SNIPPET; push_notification_event_init(dtxn, "MessageNew", config); push_notification_driver_debug(OX_LOG_LABEL, user, "Handling MessageNew event"); } } dtxn->context = txn; return TRUE; } static void push_notification_driver_ox_http_callback (const struct http_response *response, struct mail_user *user) { switch (response->status / 100) { case 2: // Success. if (user->mail_debug) { push_notification_driver_debug(OX_LOG_LABEL, user, "Notification sent successfully: %u %s", response->status, response->reason); } break; default: // Error. i_error(OX_LOG_LABEL "Error when sending notification: %u %s", response->status, response->reason); break; } } /* Callback needed for i_stream_add_destroy_callback() in * push_notification_driver_ox_process_msg. */ static void str_free_i(string_t *str) { str_free(&str); } static void push_notification_driver_ox_process_msg (struct push_notification_driver_txn *dtxn, struct push_notification_txn_msg *msg) { struct push_notification_driver_ox_config *dconfig = (struct push_notification_driver_ox_config *)dtxn->duser->context; struct http_client_request *http_req; struct push_notification_event_messagenew_data *messagenew; struct istream *payload; string_t *str; struct push_notification_driver_ox_txn *txn = (struct push_notification_driver_ox_txn *)dtxn->context; struct mail_user *user = dtxn->ptxn->muser; messagenew = push_notification_txn_msg_get_eventdata(msg, "MessageNew"); if (messagenew == NULL) { return; } push_notification_driver_ox_init_global(user, dconfig); http_req = http_client_request_url(ox_global->http_client, "PUT", dconfig->http_url, push_notification_driver_ox_http_callback, user); http_client_request_add_header(http_req, "Content-Type", "application/json; charset=utf-8"); str = str_new(default_pool, 256); str_append(str, "{\"user\":\""); json_append_escaped(str, dconfig->use_unsafe_username ? txn->unsafe_user : user->username); str_append(str, "\",\"event\":\"messageNew\",\"folder\":\""); json_append_escaped(str, msg->mailbox); str_printfa(str, "\",\"imap-uidvalidity\":%u,\"imap-uid\":%u", msg->uid_validity, msg->uid); if (messagenew->from != NULL) { str_append(str, ",\"from\":\""); json_append_escaped(str, messagenew->from); } if (messagenew->subject != NULL) { str_append(str, "\",\"subject\":\""); json_append_escaped(str, messagenew->subject); } if (messagenew->snippet != NULL) { str_append(str, "\",\"snippet\":\""); json_append_escaped(str, messagenew->snippet); } str_append(str, "\"}"); push_notification_driver_debug(OX_LOG_LABEL, user, "Sending notification: %s", str_c(str)); payload = i_stream_create_from_data(str_data(str), str_len(str)); i_stream_add_destroy_callback(payload, str_free_i, str); http_client_request_set_payload(http_req, payload, FALSE); http_client_request_submit(http_req); i_stream_unref(&payload); } static void push_notification_driver_ox_deinit (struct push_notification_driver_user *duser ATTR_UNUSED) { struct push_notification_driver_ox_config *dconfig = duser->context; i_free(dconfig->cached_ox_metadata); if (ox_global != NULL) { i_assert(ox_global->refcount > 0); --ox_global->refcount; } } static void push_notification_driver_ox_cleanup(void) { if ((ox_global != NULL) && (ox_global->refcount <= 0)) { if (ox_global->http_client != NULL) { http_client_wait(ox_global->http_client); http_client_deinit(&ox_global->http_client); } i_free_and_null(ox_global); } } /* Driver definition */ extern struct push_notification_driver push_notification_driver_ox; struct push_notification_driver push_notification_driver_ox = { .name = "ox", .v = { .init = push_notification_driver_ox_init, .begin_txn = push_notification_driver_ox_begin_txn, .process_msg = push_notification_driver_ox_process_msg, .deinit = push_notification_driver_ox_deinit, .cleanup = push_notification_driver_ox_cleanup } };