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