res_resolver_unbound: Fix config documentation.
[asterisk/asterisk.git] / res / res_pjsip_publish_asterisk.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2014, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*** MODULEINFO
20         <depend>pjproject</depend>
21         <depend>res_pjsip</depend>
22         <depend>res_pjsip_outbound_publish</depend>
23         <support_level>core</support_level>
24  ***/
25
26 #include "asterisk.h"
27
28 #include <regex.h>
29
30 #include <pjsip.h>
31 #include <pjsip_simple.h>
32
33 #include "asterisk/res_pjsip.h"
34 #include "asterisk/res_pjsip_outbound_publish.h"
35 #include "asterisk/res_pjsip_pubsub.h"
36 #include "asterisk/module.h"
37 #include "asterisk/logger.h"
38 #include "asterisk/app.h"
39
40 /*** DOCUMENTATION
41         <configInfo name="res_pjsip_publish_asterisk" language="en_US">
42                 <synopsis>SIP resource for inbound and outbound Asterisk event publications</synopsis>
43                 <description><para>
44                         <emphasis>Inbound and outbound Asterisk event publication</emphasis>
45                         </para>
46                         <para>This module allows <literal>res_pjsip</literal> to send and receive Asterisk event publications.</para>
47                 </description>
48                 <configFile name="pjsip.conf">
49                         <configObject name="asterisk-publication">
50                                 <synopsis>The configuration for inbound Asterisk event publication</synopsis>
51                                 <description><para>
52                                         Publish is <emphasis>COMPLETELY</emphasis> separate from the rest of
53                                         <literal>pjsip.conf</literal>.
54                                 </para></description>
55                                 <configOption name="devicestate_publish">
56                                         <synopsis>Optional name of a publish item that can be used to publish a request for full device state information.</synopsis>
57                                 </configOption>
58                                 <configOption name="mailboxstate_publish">
59                                         <synopsis>Optional name of a publish item that can be used to publish a request for full mailbox state information.</synopsis>
60                                 </configOption>
61                                 <configOption name="device_state" default="no">
62                                         <synopsis>Whether we should permit incoming device state events.</synopsis>
63                                 </configOption>
64                                 <configOption name="device_state_filter">
65                                         <synopsis>Optional regular expression used to filter what devices we accept events for.</synopsis>
66                                 </configOption>
67                                 <configOption name="mailbox_state" default="no">
68                                         <synopsis>Whether we should permit incoming mailbox state events.</synopsis>
69                                 </configOption>
70                                 <configOption name="mailbox_state_filter">
71                                         <synopsis>Optional regular expression used to filter what mailboxes we accept events for.</synopsis>
72                                 </configOption>
73                                 <configOption name="type">
74                                         <synopsis>Must be of type 'asterisk-publication'.</synopsis>
75                                 </configOption>
76                         </configObject>
77                 </configFile>
78         </configInfo>
79  ***/
80
81 /*! \brief Structure which contains Asterisk device state publisher state information */
82 struct asterisk_devicestate_publisher_state {
83         /*! \brief The publish client to send PUBLISH messages on */
84         struct ast_sip_outbound_publish_client *client;
85         /*! \brief Device state subscription */
86         struct stasis_subscription *device_state_subscription;
87         /*! \brief Regex used for filtering outbound device state */
88         regex_t device_state_regex;
89         /*! \brief Device state should be filtered */
90         unsigned int device_state_filter;
91 };
92
93 /*! \brief Structure which contains Asterisk mailbox publisher state information */
94 struct asterisk_mwi_publisher_state {
95         /*! \brief The publish client to send PUBLISH messages on */
96         struct ast_sip_outbound_publish_client *client;
97         /*! \brief Mailbox state subscription */
98         struct stasis_subscription *mailbox_state_subscription;
99         /*! \brief Regex used for filtering outbound mailbox state */
100         regex_t mailbox_state_regex;
101         /*! \brief Mailbox state should be filtered */
102         unsigned int mailbox_state_filter;
103 };
104
105 /*! \brief Structure which contains Asterisk publication information */
106 struct asterisk_publication_config {
107         /*! \brief Sorcery object details */
108         SORCERY_OBJECT(details);
109         /*! \brief Stringfields */
110         AST_DECLARE_STRING_FIELDS(
111                 /*! \brief Optional name of a device state publish item, used to request the remote side update us */
112                 AST_STRING_FIELD(devicestate_publish);
113                 /*! \brief Optional name of a mailbox state publish item, used to request the remote side update us */
114                 AST_STRING_FIELD(mailboxstate_publish);
115         );
116         /*! \brief Accept inbound device state events */
117         unsigned int device_state;
118         /*! \brief Regex used for filtering inbound device state */
119         regex_t device_state_regex;
120         /*! \brief Device state should be filtered */
121         unsigned int device_state_filter;
122         /*! \brief Accept inbound mailbox state events */
123         unsigned int mailbox_state;
124         /*! \brief Regex used for filtering inbound mailbox state */
125         regex_t mailbox_state_regex;
126         /*! \brief Mailbox state should be filtered */
127         unsigned int mailbox_state_filter;
128 };
129
130 /*! \brief Destroy callback for Asterisk devicestate publisher state information from datastore */
131 static void asterisk_devicestate_publisher_state_destroy(void *obj)
132 {
133         struct asterisk_devicestate_publisher_state *publisher_state = obj;
134
135         ao2_cleanup(publisher_state->client);
136
137         if (publisher_state->device_state_filter) {
138                 regfree(&publisher_state->device_state_regex);
139         }
140 }
141
142 /*! \brief Datastore for attaching devicestate publisher state information */
143 static const struct ast_datastore_info asterisk_devicestate_publisher_state_datastore = {
144         .type = "asterisk-devicestate-publisher",
145         .destroy = asterisk_devicestate_publisher_state_destroy,
146 };
147
148 /*! \brief Destroy callback for Asterisk mwi publisher state information from datastore */
149 static void asterisk_mwi_publisher_state_destroy(void *obj)
150 {
151         struct asterisk_mwi_publisher_state *publisher_state = obj;
152
153         ao2_cleanup(publisher_state->client);
154
155         if (publisher_state->mailbox_state_filter) {
156                 regfree(&publisher_state->mailbox_state_regex);
157         }
158 }
159
160 /*! \brief Datastore for attaching devicestate publisher state information */
161 static const struct ast_datastore_info asterisk_mwi_publisher_state_datastore = {
162         .type = "asterisk-mwi-publisher",
163         .destroy = asterisk_mwi_publisher_state_destroy,
164 };
165
166 /*!
167  * \brief Callback function for device state events
168  * \param ast_event
169  * \param data void pointer to ast_client structure
170  * \return void
171  */
172 static void asterisk_publisher_devstate_cb(void *data, struct stasis_subscription *sub, struct stasis_message *msg)
173 {
174         struct ast_datastore *datastore = data;
175         struct asterisk_devicestate_publisher_state *publisher_state = datastore->data;
176         struct ast_device_state_message *dev_state;
177         char eid_str[20];
178         struct ast_json *json;
179         char *text;
180         struct ast_sip_body body = {
181                 .type = "application",
182                 .subtype = "json",
183         };
184
185         if (!stasis_subscription_is_subscribed(sub) || ast_device_state_message_type() != stasis_message_type(msg)) {
186                 return;
187         }
188
189         dev_state = stasis_message_data(msg);
190         if (!dev_state->eid || ast_eid_cmp(&ast_eid_default, dev_state->eid)) {
191                 /* If the event is aggregate or didn't originate from this server, don't send it out. */
192                 return;
193         }
194
195         if (publisher_state->device_state_filter && regexec(&publisher_state->device_state_regex, dev_state->device, 0, NULL, 0)) {
196                 /* Outgoing device state has been filtered and the device name does not match */
197                 return;
198         }
199
200         ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
201         json = ast_json_pack(
202                 "{ s: s, s: s, s: s, s: i, s:s }",
203                 "type", "devicestate",
204                 "device", dev_state->device,
205                 "state", ast_devstate_str(dev_state->state),
206                 "cachable", dev_state->cachable,
207                 "eid", eid_str);
208         if (!json) {
209                 return;
210         }
211
212         text = ast_json_dump_string(json);
213         if (!text) {
214                 ast_json_unref(json);
215                 return;
216         }
217         body.body_text = text;
218
219         ast_sip_publish_client_send(publisher_state->client, &body);
220
221         ast_json_free(text);
222         ast_json_unref(json);
223 }
224
225 /*!
226  * \brief Callback function for mailbox state events
227  * \param ast_event
228  * \param data void pointer to ast_client structure
229  * \return void
230  */
231 static void asterisk_publisher_mwistate_cb(void *data, struct stasis_subscription *sub, struct stasis_message *msg)
232 {
233         struct ast_datastore *datastore = data;
234         struct asterisk_mwi_publisher_state *publisher_state = datastore->data;
235         struct ast_mwi_state *mwi_state;
236         char eid_str[20];
237         struct ast_json *json;
238         char *text;
239         struct ast_sip_body body = {
240                 .type = "application",
241                 .subtype = "json",
242         };
243
244         if (!stasis_subscription_is_subscribed(sub) || ast_mwi_state_type() != stasis_message_type(msg)) {
245                 return;
246         }
247
248         mwi_state = stasis_message_data(msg);
249         if (ast_eid_cmp(&ast_eid_default, &mwi_state->eid)) {
250                 /* If the event is aggregate or didn't originate from this server, don't send it out. */
251                 return;
252         }
253
254         if (publisher_state->mailbox_state_filter && regexec(&publisher_state->mailbox_state_regex, mwi_state->uniqueid, 0, NULL, 0)) {
255                 /* Outgoing mailbox state has been filtered and the uniqueid does not match */
256                 return;
257         }
258
259         ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
260         json = ast_json_pack(
261                 "{ s: s, s: s, s: i, s: i, s:s }",
262                 "type", "mailboxstate",
263                 "uniqueid", mwi_state->uniqueid,
264                 "old", mwi_state->old_msgs,
265                 "new", mwi_state->new_msgs,
266                 "eid", eid_str);
267         if (!json) {
268                 return;
269         }
270
271         text = ast_json_dump_string(json);
272         if (!text) {
273                 ast_json_unref(json);
274                 return;
275         }
276         body.body_text = text;
277
278         ast_sip_publish_client_send(publisher_state->client, &body);
279
280         ast_json_free(text);
281         ast_json_unref(json);
282 }
283
284 static int cached_devstate_cb(void *obj, void *arg, int flags)
285 {
286         struct stasis_message *msg = obj;
287         struct ast_datastore *datastore = arg;
288         struct asterisk_devicestate_publisher_state *publisher_state = datastore->data;
289
290         asterisk_publisher_devstate_cb(arg, publisher_state->device_state_subscription, msg);
291
292         return 0;
293 }
294
295 static int cached_mwistate_cb(void *obj, void *arg, int flags)
296 {
297         struct stasis_message *msg = obj;
298         struct ast_datastore *datastore = arg;
299         struct asterisk_mwi_publisher_state *publisher_state = datastore->data;
300
301         asterisk_publisher_mwistate_cb(arg, publisher_state->mailbox_state_subscription, msg);
302
303         return 0;
304 }
305
306 static int build_regex(regex_t *regex, const char *text)
307 {
308         int res;
309
310         if ((res = regcomp(regex, text, REG_EXTENDED | REG_ICASE | REG_NOSUB))) {
311                 size_t len = regerror(res, regex, NULL, 0);
312                 char buf[len];
313                 regerror(res, regex, buf, len);
314                 ast_log(LOG_ERROR, "Could not compile regex '%s': %s\n", text, buf);
315                 return -1;
316         }
317
318         return 0;
319 }
320
321 static int asterisk_start_devicestate_publishing(struct ast_sip_outbound_publish *configuration,
322         struct ast_sip_outbound_publish_client *client)
323 {
324         RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
325         struct asterisk_devicestate_publisher_state *publisher_state;
326         const char *value;
327         struct ao2_container *cached;
328
329         datastore = ast_sip_publish_client_alloc_datastore(&asterisk_devicestate_publisher_state_datastore,
330                 "asterisk-devicestate-publisher");
331         if (!datastore) {
332                 return -1;
333         }
334
335         publisher_state = ast_calloc(1, sizeof(struct asterisk_devicestate_publisher_state));
336         if (!publisher_state) {
337                 return -1;
338         }
339         datastore->data = publisher_state;
340
341         value = ast_sorcery_object_get_extended(configuration, "device_state_filter");
342         if (!ast_strlen_zero(value)) {
343                 if (build_regex(&publisher_state->device_state_regex, value)) {
344                         return -1;
345                 }
346                 publisher_state->device_state_filter = 1;
347         }
348
349         publisher_state->client = ao2_bump(client);
350
351         if (ast_sip_publish_client_add_datastore(client, datastore)) {
352                 return -1;
353         }
354
355         publisher_state->device_state_subscription = stasis_subscribe(ast_device_state_topic_all(),
356                 asterisk_publisher_devstate_cb, ao2_bump(datastore));
357         if (!publisher_state->device_state_subscription) {
358                 ast_sip_publish_client_remove_datastore(client, "asterisk-devicestate-publisher");
359                 ao2_ref(datastore, -1);
360                 return -1;
361         }
362
363         cached = stasis_cache_dump(ast_device_state_cache(), NULL);
364         ao2_callback(cached, OBJ_NODATA, cached_devstate_cb, datastore);
365         ao2_ref(cached, -1);
366
367         return 0;
368 }
369
370 static int asterisk_stop_devicestate_publishing(struct ast_sip_outbound_publish_client *client)
371 {
372         RAII_VAR(struct ast_datastore *, datastore, ast_sip_publish_client_get_datastore(client, "asterisk-devicestate-publisher"),
373                 ao2_cleanup);
374         struct asterisk_devicestate_publisher_state *publisher_state;
375
376         if (!datastore) {
377                 return 0;
378         }
379
380         publisher_state = datastore->data;
381         if (publisher_state->device_state_subscription) {
382                 stasis_unsubscribe_and_join(publisher_state->device_state_subscription);
383                 ao2_ref(datastore, -1);
384         }
385
386         ast_sip_publish_client_remove_datastore(client, "asterisk-devicestate-publisher");
387
388         return 0;
389 }
390
391 struct ast_sip_event_publisher_handler asterisk_devicestate_publisher_handler = {
392         .event_name = "asterisk-devicestate",
393         .start_publishing = asterisk_start_devicestate_publishing,
394         .stop_publishing = asterisk_stop_devicestate_publishing,
395 };
396
397 static int asterisk_start_mwi_publishing(struct ast_sip_outbound_publish *configuration,
398         struct ast_sip_outbound_publish_client *client)
399 {
400         RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
401         struct asterisk_mwi_publisher_state *publisher_state;
402         const char *value;
403         struct ao2_container *cached;
404
405         datastore = ast_sip_publish_client_alloc_datastore(&asterisk_mwi_publisher_state_datastore, "asterisk-mwi-publisher");
406         if (!datastore) {
407                 return -1;
408         }
409
410         publisher_state = ast_calloc(1, sizeof(struct asterisk_mwi_publisher_state));
411         if (!publisher_state) {
412                 return -1;
413         }
414         datastore->data = publisher_state;
415
416         value = ast_sorcery_object_get_extended(configuration, "mailbox_state_filter");
417         if (!ast_strlen_zero(value)) {
418                 if (build_regex(&publisher_state->mailbox_state_regex, value)) {
419                         return -1;
420                 }
421                 publisher_state->mailbox_state_filter = 1;
422         }
423
424         publisher_state->client = ao2_bump(client);
425
426         if (ast_sip_publish_client_add_datastore(client, datastore)) {
427                 return -1;
428         }
429
430         publisher_state->mailbox_state_subscription = stasis_subscribe(ast_mwi_topic_all(),
431                 asterisk_publisher_mwistate_cb, ao2_bump(datastore));
432         if (!publisher_state->mailbox_state_subscription) {
433                 ast_sip_publish_client_remove_datastore(client, "asterisk-mwi-publisher");
434                 ao2_ref(datastore, -1);
435                 return -1;
436         }
437
438         cached = stasis_cache_dump(ast_mwi_state_cache(), NULL);
439         ao2_callback(cached, OBJ_NODATA, cached_mwistate_cb, datastore);
440         ao2_ref(cached, -1);
441
442         return 0;
443 }
444
445 static int asterisk_stop_mwi_publishing(struct ast_sip_outbound_publish_client *client)
446 {
447         RAII_VAR(struct ast_datastore *, datastore, ast_sip_publish_client_get_datastore(client, "asterisk-mwi-publisher"),
448                 ao2_cleanup);
449         struct asterisk_mwi_publisher_state *publisher_state;
450
451         if (!datastore) {
452                 return 0;
453         }
454
455         publisher_state = datastore->data;
456         if (publisher_state->mailbox_state_subscription) {
457                 stasis_unsubscribe_and_join(publisher_state->mailbox_state_subscription);
458                 ao2_ref(datastore, -1);
459         }
460
461         ast_sip_publish_client_remove_datastore(client, "asterisk-mwi-publisher");
462
463         return 0;
464 }
465
466 struct ast_sip_event_publisher_handler asterisk_mwi_publisher_handler = {
467         .event_name = "asterisk-mwi",
468         .start_publishing = asterisk_start_mwi_publishing,
469         .stop_publishing = asterisk_stop_mwi_publishing,
470 };
471
472 static int asterisk_publication_new(struct ast_sip_endpoint *endpoint, const char *resource, const char *event_configuration)
473 {
474         RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
475                 event_configuration), ao2_cleanup);
476
477         /* If no inbound Asterisk publication configuration exists reject the PUBLISH */
478         if (!config) {
479                 return 404;
480         }
481
482         return 200;
483 }
484
485 static int asterisk_publication_devicestate(struct ast_sip_publication *pub, struct asterisk_publication_config *config,
486         struct ast_eid *pubsub_eid, struct ast_json *json)
487 {
488         const char *device = ast_json_string_get(ast_json_object_get(json, "device"));
489         const char *state = ast_json_string_get(ast_json_object_get(json, "state"));
490         int cachable = ast_json_integer_get(ast_json_object_get(json, "cachable"));
491
492         if (!config->device_state) {
493                 ast_debug(2, "Received device state event for resource '%s' but it is not configured to accept them\n",
494                         ast_sorcery_object_get_id(config));
495                 return 0;
496         }
497
498         if (ast_strlen_zero(device) || ast_strlen_zero(state)) {
499                 ast_debug(1, "Received incomplete device state event for resource '%s'\n",
500                         ast_sorcery_object_get_id(config));
501                 return -1;
502         }
503
504         if (config->device_state_filter && regexec(&config->device_state_regex, device, 0, NULL, 0)) {
505                 ast_debug(2, "Received device state on resource '%s' for device '%s' but it has been filtered out\n",
506                         ast_sorcery_object_get_id(config), device);
507                 return 0;
508         }
509
510         ast_publish_device_state_full(device, ast_devstate_val(state),
511                 cachable == AST_DEVSTATE_CACHABLE ? AST_DEVSTATE_CACHABLE : AST_DEVSTATE_NOT_CACHABLE,
512                 pubsub_eid);
513
514         return 0;
515 }
516
517 static int asterisk_publication_mailboxstate(struct ast_sip_publication *pub, struct asterisk_publication_config *config,
518         struct ast_eid *pubsub_eid, struct ast_json *json)
519 {
520         const char *uniqueid = ast_json_string_get(ast_json_object_get(json, "uniqueid"));
521         int old_msgs = ast_json_integer_get(ast_json_object_get(json, "old"));
522         int new_msgs = ast_json_integer_get(ast_json_object_get(json, "new"));
523         char *item_id;
524         const char *mailbox;
525
526         if (!config->mailbox_state) {
527                 ast_debug(2, "Received mailbox state event for resource '%s' but it is not configured to accept them\n",
528                         ast_sorcery_object_get_id(config));
529                 return 0;
530         }
531
532         if (ast_strlen_zero(uniqueid)) {
533                 ast_debug(1, "Received incomplete mailbox state event for resource '%s'\n",
534                         ast_sorcery_object_get_id(config));
535                 return -1;
536         }
537
538         if (config->mailbox_state_filter && regexec(&config->mailbox_state_regex, uniqueid, 0, NULL, 0)) {
539                 ast_debug(2, "Received mailbox state on resource '%s' for uniqueid '%s' but it has been filtered out\n",
540                         ast_sorcery_object_get_id(config), uniqueid);
541                 return 0;
542         }
543
544         item_id = ast_strdupa(uniqueid);
545         mailbox = strsep(&item_id, "@");
546
547         ast_publish_mwi_state_full(mailbox, item_id, new_msgs, old_msgs, NULL, pubsub_eid);
548
549         return 0;
550 }
551
552 static int asterisk_publication_devicestate_refresh(struct ast_sip_publication *pub,
553         struct asterisk_publication_config *config, struct ast_eid *pubsub_eid, struct ast_json *json)
554 {
555         struct ast_sip_outbound_publish_client *client;
556         struct ast_datastore *datastore;
557         struct ao2_container *cached;
558
559         if (ast_strlen_zero(config->devicestate_publish)) {
560                 return 0;
561         }
562
563         client = ast_sip_publish_client_get(config->devicestate_publish);
564         if (!client) {
565                 ast_log(LOG_ERROR, "Received refresh request for devicestate on publication '%s' but publish '%s' is not available\n",
566                         ast_sorcery_object_get_id(config), config->devicestate_publish);
567                 return 0;
568         }
569
570         datastore = ast_sip_publish_client_get_datastore(client, "asterisk-devicestate-publisher");
571         if (!datastore) {
572                 ao2_ref(client, -1);
573                 return 0;
574         }
575
576         cached = stasis_cache_dump(ast_device_state_cache(), NULL);
577         if (cached) {
578                 ao2_callback(cached, OBJ_NODATA, cached_devstate_cb, datastore);
579                 ao2_ref(cached, -1);
580         }
581         ao2_ref(client, -1);
582         ao2_ref(datastore, -1);
583
584         return 0;
585 }
586
587 static int asterisk_publication_devicestate_state_change(struct ast_sip_publication *pub, pjsip_msg_body *body,
588                         enum ast_sip_publish_state state)
589 {
590         RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
591                 ast_sip_publication_get_event_configuration(pub)), ao2_cleanup);
592         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
593         const char *eid, *type;
594         struct ast_eid pubsub_eid;
595         int res = -1;
596
597         /* If no configuration exists for this publication it has most likely been removed, so drop this immediately */
598         if (!config) {
599                 return -1;
600         }
601
602         /* If no body exists this is a refresh and can be ignored */
603         if (!body) {
604                 return 0;
605         }
606
607         /* We only accept JSON for content */
608         if (pj_strcmp2(&body->content_type.type, "application") ||
609                 pj_strcmp2(&body->content_type.subtype, "json")) {
610                 ast_debug(2, "Received unsupported content type for Asterisk event on resource '%s'\n",
611                         ast_sorcery_object_get_id(config));
612                 return -1;
613         }
614
615         json = ast_json_load_buf(body->data, body->len, NULL);
616         if (!json) {
617                 ast_debug(1, "Received unparseable JSON event for resource '%s'\n",
618                         ast_sorcery_object_get_id(config));
619                 return -1;
620         }
621
622         eid = ast_json_string_get(ast_json_object_get(json, "eid"));
623         if (!eid) {
624                 ast_debug(1, "Received event without eid for resource '%s'\n",
625                         ast_sorcery_object_get_id(config));
626                 return -1;
627         }
628         ast_str_to_eid(&pubsub_eid, eid);
629
630         type = ast_json_string_get(ast_json_object_get(json, "type"));
631         if (!type) {
632                 ast_debug(1, "Received event without type for resource '%s'\n",
633                         ast_sorcery_object_get_id(config));
634                 return -1;
635         } else if (!strcmp(type, "devicestate")) {
636                 res = asterisk_publication_devicestate(pub, config, &pubsub_eid, json);
637         } else if (!strcmp(type, "refresh")) {
638                 res = asterisk_publication_devicestate_refresh(pub, config, &pubsub_eid, json);
639         }
640
641         return res;
642 }
643
644 static int asterisk_publication_mwi_refresh(struct ast_sip_publication *pub,
645         struct asterisk_publication_config *config, struct ast_eid *pubsub_eid, struct ast_json *json)
646 {
647         struct ast_sip_outbound_publish_client *client;
648         struct ast_datastore *datastore;
649         struct ao2_container *cached;
650
651         if (ast_strlen_zero(config->mailboxstate_publish)) {
652                 return 0;
653         }
654
655         client = ast_sip_publish_client_get(config->mailboxstate_publish);
656         if (!client) {
657                 ast_log(LOG_ERROR, "Received refresh request for mwi state on publication '%s' but publish '%s' is not available\n",
658                         ast_sorcery_object_get_id(config), config->mailboxstate_publish);
659                 return 0;
660         }
661
662         datastore = ast_sip_publish_client_get_datastore(client, "asterisk-mwi-publisher");
663         if (!datastore) {
664                 ao2_ref(client, -1);
665                 return 0;
666         }
667
668         cached = stasis_cache_dump(ast_mwi_state_cache(), NULL);
669         if (cached) {
670                 ao2_callback(cached, OBJ_NODATA, cached_mwistate_cb, datastore);
671                 ao2_ref(cached, -1);
672         }
673         ao2_ref(client, -1);
674         ao2_ref(datastore, -1);
675
676         return 0;
677 }
678
679 static int asterisk_publication_mwi_state_change(struct ast_sip_publication *pub, pjsip_msg_body *body,
680                         enum ast_sip_publish_state state)
681 {
682         RAII_VAR(struct asterisk_publication_config *, config, ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "asterisk-publication",
683                 ast_sip_publication_get_event_configuration(pub)), ao2_cleanup);
684         RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
685         const char *eid, *type;
686         struct ast_eid pubsub_eid;
687         int res = -1;
688
689         /* If no configuration exists for this publication it has most likely been removed, so drop this immediately */
690         if (!config) {
691                 return -1;
692         }
693
694         /* If no body exists this is a refresh and can be ignored */
695         if (!body) {
696                 return 0;
697         }
698
699         /* We only accept JSON for content */
700         if (pj_strcmp2(&body->content_type.type, "application") ||
701                 pj_strcmp2(&body->content_type.subtype, "json")) {
702                 ast_debug(2, "Received unsupported content type for Asterisk event on resource '%s'\n",
703                         ast_sorcery_object_get_id(config));
704                 return -1;
705         }
706
707         json = ast_json_load_buf(body->data, body->len, NULL);
708         if (!json) {
709                 ast_debug(1, "Received unparseable JSON event for resource '%s'\n",
710                         ast_sorcery_object_get_id(config));
711                 return -1;
712         }
713
714         eid = ast_json_string_get(ast_json_object_get(json, "eid"));
715         if (!eid) {
716                 ast_debug(1, "Received event without eid for resource '%s'\n",
717                         ast_sorcery_object_get_id(config));
718                 return -1;
719         }
720         ast_str_to_eid(&pubsub_eid, eid);
721
722         type = ast_json_string_get(ast_json_object_get(json, "type"));
723         if (!type) {
724                 ast_debug(1, "Received event without type for resource '%s'\n",
725                         ast_sorcery_object_get_id(config));
726                 return -1;
727         } else if (!strcmp(type, "mailboxstate")) {
728                 res = asterisk_publication_mailboxstate(pub, config, &pubsub_eid, json);
729         } else if (!strcmp(type, "refresh")) {
730                 res = asterisk_publication_mwi_refresh(pub, config, &pubsub_eid, json);
731         }
732
733         return res;
734 }
735
736 static int send_refresh_cb(void *obj, void *arg, int flags)
737 {
738         struct asterisk_publication_config *config = obj;
739         struct ast_sip_outbound_publish_client *client;
740
741         if (!ast_strlen_zero(config->devicestate_publish)) {
742                 client = ast_sip_publish_client_get(config->devicestate_publish);
743                 if (client) {
744                         ast_sip_publish_client_send(client, arg);
745                         ao2_ref(client, -1);
746                 }
747         }
748
749         if (!ast_strlen_zero(config->mailboxstate_publish)) {
750                 client = ast_sip_publish_client_get(config->mailboxstate_publish);
751                 if (client) {
752                         ast_sip_publish_client_send(client, arg);
753                         ao2_ref(client, -1);
754                 }
755         }
756
757         return 0;
758 }
759
760 /*! \brief Internal function to send refresh requests to all publications */
761 static void asterisk_publication_send_refresh(void)
762 {
763         struct ao2_container *publications = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "asterisk-publication", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
764         char eid_str[20];
765         struct ast_json *json;
766         char *text;
767         struct ast_sip_body body = {
768                 .type = "application",
769                 .subtype = "json",
770         };
771
772         if (!publications) {
773                 return;
774         }
775
776         ast_eid_to_str(eid_str, sizeof(eid_str), &ast_eid_default);
777         json = ast_json_pack(
778                 "{ s: s, s: s }",
779                 "type", "refresh",
780                 "eid", eid_str);
781         if (!json) {
782                 ao2_ref(publications, -1);
783                 return;
784         }
785
786         text = ast_json_dump_string(json);
787         if (!text) {
788                 ast_json_unref(json);
789                 ao2_ref(publications, -1);
790                 return;
791         }
792         body.body_text = text;
793
794         ao2_callback(publications, OBJ_NODATA, send_refresh_cb, &body);
795
796         ast_json_free(text);
797         ast_json_unref(json);
798         ao2_ref(publications, -1);
799 }
800
801 struct ast_sip_publish_handler asterisk_devicestate_publication_handler = {
802         .event_name = "asterisk-devicestate",
803         .new_publication = asterisk_publication_new,
804         .publication_state_change = asterisk_publication_devicestate_state_change,
805 };
806
807 struct ast_sip_publish_handler asterisk_mwi_publication_handler = {
808         .event_name = "asterisk-mwi",
809         .new_publication = asterisk_publication_new,
810         .publication_state_change = asterisk_publication_mwi_state_change,
811 };
812
813 /*! \brief Destructor function for Asterisk publication configuration */
814 static void asterisk_publication_config_destroy(void *obj)
815 {
816         struct asterisk_publication_config *config = obj;
817
818         ast_string_field_free_memory(config);
819 }
820
821 /*! \brief Allocator function for Asterisk publication configuration */
822 static void *asterisk_publication_config_alloc(const char *name)
823 {
824         struct asterisk_publication_config *config = ast_sorcery_generic_alloc(sizeof(*config),
825                 asterisk_publication_config_destroy);
826
827         if (!config || ast_string_field_init(config, 256)) {
828                 ao2_cleanup(config);
829                 return NULL;
830         }
831
832         return config;
833 }
834
835 static int regex_filter_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
836 {
837         struct asterisk_publication_config *config = obj;
838         int res = -1;
839
840         if (ast_strlen_zero(var->value)) {
841                 return 0;
842         }
843
844         if (!strcmp(var->name, "device_state_filter")) {
845                 if (!(res = build_regex(&config->device_state_regex, var->value))) {
846                         config->device_state_filter = 1;
847                 }
848         } else if (!strcmp(var->name, "mailbox_state_filter")) {
849                 if (!(res = build_regex(&config->mailbox_state_regex, var->value))) {
850                         config->mailbox_state_filter = 1;
851                 }
852         }
853
854         return res;
855 }
856
857 static int load_module(void)
858 {
859         CHECK_PJSIP_PUBSUB_MODULE_LOADED();
860
861         if (ast_eid_is_empty(&ast_eid_default)) {
862                 ast_log(LOG_ERROR, "Entity ID is not set.\n");
863                 return AST_MODULE_LOAD_DECLINE;
864         }
865
866         ast_sorcery_apply_config(ast_sip_get_sorcery(), "asterisk-publication");
867         ast_sorcery_apply_default(ast_sip_get_sorcery(), "asterisk-publication", "config", "pjsip.conf,criteria=type=asterisk-publication");
868
869         if (ast_sorcery_object_register(ast_sip_get_sorcery(), "asterisk-publication", asterisk_publication_config_alloc, NULL, NULL)) {
870                 ast_log(LOG_ERROR, "Unable to register 'asterisk-publication' type with sorcery\n");
871                 return AST_MODULE_LOAD_DECLINE;
872         }
873
874         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "type", "", OPT_NOOP_T, 0, 0);
875         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "devicestate_publish", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct asterisk_publication_config, devicestate_publish));
876         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "mailboxstate_publish", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct asterisk_publication_config, mailboxstate_publish));
877         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "device_state", "no", OPT_BOOL_T, 1, FLDSET(struct asterisk_publication_config, device_state));
878         ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "asterisk-publication", "device_state_filter", "", regex_filter_handler, NULL, NULL, 0, 0);
879         ast_sorcery_object_field_register(ast_sip_get_sorcery(), "asterisk-publication", "mailbox_state", "no", OPT_BOOL_T, 1, FLDSET(struct asterisk_publication_config, mailbox_state));
880         ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "asterisk-publication", "mailbox_state_filter", "", regex_filter_handler, NULL, NULL, 0, 0);
881         ast_sorcery_reload_object(ast_sip_get_sorcery(), "asterisk-publication");
882
883         if (ast_sip_register_publish_handler(&asterisk_devicestate_publication_handler)) {
884                 ast_log(LOG_WARNING, "Unable to register event publication handler %s\n",
885                         asterisk_devicestate_publication_handler.event_name);
886                 return AST_MODULE_LOAD_DECLINE;
887         }
888         if (ast_sip_register_publish_handler(&asterisk_mwi_publication_handler)) {
889                 ast_log(LOG_WARNING, "Unable to register event publication handler %s\n",
890                         asterisk_mwi_publication_handler.event_name);
891                 ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
892                 return AST_MODULE_LOAD_DECLINE;
893         }
894         if (ast_sip_register_event_publisher_handler(&asterisk_devicestate_publisher_handler)) {
895                 ast_log(LOG_WARNING, "Unable to register event publisher handler %s\n",
896                         asterisk_devicestate_publisher_handler.event_name);
897                 ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
898                 ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
899                 return AST_MODULE_LOAD_DECLINE;
900         }
901         if (ast_sip_register_event_publisher_handler(&asterisk_mwi_publisher_handler)) {
902                 ast_log(LOG_WARNING, "Unable to register event publisher handler %s\n",
903                         asterisk_mwi_publisher_handler.event_name);
904                 ast_sip_unregister_event_publisher_handler(&asterisk_mwi_publisher_handler);
905                 ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
906                 ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
907                 return AST_MODULE_LOAD_DECLINE;
908         }
909
910         asterisk_publication_send_refresh();
911
912         return AST_MODULE_LOAD_SUCCESS;
913 }
914
915 static int reload_module(void)
916 {
917         ast_sorcery_reload_object(ast_sip_get_sorcery(), "asterisk-publication");
918         asterisk_publication_send_refresh();
919         return 0;
920 }
921
922 static int unload_module(void)
923 {
924         ast_sip_unregister_publish_handler(&asterisk_devicestate_publication_handler);
925         ast_sip_unregister_publish_handler(&asterisk_mwi_publication_handler);
926         ast_sip_unregister_event_publisher_handler(&asterisk_devicestate_publisher_handler);
927         ast_sip_unregister_event_publisher_handler(&asterisk_mwi_publisher_handler);
928         ast_sorcery_object_unregister(ast_sip_get_sorcery(), "asterisk-publication");
929         return 0;
930 }
931
932 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Asterisk Event PUBLISH Support",
933         .load = load_module,
934         .reload = reload_module,
935         .unload = unload_module,
936         .load_pri = AST_MODPRI_CHANNEL_DEPEND + 5,
937 );