Asterisk - The Open Source Telephony Project GIT-master-4f2b068
Loading...
Searching...
No Matches
res_pjsip_outbound_publish.c
Go to the documentation of this file.
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_pjproject</depend>
22 <depend>res_pjsip</depend>
23 <support_level>core</support_level>
24 ***/
25
26#include "asterisk.h"
27
28#include <pjsip.h>
29#include <pjsip_simple.h>
30
32#include "asterisk/res_pjsip.h"
34#include "asterisk/module.h"
36#include "asterisk/taskpool.h"
38#include "asterisk/datastore.h"
40
41/*** DOCUMENTATION
42 <configInfo name="res_pjsip_outbound_publish" language="en_US">
43 <synopsis>SIP resource for outbound publish</synopsis>
44 <description><para>
45 <emphasis>Outbound Publish</emphasis>
46 </para>
47 <para>This module allows <literal>res_pjsip</literal> to publish to other SIP servers.</para>
48 </description>
49 <configFile name="pjsip.conf">
50 <configObject name="outbound-publish">
51 <since>
52 <version>13.0.0</version>
53 </since>
54 <synopsis>The configuration for outbound publish</synopsis>
55 <description><para>
56 Publish is <emphasis>COMPLETELY</emphasis> separate from the rest of
57 <literal>pjsip.conf</literal>. A minimal configuration consists of
58 setting a <literal>server_uri</literal> and <literal>event</literal>.
59 </para></description>
60 <configOption name="expiration" default="3600">
61 <since>
62 <version>13.0.0</version>
63 </since>
64 <synopsis>Expiration time for publications in seconds</synopsis>
65 </configOption>
66 <configOption name="outbound_auth" default="">
67 <since>
68 <version>13.0.0</version>
69 </since>
70 <synopsis>Authentication object(s) to be used for outbound publishes.</synopsis>
71 <description><para>
72 This is a comma-delimited list of <replaceable>auth</replaceable>
73 sections defined in <filename>pjsip.conf</filename> used to respond
74 to outbound authentication challenges.</para>
75 <note><para>
76 Using the same auth section for inbound and outbound
77 authentication is not recommended. There is a difference in
78 meaning for an empty realm setting between inbound and outbound
79 authentication uses. See the auth realm description for details.
80 </para></note>
81 </description>
82 </configOption>
83 <configOption name="outbound_proxy" default="">
84 <since>
85 <version>13.0.0</version>
86 </since>
87 <synopsis>Full SIP URI of the outbound proxy used to send publishes</synopsis>
88 </configOption>
89 <configOption name="server_uri">
90 <since>
91 <version>13.0.0</version>
92 </since>
93 <synopsis>SIP URI of the server and entity to publish to</synopsis>
94 <description><para>
95 This is the URI at which to find the entity and server to send the outbound PUBLISH to.
96 This URI is used as the request URI of the outbound PUBLISH request from Asterisk.
97 </para></description>
98 </configOption>
99 <configOption name="from_uri">
100 <since>
101 <version>13.0.0</version>
102 </since>
103 <synopsis>SIP URI to use in the From header</synopsis>
104 <description><para>
105 This is the URI that will be placed into the From header of outgoing PUBLISH
106 messages. If no URI is specified then the URI provided in <literal>server_uri</literal>
107 will be used.
108 </para></description>
109 </configOption>
110 <configOption name="to_uri">
111 <since>
112 <version>13.0.0</version>
113 </since>
114 <synopsis>SIP URI to use in the To header</synopsis>
115 <description><para>
116 This is the URI that will be placed into the To header of outgoing PUBLISH
117 messages. If no URI is specified then the URI provided in <literal>server_uri</literal>
118 will be used.
119 </para></description>
120 </configOption>
121 <configOption name="event" default="">
122 <since>
123 <version>13.0.0</version>
124 </since>
125 <synopsis>Event type of the PUBLISH.</synopsis>
126 </configOption>
127 <configOption name="max_auth_attempts" default="5">
128 <since>
129 <version>13.0.0</version>
130 </since>
131 <synopsis>Maximum number of authentication attempts before stopping the publication.</synopsis>
132 </configOption>
133 <configOption name="transport">
134 <since>
135 <version>13.9.0</version>
136 </since>
137 <synopsis>Transport used for outbound publish</synopsis>
138 <description>
139 <note><para>A <replaceable>transport</replaceable> configured in
140 <literal>pjsip.conf</literal>. As with other <literal>res_pjsip</literal> modules, this will use the first available transport of the appropriate type if unconfigured.</para></note>
141 </description>
142 </configOption>
143 <configOption name="multi_user" default="no">
144 <since>
145 <version>14.0.0</version>
146 </since>
147 <synopsis>Enable multi-user support</synopsis>
148 <description><para>When enabled the user portion of the server uri is replaced by a dynamically created user</para></description>
149 </configOption>
150 <configOption name="type">
151 <since>
152 <version>13.0.0</version>
153 </since>
154 <synopsis>Must be of type 'outbound-publish'.</synopsis>
155 </configOption>
156 </configObject>
157 </configFile>
158 </configInfo>
159 ***/
160
161static int pjsip_max_url_size = PJSIP_MAX_URL_SIZE;
162
163/*! \brief Queued outbound publish message */
165 /*! \brief Optional body */
167 /*! \brief Linked list information */
169 /*! \brief Extra space for body contents */
171};
172
173/*
174 * A note about some of the object types used in this module:
175 *
176 * The reason we currently have 4 separate object types that relate to configuration,
177 * publishing, state, and client information is due to object lifetimes and order of
178 * destruction dependencies.
179 *
180 * Separation of concerns is a good thing and of course it makes sense to have a
181 * configuration object type as well as an object type wrapper around pjsip's publishing
182 * client class. There also may be run time state data that needs to be tracked, so
183 * again having something to handle that is prudent. However, it may be tempting to think
184 * "why not combine the state and client object types?" Especially seeing as how they have
185 * a one-to-one relationship. The answer is, it's possible, but it'd make the code a bit
186 * more awkward.
187 *
188 * Currently this module maintains a global container of current state objects. When this
189 * states container is replaced, or deleted, it un-references all contained objects. Any
190 * state with a reference left have probably been carried over from a reload/realtime fetch.
191 * States not carried over are destructed and the associated client (and all its publishers)
192 * get unpublished.
193 *
194 * This "unpublishing" goes through a careful process of unpublishing the client, all its
195 * publishers, and making sure all the appropriate references are removed in a sane order.
196 * This process is essentially kicked off with the destruction of the state. If the state
197 * and client objects were to be merged, where clients became the globally tracked object
198 * type, this "unpublishing" process would never start because of the multiple references
199 * held to the client object over it's lifetime. Meaning the global tracking container
200 * would remove its reference to the client object when done with it, but other sources
201 * would still be holding a reference to it (namely the datastore and publisher(s)).
202 *
203 * Thus at this time it is easier to keep them separate.
204 */
205
206/*! \brief Outbound publish information */
208 /*! \brief Sorcery object details */
210 /*! \brief Stringfields */
212 /*! \brief URI for the entity and server */
214 /*! \brief URI for the From header */
216 /*! \brief URI for the To header */
218 /*! \brief Explicit transport to use for publish */
220 /*! \brief Outbound proxy to use */
222 /*! \brief The event type to publish */
224 );
225 /*! \brief Requested expiration time */
226 unsigned int expiration;
227 /*! \brief Maximum number of auth attempts before stopping the publish client */
228 unsigned int max_auth_attempts;
229 /*! \brief Configured authentication credentials */
231 /*! \brief The publishing client is used for multiple users when true */
232 unsigned int multi_user;
233};
234
236 /*! \brief The client object that 'owns' this client
237
238 \note any potential circular reference problems are accounted
239 for (see publisher alloc for more information)
240 */
242 /*! \brief Underlying publish client */
243 pjsip_publishc *client;
244 /*! \brief The From URI for this specific publisher */
245 char *from_uri;
246 /*! \brief The To URI for this specific publisher */
247 char *to_uri;
248 /*! \brief Timer entry for refreshing publish */
249 pj_timer_entry timer;
250 /*! \brief The number of auth attempts done */
251 unsigned int auth_attempts;
252 /*! \brief Queue of outgoing publish messages to send*/
254 /*! \brief The message currently being sent */
256 /*! \brief Publish client should be destroyed */
257 unsigned int destroy;
258 /*! \brief Serializer for stuff and things */
260 /*! \brief User, if any, associated with the publisher */
261 char user[0];
262};
263
264/*! \brief Outbound publish client state information (persists for lifetime of a publish) */
266 /*! \brief Outbound publish information */
268 /*! \brief Publisher datastores set up by handlers */
270 /*! \brief Container of all the client publishing objects */
272 /*! \brief Publishing has been fully started and event type informed */
273 unsigned int started;
274};
275
276/*! \brief Outbound publish state information (persists for lifetime of a publish) */
278 /*! \brief Outbound publish client */
280 /* publish state id lookup key - same as publish configuration id */
281 char id[0];
282};
283
284/*!
285 * \brief Used for locking while loading/reloading
286 *
287 * Mutli-user configurations make it so publishers can be dynamically added and
288 * removed. Publishers should not be added or removed during a [re]load since
289 * it could cause the current_clients container to be out of sync. Thus the
290 * reason for this lock.
291 */
293
294#define DEFAULT_PUBLISHER_BUCKETS 119
297
298/*! Time needs to be long enough for a transaction to timeout if nothing replies. */
299#define MAX_UNLOAD_TIMEOUT_TIME 35 /* Seconds */
300
301/*! Shutdown group to monitor sip_outbound_registration_client_state serializers. */
303
304/*! \brief Default number of client state container buckets */
305#define DEFAULT_STATE_BUCKETS 31
306static AO2_GLOBAL_OBJ_STATIC(current_states);
307/*! \brief Used on [re]loads to hold new state data */
309
310/*! \brief hashing function for state objects */
311static int outbound_publish_state_hash(const void *obj, const int flags)
312{
313 const struct ast_sip_outbound_publish_state *object;
314 const char *key;
315
316 switch (flags & OBJ_SEARCH_MASK) {
317 case OBJ_SEARCH_KEY:
318 key = obj;
319 break;
321 object = obj;
322 key = object->id;
323 break;
324 default:
325 ast_assert(0);
326 return 0;
327 }
328 return ast_str_hash(key);
329}
330
331/*! \brief comparator function for client objects */
332static int outbound_publish_state_cmp(void *obj, void *arg, int flags)
333{
334 const struct ast_sip_outbound_publish_state *object_left = obj;
335 const struct ast_sip_outbound_publish_state *object_right = arg;
336 const char *right_key = arg;
337 int cmp;
338
339 switch (flags & OBJ_SEARCH_MASK) {
341 right_key = object_right->id;
342 /* Fall through */
343 case OBJ_SEARCH_KEY:
344 cmp = strcmp(object_left->id, right_key);
345 break;
347 /* Not supported by container. */
348 ast_assert(0);
349 return 0;
350 default:
351 cmp = 0;
352 break;
353 }
354 if (cmp) {
355 return 0;
356 }
357 return CMP_MATCH;
358}
359
361{
362 struct ao2_container *container;
364
366 ast_sip_get_sorcery(), "outbound-publish",
368
369 if (!new_states) {
370 return container;
371 }
372
376
377 return container;
378}
379
381
386
388{
390
392 if (!strcmp(iter->event_name, event_name)) {
393 break;
394 }
395 }
396 return iter;
397}
398
399/*! \brief Helper function which cancels the refresh timer on a publisher */
400static void cancel_publish_refresh(struct sip_outbound_publisher *publisher)
401{
402 if (pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(ast_sip_get_pjsip_endpoint()),
403 &publisher->timer, 0)) {
404 /* The timer was successfully cancelled, drop the refcount of the publisher */
405 ao2_ref(publisher, -1);
406 }
407}
408
409/*! \brief Helper function which sets up the timer to send publication */
410static void schedule_publish_refresh(struct sip_outbound_publisher *publisher, int expiration)
411{
413 pj_time_val delay = { .sec = 0, };
414
415 cancel_publish_refresh(publisher);
416
417 if (expiration > 0) {
418 delay.sec = expiration - PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH;
419 }
420 if (publish->expiration && ((delay.sec > publish->expiration) || !delay.sec)) {
421 delay.sec = publish->expiration;
422 }
423 if (delay.sec < PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH) {
424 delay.sec = PJSIP_PUBLISHC_DELAY_BEFORE_REFRESH;
425 }
426
427 ao2_ref(publisher, +1);
428 if (pjsip_endpt_schedule_timer(ast_sip_get_pjsip_endpoint(), &publisher->timer, &delay) != PJ_SUCCESS) {
429 ast_log(LOG_WARNING, "Failed to pass timed publish refresh to scheduler\n");
430 ao2_ref(publisher, -1);
431 }
432 ao2_ref(publish, -1);
433}
434
435static int publisher_client_send(void *obj, void *arg, void *data, int flags);
436
437/*! \brief Publish client timer callback function */
438static void sip_outbound_publish_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
439{
440 struct sip_outbound_publisher *publisher = entry->user_data;
441
442 ao2_lock(publisher);
443 if (AST_LIST_EMPTY(&publisher->queue)) {
444 int res;
445 /* If there are no outstanding messages send an empty PUBLISH message so our publication doesn't expire */
446 publisher_client_send(publisher, NULL, &res, 0);
447 }
448 ao2_unlock(publisher);
449
450 ao2_ref(publisher, -1);
451}
452
453/*! \brief Task for cancelling a refresh timer */
454static int cancel_refresh_timer_task(void *data)
455{
456 struct sip_outbound_publisher *publisher = data;
457
458 cancel_publish_refresh(publisher);
459 ao2_ref(publisher, -1);
460
461 return 0;
462}
463
464static void set_transport(struct sip_outbound_publisher *publisher, pjsip_tx_data *tdata)
465{
466 if (!ast_strlen_zero(publisher->owner->publish->transport)) {
467 pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
468
470 publisher->owner->publish->transport, &selector);
471 pjsip_tx_data_set_transport(tdata, &selector);
472 ast_sip_tpselector_unref(&selector);
473 }
474}
475
476/*! \brief Task for sending an unpublish */
477static int send_unpublish_task(void *data)
478{
479 struct sip_outbound_publisher *publisher = data;
480 pjsip_tx_data *tdata;
481
482 if (pjsip_publishc_unpublish(publisher->client, &tdata) == PJ_SUCCESS) {
483 set_transport(publisher, tdata);
484 pjsip_publishc_send(publisher->client, tdata);
485 }
486
487 ao2_ref(publisher, -1);
488
489 return 0;
490}
491
494{
495 if (!handler) {
497 }
498
499 if (handler) {
500 handler->stop_publishing(client);
501 }
502}
503
504static int cancel_and_unpublish(void *obj, void *arg, int flags);
505
506/*! \brief Helper function which starts or stops publish clients when applicable */
508{
510 struct ao2_container *states;
511 struct ao2_iterator i;
513
514 if (!publishes) {
515 return;
516 }
517
518 states = ao2_global_obj_ref(current_states);
519 if (!states) {
520 return;
521 }
522
523 i = ao2_iterator_init(states, 0);
524 while ((state = ao2_iterator_next(&i))) {
525 struct ast_sip_outbound_publish *publish = ao2_bump(state->client->publish);
527
528 if (!state->client->started) {
529 /* If the publisher client has not yet been started try to start it */
530 if (!handler) {
531 ast_debug(2, "Could not find handler for event '%s' for outbound publish client '%s'\n",
533 } else if (handler->start_publishing(publish, state->client)) {
534 ast_log(LOG_ERROR, "Failed to start outbound publish with event '%s' for client '%s'\n",
536 } else {
537 state->client->started = 1;
538 }
539 } else if (state->client->started && !handler && removed && !strcmp(publish->event, removed->event_name)) {
540 stop_publishing(state->client, removed);
541 ao2_callback(state->client->publishers, OBJ_NODATA, cancel_and_unpublish, NULL);
542 state->client->started = 0;
543 }
544 ao2_ref(publish, -1);
545 ao2_ref(state, -1);
546 }
548 ao2_ref(states, -1);
549}
550
552{
553 struct ao2_container *states = ao2_global_obj_ref(current_states);
555
556 if (!states) {
557 return NULL;
558 }
559
560 res = ao2_find(states, id, OBJ_SEARCH_KEY);
561 ao2_ref(states, -1);
562 return res;
563}
564
566{
568
569 if (!state) {
570 return NULL;
571 }
572
573 ao2_ref(state->client, +1);
574 ao2_ref(state, -1);
575 return state->client;
576}
577
579{
580 struct ast_sip_outbound_publish *publish = client->publish;
581
582 return S_OR(publish->from_uri, S_OR(publish->server_uri, ""));
583}
584
586 struct ast_sip_outbound_publish_client *client, const char *user);
587
589 struct ast_sip_outbound_publish_client *client, const char *user)
590{
591 struct sip_outbound_publisher *publisher;
592
593 /*
594 * Lock before searching since there could be a race between searching and adding.
595 * Just use the load_lock since we might need to lock it anyway (if adding) and
596 * also it simplifies the code (otherwise we'd have to lock the publishers, no-
597 * lock the search and pass a flag to 'add publisher to no-lock the potential link).
598 */
600 publisher = ao2_find(client->publishers, user, OBJ_SEARCH_KEY);
601 if (!publisher) {
604 return NULL;
605 }
606 }
608
609 return publisher;
610}
611
613 char *uri, size_t size)
614{
615 struct sip_outbound_publisher *publisher;
616
618 if (!publisher) {
619 return NULL;
620 }
621
622 ast_copy_string(uri, publisher->from_uri, size);
623 ao2_ref(publisher, -1);
624
625 return uri;
626}
627
629{
630 struct ast_sip_outbound_publish *publish = client->publish;
631
632 return S_OR(publish->to_uri, S_OR(publish->server_uri, ""));
633}
634
636 char *uri, size_t size)
637{
638 struct sip_outbound_publisher *publisher;
639
641 if (!publisher) {
642 return NULL;
643 }
644
645 ast_copy_string(uri, publisher->to_uri, size);
646 ao2_ref(publisher, -1);
647
648 return uri;
649}
650
652{
653 struct ast_sip_event_publisher_handler *existing;
655
656 if (!handler->start_publishing || !handler->stop_publishing) {
657 ast_log(LOG_ERROR, "Handler does not implement required callbacks. Cannot register\n");
658 return -1;
659 } else if (ast_strlen_zero(handler->event_name)) {
660 ast_log(LOG_ERROR, "No event package specified for event publisher handler. Cannot register\n");
661 return -1;
662 }
663
664 existing = find_publisher_handler_for_event_name(handler->event_name);
665 if (existing) {
666 ast_log(LOG_ERROR, "Unable to register event publisher handler for event %s. "
667 "A handler is already registered\n", handler->event_name);
668 return -1;
669 }
670
672
674
675 return 0;
676}
677
692
693/*! \brief Destructor function for publish information */
694static void sip_outbound_publish_destroy(void *obj)
695{
696 struct ast_sip_outbound_publish *publish = obj;
697
698 ast_sip_auth_vector_destroy(&publish->outbound_auths);
699
701}
702
703/*! \brief Allocator function for publish information */
704static void *sip_outbound_publish_alloc(const char *name)
705{
708
709 if (!publish || ast_string_field_init(publish, 256)) {
711 return NULL;
712 }
713
714 return publish;
715}
716
718{
719 struct ast_datastore *datastore = obj;
720
721 /* Using the destroy function (if present) destroy the data */
722 if (datastore->info->destroy != NULL && datastore->data != NULL) {
723 datastore->info->destroy(datastore->data);
724 datastore->data = NULL;
725 }
726
727 ast_free((void *) datastore->uid);
728 datastore->uid = NULL;
729}
730
732{
733 RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
734 const char *uid_ptr = uid;
735 char uuid_buf[AST_UUID_STR_LEN];
736
737 if (!info) {
738 return NULL;
739 }
740
741 datastore = ao2_alloc(sizeof(*datastore), sip_outbound_publish_datastore_destroy);
742 if (!datastore) {
743 return NULL;
744 }
745
746 datastore->info = info;
747 if (ast_strlen_zero(uid)) {
748 /* They didn't provide an ID so we'll provide one ourself */
749 uid_ptr = ast_uuid_generate_str(uuid_buf, sizeof(uuid_buf));
750 }
751
752 datastore->uid = ast_strdup(uid_ptr);
753 if (!datastore->uid) {
754 return NULL;
755 }
756
757 ao2_ref(datastore, +1);
758 return datastore;
759}
760
762 struct ast_datastore *datastore)
763{
764 ast_assert(datastore != NULL);
765 ast_assert(datastore->info != NULL);
766 ast_assert(!ast_strlen_zero(datastore->uid));
767
768 if (!ao2_link(client->datastores, datastore)) {
769 return -1;
770 }
771 return 0;
772}
773
779
785
787{
788 RAII_VAR(struct sip_outbound_publisher *, publisher, data, ao2_cleanup);
789 SCOPED_AO2LOCK(lock, publisher);
791 pjsip_tx_data *tdata;
792 pj_status_t status;
793
794 if (publisher->destroy || publisher->sending || !(message = AST_LIST_FIRST(&publisher->queue))) {
795 return 0;
796 }
797
798 if (pjsip_publishc_publish(publisher->client, PJ_FALSE, &tdata) != PJ_SUCCESS) {
799 goto fatal;
800 }
801
802 if (!ast_strlen_zero(message->body.type) && !ast_strlen_zero(message->body.subtype) &&
803 ast_sip_add_body(tdata, &message->body)) {
804 pjsip_tx_data_dec_ref(tdata);
805 goto fatal;
806 }
807
808 set_transport(publisher, tdata);
809
810 status = pjsip_publishc_send(publisher->client, tdata);
811 if (status == PJ_EBUSY) {
812 /* We attempted to send the message but something else got there first */
813 goto service;
814 } else if (status != PJ_SUCCESS) {
815 goto fatal;
816 }
817
818 publisher->sending = message;
819
820 return 0;
821
822fatal:
823 AST_LIST_REMOVE_HEAD(&publisher->queue, entry);
825
826service:
827 if (ast_sip_push_task(publisher->serializer, sip_publisher_service_queue, ao2_bump(publisher))) {
828 ao2_ref(publisher, -1);
829 }
830 return -1;
831}
832
833static int publisher_client_send(void *obj, void *arg, void *data, int flags)
834{
835 struct sip_outbound_publisher *publisher = obj;
836 const struct ast_sip_body *body = arg;
838 size_t type_len = 0, subtype_len = 0, body_text_len = 0;
839 int *res = data;
840 SCOPED_AO2LOCK(lock, publisher);
841
842 *res = -1;
843 if (!publisher->client) {
844 return -1;
845 }
846
847 /* If a body is present we need more space for the contents of it */
848 if (body) {
849 type_len = strlen(body->type) + 1;
850 subtype_len = strlen(body->subtype) + 1;
851 body_text_len = strlen(body->body_text) + 1;
852 }
853
854 message = ast_calloc(1, sizeof(*message) + type_len + subtype_len + body_text_len);
855 if (!message) {
856 return -1;
857 }
858
859 if (body) {
860 char *dst = message->body_contents;
861
862 message->body.type = strcpy(dst, body->type);
863 dst += type_len;
864 message->body.subtype = strcpy(dst, body->subtype);
865 dst += subtype_len;
866 message->body.body_text = strcpy(dst, body->body_text);
867 }
868
870
872 if (*res) {
873 ao2_ref(publisher, -1);
874 }
875
876 return *res;
877}
878
880 const struct ast_sip_body *body)
881{
882 SCOPED_AO2LOCK(lock, client);
883 int res = 0;
884
886 publisher_client_send, (void *)body, &res);
887 return res;
888}
889
891 pj_pool_t *pool, const char *uri, const char *user, pj_str_t *res_uri)
892{
893 pj_str_t tmp;
894 pjsip_uri *parsed;
895 pjsip_sip_uri *parsed_uri;
896 int size;
897
898 pj_strdup2_with_null(pool, &tmp, uri);
899 if (!(parsed = pjsip_parse_uri(pool, tmp.ptr, tmp.slen, 0))) {
900 return -1;
901 }
902
903 if (!(parsed_uri = pjsip_uri_get_uri(parsed))) {
904 return -1;
905 }
906
907 if (!ast_strlen_zero(user)) {
908 pj_strdup2(pool, &parsed_uri->user, user);
909 }
910
911 res_uri->ptr = (char*) pj_pool_alloc(pool, pjsip_max_url_size);
912 if (!res_uri->ptr) {
913 return -1;
914 }
915
916 if ((size = pjsip_uri_print(PJSIP_URI_IN_OTHER, parsed_uri, res_uri->ptr,
917 pjsip_max_url_size - 1)) <= 0) {
918 return -1;
919 }
920 res_uri->ptr[size] = '\0';
921 res_uri->slen = size;
922
923 return 0;
924}
925
927 pj_pool_t *pool, struct sip_outbound_publisher *publisher,
928 pj_str_t *server_uri, pj_str_t *to_uri, pj_str_t *from_uri)
929{
930 struct ast_sip_outbound_publish *publish = publisher->owner->publish;
931
932 if (sip_outbound_publisher_set_uri(pool, publish->server_uri, publisher->user, server_uri)) {
933 ast_log(LOG_ERROR, "Invalid server URI '%s' specified on outbound publish '%s'\n",
935 return -1;
936 }
937
938 if (ast_strlen_zero(publish->to_uri)) {
939 to_uri->ptr = server_uri->ptr;
940 to_uri->slen = server_uri->slen;
941 } else if (sip_outbound_publisher_set_uri(pool, publish->to_uri, publisher->user, to_uri)) {
942 ast_log(LOG_ERROR, "Invalid to URI '%s' specified on outbound publish '%s'\n",
944 return -1;
945 }
946
947 publisher->to_uri = ast_strdup(to_uri->ptr);
948 if (!publisher->to_uri) {
949 return -1;
950 }
951
952 if (ast_strlen_zero(publish->from_uri)) {
953 from_uri->ptr = server_uri->ptr;
954 from_uri->slen = server_uri->slen;
955 } else if (sip_outbound_publisher_set_uri(pool, publish->from_uri, publisher->user, from_uri)) {
956 ast_log(LOG_ERROR, "Invalid from URI '%s' specified on outbound publish '%s'\n",
958 return -1;
959 }
960
961 publisher->from_uri = ast_strdup(from_uri->ptr);
962 if (!publisher->from_uri) {
963 return -1;
964 }
965
966 return 0;
967}
968
969static void sip_outbound_publish_callback(struct pjsip_publishc_cbparam *param);
970
971/*! \brief Helper function that allocates a pjsip publish client and configures it */
972static int sip_outbound_publisher_init(void *data)
973{
974 struct sip_outbound_publisher *publisher = data;
976 pjsip_publishc_opt opt = {
977 .queue_request = PJ_FALSE,
978 };
979 pj_pool_t *pool;
980 pj_str_t event, server_uri, to_uri, from_uri;
981
982 if (publisher->client) {
983 return 0;
984 }
985
986 if (pjsip_publishc_create(ast_sip_get_pjsip_endpoint(), &opt,
988 &publisher->client) != PJ_SUCCESS) {
989 ao2_ref(publisher, -1);
990 return -1;
991 }
992
993 publish = ao2_bump(publisher->owner->publish);
994
995 if (!ast_strlen_zero(publish->outbound_proxy)) {
996 pjsip_route_hdr route_set, *route;
997 static const pj_str_t ROUTE_HNAME = { "Route", 5 };
998
999 pj_list_init(&route_set);
1000
1001 if (!(route = pjsip_parse_hdr(pjsip_publishc_get_pool(publisher->client), &ROUTE_HNAME,
1002 (char*)publish->outbound_proxy, strlen(publish->outbound_proxy), NULL))) {
1003 pjsip_publishc_destroy(publisher->client);
1004 return -1;
1005 }
1006 pj_list_insert_nodes_before(&route_set, route);
1007
1008 pjsip_publishc_set_route_set(publisher->client, &route_set);
1009 }
1010
1011 pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "URI Validation",
1013 if (!pool) {
1014 ast_log(LOG_ERROR, "Could not create pool for URI validation on outbound publish '%s'\n",
1016 pjsip_publishc_destroy(publisher->client);
1017 return -1;
1018 }
1019
1020 if (sip_outbound_publisher_set_uris(pool, publisher, &server_uri, &from_uri, &to_uri)) {
1021 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
1022 pjsip_publishc_destroy(publisher->client);
1023 return -1;
1024 }
1025
1026 pj_cstr(&event, publish->event);
1027 if (pjsip_publishc_init(publisher->client, &event, &server_uri, &from_uri, &to_uri,
1028 publish->expiration) != PJ_SUCCESS) {
1029 ast_log(LOG_ERROR, "Failed to initialize publishing client on outbound publish '%s'\n",
1031 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
1032 pjsip_publishc_destroy(publisher->client);
1033 return -1;
1034 }
1035
1036 pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
1037 return 0;
1038}
1039
1040static int sip_outbound_publisher_reinit(void *obj, void *arg, int flags)
1041{
1042 return sip_outbound_publisher_init(obj);
1043}
1044
1046{
1048 return 0;
1049}
1050
1051/*! \brief Destructor function for publish client */
1053{
1054 struct sip_outbound_publisher *publisher = obj;
1056
1057 /* You might be tempted to think "the publish client isn't being destroyed" but it actually is - just elsewhere */
1058
1059 while ((message = AST_LIST_REMOVE_HEAD(&publisher->queue, entry))) {
1061 }
1062
1063 ao2_cleanup(publisher->owner);
1064 ast_free(publisher->from_uri);
1065 ast_free(publisher->to_uri);
1066
1068}
1069
1071 struct ast_sip_outbound_publish_client *client, const char *user)
1072{
1073 struct sip_outbound_publisher *publisher;
1074 char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];
1075
1076 publisher = ao2_alloc(sizeof(*publisher) + (user ? strlen(user) : 0) + 1,
1078 if (!publisher) {
1079 return NULL;
1080 }
1081
1082 /*
1083 * Bump the ref to the client. This essentially creates a circular reference,
1084 * but it is needed in order to make sure the client object doesn't get pulled
1085 * out from under us when the publisher stops publishing.
1086 *
1087 * The circular reference is alleviated by calling cancel_and_unpublish for
1088 * each client, from the state's destructor. By calling it there all references
1089 * to the publishers should go to zero, thus calling the publisher's destructor.
1090 * This in turn removes the client reference we added here. The state then removes
1091 * its reference to the client, which should take it to zero.
1092 */
1093 publisher->owner = ao2_bump(client);
1094 publisher->timer.user_data = publisher;
1095 publisher->timer.cb = sip_outbound_publish_timer_cb;
1096 if (user) {
1097 strcpy(publisher->user, user);
1098 } else {
1099 *publisher->user = '\0';
1100 }
1101
1102 ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/outpub/%s",
1104
1105 publisher->serializer = ast_sip_create_serializer_group(tps_name,
1107 if (!publisher->serializer) {
1108 ao2_ref(publisher, -1);
1109 return NULL;
1110 }
1111
1113 ast_log(LOG_ERROR, "Unable to create publisher for outbound publish '%s'\n",
1115 ao2_ref(publisher, -1);
1116 return NULL;
1117 }
1118
1119 return publisher;
1120}
1121
1123 struct ast_sip_outbound_publish_client *client, const char *user)
1124{
1125 struct sip_outbound_publisher *publisher =
1127
1128 if (!publisher) {
1129 return NULL;
1130 }
1131
1132 if (!ao2_link(client->publishers, publisher)) {
1133 /*
1134 * No need to bump the reference here. The task will take care of
1135 * removing the reference.
1136 */
1137 if (ast_sip_push_task(publisher->serializer, cancel_refresh_timer_task, publisher)) {
1138 ao2_ref(publisher, -1);
1139 }
1140 return NULL;
1141 }
1142
1143 return publisher;
1144}
1145
1147 const char *user, const struct ast_sip_body *body)
1148{
1149 struct sip_outbound_publisher *publisher;
1150 int res;
1151
1153 if (!publisher) {
1154 return -1;
1155 }
1156
1157 publisher_client_send(publisher, (void *)body, &res, 0);
1158 ao2_ref(publisher, -1);
1159 return res;
1160}
1161
1168
1169static int explicit_publish_destroy(void *data)
1170{
1171 struct sip_outbound_publisher *publisher = data;
1172
1173 /*
1174 * If there is no pjsip publishing client then we obviously don't need
1175 * to destroy it. Also, the ref for the Asterisk publishing client that
1176 * pjsip had would not exist or should already be gone as well.
1177 */
1178 if (publisher->client) {
1179 pjsip_publishc_destroy(publisher->client);
1180 ao2_ref(publisher, -1);
1181 }
1182
1183 ao2_ref(publisher, -1);
1184
1185 return 0;
1186}
1187
1188/*! \brief Helper function which cancels and un-publishes a no longer used client */
1189static int cancel_and_unpublish(void *obj, void *arg, int flags)
1190{
1191 struct sip_outbound_publisher *publisher = obj;
1192 struct ast_sip_outbound_publish_client *client = publisher->owner;
1193
1194 SCOPED_AO2LOCK(lock, publisher);
1195
1196 if (!client->started) {
1197 /* If the publisher was never started, there's nothing to unpublish, so just
1198 * destroy the publication and remove its reference to the publisher.
1199 */
1200 if (ast_sip_push_task(publisher->serializer, explicit_publish_destroy, ao2_bump(publisher))) {
1201 ao2_ref(publisher, -1);
1202 }
1203 return 0;
1204 }
1205
1206 if (ast_sip_push_task(publisher->serializer, cancel_refresh_timer_task, ao2_bump(publisher))) {
1207 ast_log(LOG_WARNING, "Could not stop refresh timer on outbound publish '%s'\n",
1209 ao2_ref(publisher, -1);
1210 }
1211
1212 /* If nothing is being sent right now send the unpublish - the destroy will happen in the subsequent callback */
1213 if (!publisher->sending) {
1214 if (ast_sip_push_task(publisher->serializer, send_unpublish_task, ao2_bump(publisher))) {
1215 ast_log(LOG_WARNING, "Could not send unpublish message on outbound publish '%s'\n",
1217 ao2_ref(publisher, -1);
1218 }
1219 }
1220 publisher->destroy = 1;
1221 return 0;
1222}
1223
1224/*! \brief Destructor function for publish client */
1226{
1227 struct ast_sip_outbound_publish_client *client = obj;
1228
1229 ao2_cleanup(client->datastores);
1230
1231 /*
1232 * The client's publishers have already been unpublished and destroyed
1233 * by this point, so it is safe to finally remove the reference to the
1234 * publish object. The client needed to hold a reference to it until
1235 * the publishers were done with it.
1236 */
1237 ao2_cleanup(client->publish);
1238}
1239
1240/*! \brief Destructor function for publish state */
1242{
1244
1245 stop_publishing(state->client, NULL);
1246 /*
1247 * Since the state is being destroyed the associated client needs to also
1248 * be destroyed. However simply removing the reference to the client will
1249 * not initiate client destruction since the client's publisher(s) hold a
1250 * reference to the client object as well. So we need to unpublish the
1251 * the client's publishers here, which will remove the publisher's client
1252 * reference during that process.
1253 *
1254 * That being said we don't want to remove the client's reference to the
1255 * publish object just yet. We'll hold off on that until client destruction
1256 * itself. This is because the publishers need access to the client's
1257 * publish object while they are unpublishing.
1258 */
1260 ao2_cleanup(state->client->publishers);
1261
1262 state->client->started = 0;
1263 ao2_cleanup(state->client);
1264}
1265
1266/*!
1267 * \internal
1268 * \brief Check if a publish can be reused
1269 *
1270 * This checks if the existing outbound publish's configuration differs from a newly-applied
1271 * outbound publish.
1272 *
1273 * \param existing The pre-existing outbound publish
1274 * \param applied The newly-created publish
1275 */
1276static int can_reuse_publish(struct ast_sip_outbound_publish *existing, struct ast_sip_outbound_publish *applied)
1277{
1278 int i;
1279
1280 if (strcmp(existing->server_uri, applied->server_uri) || strcmp(existing->from_uri, applied->from_uri) ||
1281 strcmp(existing->to_uri, applied->to_uri) || strcmp(existing->outbound_proxy, applied->outbound_proxy) ||
1282 strcmp(existing->event, applied->event) ||
1284 return 0;
1285 }
1286
1287 for (i = 0; i < AST_VECTOR_SIZE(&existing->outbound_auths); ++i) {
1288 if (strcmp(AST_VECTOR_GET(&existing->outbound_auths, i), AST_VECTOR_GET(&applied->outbound_auths, i))) {
1289 return 0;
1290 }
1291 }
1292
1293 return 1;
1294}
1295
1296/*! \brief Callback function for publish client responses */
1297static void sip_outbound_publish_callback(struct pjsip_publishc_cbparam *param)
1298{
1299#define DESTROY_CLIENT() do { \
1300 pjsip_publishc_destroy(publisher->client); \
1301 publisher->client = NULL; \
1302 ao2_ref(publisher, -1); } while (0)
1303
1304 RAII_VAR(struct sip_outbound_publisher *, publisher, ao2_bump(param->token), ao2_cleanup);
1305 RAII_VAR(struct ast_sip_outbound_publish *, publish, ao2_bump(publisher->owner->publish), ao2_cleanup);
1306 SCOPED_AO2LOCK(lock, publisher);
1307 pjsip_tx_data *tdata;
1308
1309 if (publisher->destroy) {
1310 if (publisher->sending) {
1311 publisher->sending = NULL;
1312
1313 if (!ast_sip_push_task(publisher->serializer, send_unpublish_task, ao2_bump(publisher))) {
1314 return;
1315 }
1316 ast_log(LOG_WARNING, "Could not send unpublish message on outbound publish '%s'\n",
1318 ao2_ref(publisher, -1);
1319 }
1320 /* Once the destroy is called this callback will not get called any longer, so drop the publisher ref */
1322 return;
1323 }
1324
1325 if (param->code == 401 || param->code == 407) {
1326 pjsip_transaction *tsx = pjsip_rdata_get_tsx(param->rdata);
1327
1328 if (!ast_sip_create_request_with_auth(&publish->outbound_auths,
1329 param->rdata, tsx->last_tx, &tdata)) {
1330 set_transport(publisher, tdata);
1331 pjsip_publishc_send(publisher->client, tdata);
1332 }
1333 publisher->auth_attempts++;
1334
1335 if (publisher->auth_attempts == publish->max_auth_attempts) {
1337 ast_log(LOG_ERROR, "Reached maximum number of PUBLISH authentication attempts on outbound publish '%s'\n",
1339
1340 goto end;
1341 }
1342 return;
1343 }
1344
1345 publisher->auth_attempts = 0;
1346
1347 if (param->code == 412) {
1349 if (sip_outbound_publisher_init(publisher)) {
1350 ast_log(LOG_ERROR, "Failed to create a new outbound publish client for '%s' on 412 response\n",
1352 goto end;
1353 }
1354
1355 /* Setting this to NULL will cause a new PUBLISH to get created and sent for the same underlying body */
1356 publisher->sending = NULL;
1357 } else if (param->code == 423) {
1358 /* Update the expiration with the new expiration time if available */
1359 pjsip_expires_hdr *expires;
1360
1361 expires = pjsip_msg_find_hdr(param->rdata->msg_info.msg, PJSIP_H_MIN_EXPIRES, NULL);
1362 if (!expires || !expires->ivalue) {
1364 ast_log(LOG_ERROR, "Received 423 response on outbound publish '%s' without a Min-Expires header\n",
1366 goto end;
1367 }
1368
1369 pjsip_publishc_update_expires(publisher->client, expires->ivalue);
1370 publisher->sending = NULL;
1371 } else if (publisher->sending) {
1372 /* Remove the message currently being sent so that when the queue is serviced another will get sent */
1373 AST_LIST_REMOVE_HEAD(&publisher->queue, entry);
1374 ast_free(publisher->sending);
1375 publisher->sending = NULL;
1376 if (!param->rdata) {
1377 ast_log(LOG_NOTICE, "No response received for outbound publish '%s'\n",
1379 }
1380 }
1381
1382 if (AST_LIST_EMPTY(&publisher->queue)) {
1383 schedule_publish_refresh(publisher, param->expiration);
1384 }
1385
1386end:
1387 if (!publisher->client) {
1389
1390 while ((message = AST_LIST_REMOVE_HEAD(&publisher->queue, entry))) {
1392 }
1393 } else {
1394 if (ast_sip_push_task(publisher->serializer, sip_publisher_service_queue, ao2_bump(publisher))) {
1395 ao2_ref(publisher, -1);
1396 }
1397 }
1398}
1399
1400#define DATASTORE_BUCKETS 53
1401
1402static int datastore_hash(const void *obj, int flags)
1403{
1404 const struct ast_datastore *datastore;
1405 const char *uid;
1406
1407 switch (flags & OBJ_SEARCH_MASK) {
1408 case OBJ_SEARCH_KEY:
1409 uid = obj;
1410 break;
1411 case OBJ_SEARCH_OBJECT:
1412 datastore = obj;
1413 uid = datastore->uid;
1414 break;
1415 default:
1416 /* Hash can only work on something with a full key. */
1417 ast_assert(0);
1418 return 0;
1419 }
1420
1421 return ast_str_hash(uid);
1422}
1423
1424static int datastore_cmp(void *obj, void *arg, int flags)
1425{
1426 const struct ast_datastore *object_left = obj;
1427 const struct ast_datastore *object_right = arg;
1428 const char *right_key = arg;
1429 int cmp;
1430
1431 switch (flags & OBJ_SEARCH_MASK) {
1432 case OBJ_SEARCH_OBJECT:
1433 right_key = object_right->uid;
1434 /* Fall through */
1435 case OBJ_SEARCH_KEY:
1436 cmp = strcmp(object_left->uid, right_key);
1437 break;
1439 cmp = strncmp(object_left->uid, right_key, strlen(right_key));
1440 break;
1441 default:
1442 /*
1443 * What arg points to is specific to this traversal callback
1444 * and has no special meaning to astobj2.
1445 */
1446 cmp = 0;
1447 break;
1448 }
1449 if (cmp) {
1450 return 0;
1451 }
1452 /*
1453 * At this point the traversal callback is identical to a sorted
1454 * container.
1455 */
1456 return CMP_MATCH;
1457}
1458
1459/*! \brief Allocator function for publish client */
1462{
1463 const char *id = ast_sorcery_object_get_id(publish);
1465 ao2_alloc(sizeof(*state) + strlen(id) + 1, sip_outbound_publish_state_destroy);
1466
1467 if (!state) {
1468 return NULL;
1469 }
1470
1471 state->client = ao2_alloc(sizeof(*state->client), sip_outbound_publish_client_destroy);
1472 if (!state->client) {
1473 ao2_ref(state, -1);
1474 return NULL;
1475 }
1476
1479 if (!state->client->datastores) {
1480 ao2_ref(state, -1);
1481 return NULL;
1482 }
1483
1486 sip_outbound_publisher_hash_fn, NULL, sip_outbound_publisher_cmp_fn);
1487 if (!state->client->publishers) {
1488 ao2_ref(state, -1);
1489 return NULL;
1490 }
1491
1492 state->client->publish = ao2_bump(publish);
1493
1494 strcpy(state->id, id);
1495 return state;
1496}
1497
1499{
1500 if (ast_strlen_zero(publish->server_uri)) {
1501 ast_log(LOG_ERROR, "No server URI specified on outbound publish '%s'\n",
1503 return -1;
1504 } else if (ast_sip_validate_uri_length(publish->server_uri)) {
1505 ast_log(LOG_ERROR, "Server URI or hostname length exceeds pjproject limit or is not a sip(s) uri: '%s' on outbound publish '%s'\n",
1506 publish->server_uri,
1508 return -1;
1509 } else if (ast_strlen_zero(publish->event)) {
1510 ast_log(LOG_ERROR, "No event type specified for outbound publish '%s'\n",
1512 return -1;
1513 } else if (!ast_strlen_zero(publish->from_uri)
1514 && ast_sip_validate_uri_length(publish->from_uri)) {
1515 ast_log(LOG_ERROR, "From URI or hostname length exceeds pjproject limit or is not a sip(s) uri: '%s' on outbound publish '%s'\n",
1516 publish->from_uri,
1518 return -1;
1519 } else if (!ast_strlen_zero(publish->to_uri)
1521 ast_log(LOG_ERROR, "To URI or hostname length exceeds pjproject limit or is not a sip(s) uri: '%s' on outbound publish '%s'\n",
1522 publish->to_uri,
1524 return -1;
1525 }
1526 return 0;
1527}
1528
1530 struct ast_sip_outbound_publish_state *current_state)
1531{
1532 struct ast_sip_outbound_publish *old_publish;
1533
1534 /*
1535 * Don't maintain the old state/client objects if the multi_user option changed.
1536 */
1537 if ((!publish->multi_user && current_state->client->publish->multi_user) ||
1538 (publish->multi_user && !current_state->client->publish->multi_user)) {
1539 return 0;
1540 }
1541
1542
1543 if (!can_reuse_publish(current_state->client->publish, publish)) {
1544 /*
1545 * Something significant has changed in the configuration, so we are
1546 * unable to use the old state object. The current state needs to go
1547 * away and a new one needs to be created.
1548 */
1549 return 0;
1550 }
1551
1552 /*
1553 * We can reuse the current state object so keep it, but swap out the
1554 * underlying publish object with the new one.
1555 */
1556 old_publish = current_state->client->publish;
1557 current_state->client->publish = publish;
1559 current_state->client->publishers)) {
1560 /*
1561 * If the state object fails to re-initialize then swap
1562 * the old publish info back in.
1563 */
1564 current_state->client->publish = publish;
1565 ast_log(LOG_ERROR, "Unable to reinitialize client(s) for outbound publish '%s'\n",
1566 ast_sorcery_object_get_id(current_state->client->publish));
1567 return -1;
1568 }
1569
1570 /*
1571 * Since we swapped out the publish object the new one needs a ref
1572 * while the old one needs to go away.
1573 */
1574 ao2_ref(current_state->client->publish, +1);
1575 ao2_cleanup(old_publish);
1576
1577 /* Tell the caller that the current state object should be used */
1578 return 1;
1579}
1580
1581/*! \brief Apply function which finds or allocates a state structure */
1582static int sip_outbound_publish_apply(const struct ast_sorcery *sorcery, void *obj)
1583{
1584#define ADD_TO_NEW_STATES(__obj) \
1585 do { if (__obj) { \
1586 ao2_link(new_states, __obj); \
1587 ao2_ref(__obj, -1); } } while (0)
1588
1589 struct ast_sip_outbound_publish *applied = obj;
1590 struct ast_sip_outbound_publish_state *current_state, *new_state;
1591 struct sip_outbound_publisher *publisher = NULL;
1592 int res;
1593
1594 /*
1595 * New states are being loaded or reloaded. We'll need to add the new
1596 * object if created/updated, or keep the old object if an error occurs.
1597 */
1598 if (!new_states) {
1602
1603 if (!new_states) {
1604 ast_log(LOG_ERROR, "Unable to allocate new states container\n");
1605 return -1;
1606 }
1607 }
1608
1609 /* If there is current state we'll want to maintain it if any errors occur */
1610 current_state = sip_publish_state_get(ast_sorcery_object_get_id(applied));
1611
1612 if ((res = validate_publish_config(applied))) {
1613 ADD_TO_NEW_STATES(current_state);
1614 return res;
1615 }
1616
1617 if (current_state && (res = current_state_reusable(applied, current_state))) {
1618 /*
1619 * The current state object was able to be reused, or an error
1620 * occurred. Either way we keep the current state and be done.
1621 */
1622 ADD_TO_NEW_STATES(current_state);
1623 return res == 1 ? 0 : -1;
1624 }
1625
1626 /*
1627 * No current state was found or it was unable to be reused. Either way
1628 * we'll need to create a new state object.
1629 */
1630 new_state = sip_outbound_publish_state_alloc(applied);
1631 if (!new_state) {
1632 ast_log(LOG_ERROR, "Unable to create state for outbound publish '%s'\n",
1633 ast_sorcery_object_get_id(applied));
1634 ADD_TO_NEW_STATES(current_state);
1635 return -1;
1636 };
1637
1638 if (!applied->multi_user &&
1639 !(publisher = sip_outbound_publish_client_add_publisher(new_state->client, NULL))) {
1640 ADD_TO_NEW_STATES(current_state);
1641 ao2_ref(new_state, -1);
1642 return -1;
1643 }
1644 ao2_cleanup(publisher);
1645
1646 ADD_TO_NEW_STATES(new_state);
1647 ao2_cleanup(current_state);
1648 return res;
1649}
1650
1651static int outbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
1652{
1653 struct ast_sip_outbound_publish *publish = obj;
1654
1655 return ast_sip_auth_vector_init(&publish->outbound_auths, var->value);
1656}
1657
1658
1659static int unload_module(void)
1660{
1661 int remaining;
1662
1664
1665 ao2_global_obj_release(current_states);
1666
1667 /* Wait for publication serializers to get destroyed. */
1668 ast_debug(2, "Waiting for publication to complete for unload.\n");
1670 if (remaining) {
1671 ast_log(LOG_WARNING, "Unload incomplete. Could not stop %d outbound publications. Try again later.\n",
1672 remaining);
1673 return -1;
1674 }
1675
1676 ast_debug(2, "Successful shutdown.\n");
1677
1680
1681 return 0;
1682}
1683
1684static int load_module(void)
1685{
1686 /* As of pjproject 2.4.5, PJSIP_MAX_URL_SIZE isn't exposed yet but we try anyway. */
1687 ast_pjproject_get_buildopt("PJSIP_MAX_URL_SIZE", "%d", &pjsip_max_url_size);
1688
1690 if (!shutdown_group) {
1692 }
1693
1694 ast_sorcery_apply_config(ast_sip_get_sorcery(), "res_pjsip_outbound_publish");
1695 ast_sorcery_apply_default(ast_sip_get_sorcery(), "outbound-publish", "config", "pjsip.conf,criteria=type=outbound-publish");
1696
1699 ast_log(LOG_ERROR, "Unable to register 'outbound-publish' type with sorcery\n");
1700 unload_module();
1702 }
1703
1704 ast_sorcery_object_field_register(ast_sip_get_sorcery(), "outbound-publish", "type", "", OPT_NOOP_T, 0, 0);
1710 ast_sorcery_object_field_register(ast_sip_get_sorcery(), "outbound-publish", "expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_outbound_publish, expiration));
1711 ast_sorcery_object_field_register(ast_sip_get_sorcery(), "outbound-publish", "max_auth_attempts", "5", OPT_UINT_T, 0, FLDSET(struct ast_sip_outbound_publish, max_auth_attempts));
1713 ast_sorcery_object_field_register_custom(ast_sip_get_sorcery(), "outbound-publish", "outbound_auth", "", outbound_auth_handler, NULL, NULL, 0, 0);
1714 ast_sorcery_object_field_register(ast_sip_get_sorcery(), "outbound-publish", "multi_user", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_outbound_publish, multi_user));
1715
1716 ast_sorcery_reload_object(ast_sip_get_sorcery(), "outbound-publish");
1717
1721
1722 pjsip_publishc_init_module(ast_sip_get_pjsip_endpoint());
1723
1725}
1726
1736
1738 .support_level = AST_MODULE_SUPPORT_CORE,
1739 .load = load_module,
1741 .unload = unload_module,
1742 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
1743 .requires = "res_pjproject,res_pjsip",
jack_status_t status
Definition app_jack.c:149
ast_mutex_t lock
Definition app_sla.c:337
#define var
Definition ast_expr2f.c:605
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition astmm.h:241
#define ast_calloc(num, len)
A wrapper for calloc()
Definition astmm.h:202
#define ast_log
Definition astobj2.c:42
#define ao2_iterator_next(iter)
Definition astobj2.h:1911
#define AO2_GLOBAL_OBJ_STATIC(name)
Define a global object holder to be used to hold an ao2 object, statically initialized.
Definition astobj2.h:847
#define ao2_link(container, obj)
Add an object to a container.
Definition astobj2.h:1532
@ CMP_MATCH
Definition astobj2.h:1027
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition astobj2.h:367
@ AO2_ALLOC_OPT_LOCK_MUTEX
Definition astobj2.h:363
#define ao2_global_obj_replace_unref(holder, obj)
Replace an ao2 object in the global holder, throwing away any old object.
Definition astobj2.h:901
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container,...
Definition astobj2.h:1693
#define AO2_STRING_FIELD_CMP_FN(stype, field)
Creates a compare function for a structure string field.
Definition astobj2.h:2048
#define ao2_cleanup(obj)
Definition astobj2.h:1934
#define ao2_callback_data(container, flags, cb_fn, arg, data)
Definition astobj2.h:1723
#define ao2_global_obj_ref(holder)
Get a reference to the object stored in the global holder.
Definition astobj2.h:918
#define ao2_find(container, arg, flags)
Definition astobj2.h:1736
struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags) attribute_warn_unused_result
Create an iterator for a container.
#define ao2_unlock(a)
Definition astobj2.h:729
#define ao2_lock(a)
Definition astobj2.h:717
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition astobj2.h:459
#define ao2_global_obj_release(holder)
Release the ao2 object held in the global holder.
Definition astobj2.h:859
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition astobj2.h:480
#define AO2_STRING_FIELD_HASH_FN(stype, field)
Creates a hash function for a structure string field.
Definition astobj2.h:2032
void ao2_iterator_destroy(struct ao2_iterator *iter)
Destroy a container iterator.
@ OBJ_SEARCH_PARTIAL_KEY
The arg parameter is a partial search key similar to OBJ_SEARCH_KEY.
Definition astobj2.h:1116
@ OBJ_SEARCH_OBJECT
The arg parameter is an object of the same type.
Definition astobj2.h:1087
@ OBJ_NODATA
Definition astobj2.h:1044
@ OBJ_SEARCH_MASK
Search option field mask.
Definition astobj2.h:1072
@ OBJ_UNLINK
Definition astobj2.h:1039
@ OBJ_SEARCH_KEY
The arg parameter is a search key, but is not an object.
Definition astobj2.h:1101
#define ao2_alloc(data_size, destructor_fn)
Definition astobj2.h:409
#define ao2_container_alloc_hash(ao2_options, container_options, n_buckets, hash_fn, sort_fn, cmp_fn)
Allocate and initialize a hash container with the desired number of buckets.
Definition astobj2.h:1303
enum ast_cc_service_type service
Definition ccss.c:399
static struct ast_sorcery * sorcery
#define STRFLDSET(type,...)
Convert a struct and a list of stringfield fields to an argument list of field offsets.
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
@ OPT_UINT_T
Type for default option handler for unsigned integers.
@ OPT_NOOP_T
Type for a default handler that should do nothing.
@ OPT_BOOL_T
Type for default option handler for bools (ast_true/ast_false)
@ OPT_STRINGFIELD_T
Type for default option handler for stringfields.
Asterisk datastore objects.
char * end
Definition eagi_proxy.c:73
static const char name[]
Definition format_mp3.c:68
#define ast_sip_push_task(serializer, sip_task, task_data)
Definition res_pjsip.h:2094
struct ast_taskprocessor * ast_sip_create_serializer_group(const char *name, struct ast_serializer_shutdown_group *shutdown_group)
Create a new serializer for SIP tasks.
Definition res_pjsip.c:2083
#define ast_sip_push_task_wait_servant(serializer, sip_task, task_data)
Definition res_pjsip.h:2133
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_NOTICE
#define LOG_WARNING
#define AST_RWLIST_REMOVE_CURRENT
#define AST_RWLIST_RDLOCK(head)
Read locks a list.
Definition linkedlists.h:78
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
#define AST_RWLIST_TRAVERSE_SAFE_BEGIN
#define AST_RWLIST_WRLOCK(head)
Write locks a list.
Definition linkedlists.h:52
#define AST_RWLIST_UNLOCK(head)
Attempts to unlock a read/write based list.
#define AST_RWLIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a read/write list of specified type, statically initialized.
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
#define AST_RWLIST_TRAVERSE_SAFE_END
#define AST_RWLIST_TRAVERSE
#define AST_RWLIST_INSERT_TAIL
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
#define ast_rwlock_wrlock(a)
Definition lock.h:243
#define SCOPED_AO2LOCK(varname, obj)
scoped lock specialization for ao2 mutexes.
Definition lock.h:611
#define AST_RWLOCK_DEFINE_STATIC(rwlock)
Definition lock.h:550
#define SCOPED_WRLOCK(varname, lock)
scoped lock specialization for write locks
Definition lock.h:606
#define SCOPED_LOCK(varname, lock, lockfunc, unlockfunc)
Scoped Locks.
Definition lock.h:590
#define ast_rwlock_unlock(a)
Definition lock.h:241
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition module.h:331
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition module.h:330
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition module.h:557
@ AST_MODPRI_CHANNEL_DEPEND
Definition module.h:340
@ AST_MODULE_SUPPORT_CORE
Definition module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition module.h:78
static int reload(void)
unsigned char publish
struct ao2_container * container
Definition res_fax.c:603
int ast_pjproject_get_buildopt(char *option, char *format_string,...)
Retrieve a pjproject build option.
int ast_sip_create_request_with_auth(const struct ast_sip_auth_vector *auths, pjsip_rx_data *challenge, pjsip_tx_data *tdata, pjsip_tx_data **new_request)
Create a response to an authentication challenge.
Definition res_pjsip.c:208
pjsip_endpoint * ast_sip_get_pjsip_endpoint(void)
Get a pointer to the PJSIP endpoint.
Definition res_pjsip.c:514
int ast_sip_add_body(pjsip_tx_data *tdata, const struct ast_sip_body *body)
Add a body to an outbound SIP message.
Definition res_pjsip.c:2046
void ast_sip_tpselector_unref(pjsip_tpselector *selector)
Unreference a pjsip_tpselector.
Definition res_pjsip.c:917
void ast_sip_auth_vector_destroy(struct ast_sip_auth_vector *vector)
Free contents of an auth vector.
int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip_tpselector *selector)
Sets pjsip_tpselector from ast_sip_transport.
Definition res_pjsip.c:887
struct ast_sorcery * ast_sip_get_sorcery(void)
Get a pointer to the SIP sorcery structure.
int ast_sip_auth_vector_init(struct ast_sip_auth_vector *vector, const char *auth_names)
Initialize an auth vector with the configured values.
int ast_sip_publish_client_add_datastore(struct ast_sip_outbound_publish_client *client, struct ast_datastore *datastore)
Add a datastore to a SIP event publisher.
static struct ast_sip_event_publisher_handler * find_publisher_handler_for_event_name(const char *event_name)
const char * ast_sip_publish_client_get_user_to_uri(struct ast_sip_outbound_publish_client *client, const char *user, char *uri, size_t size)
Get the To URI the client will use for a specific user.
void ast_sip_publish_client_remove_datastore(struct ast_sip_outbound_publish_client *client, const char *name)
Remove a publication datastore from an event publisher.
const char * ast_sip_publish_client_get_user_from_uri(struct ast_sip_outbound_publish_client *client, const char *user, char *uri, size_t size)
Get the From URI the client will use for a specific user.
static int can_reuse_publish(struct ast_sip_outbound_publish *existing, struct ast_sip_outbound_publish *applied)
void ast_sip_unregister_event_publisher_handler(struct ast_sip_event_publisher_handler *handler)
Unregister a publish handler.
static void sub_add_handler(struct ast_sip_event_publisher_handler *handler)
static int current_state_reusable(struct ast_sip_outbound_publish *publish, struct ast_sip_outbound_publish_state *current_state)
static void stop_publishing(struct ast_sip_outbound_publish_client *client, struct ast_sip_event_publisher_handler *handler)
static ast_rwlock_t load_lock
Used for locking while loading/reloading.
static void sip_outbound_publish_synchronize(struct ast_sip_event_publisher_handler *removed)
Helper function which starts or stops publish clients when applicable.
static struct ast_serializer_shutdown_group * shutdown_group
static struct ast_sip_outbound_publish_state * sip_publish_state_get(const char *id)
static int sip_outbound_publisher_set_uris(pj_pool_t *pool, struct sip_outbound_publisher *publisher, pj_str_t *server_uri, pj_str_t *to_uri, pj_str_t *from_uri)
static int outbound_publish_state_hash(const void *obj, const int flags)
hashing function for state objects
static void sip_outbound_publish_destroy(void *obj)
Destructor function for publish information.
#define DATASTORE_BUCKETS
static void sip_outbound_publish_callback(struct pjsip_publishc_cbparam *param)
Callback function for publish client responses.
static int sip_outbound_publisher_reinit_all(void *data)
static void sip_outbound_publish_datastore_destroy(void *obj)
#define DEFAULT_STATE_BUCKETS
Default number of client state container buckets.
struct ast_datastore * ast_sip_publish_client_alloc_datastore(const struct ast_datastore_info *info, const char *uid)
Alternative for ast_datastore_alloc()
int ast_sip_publish_client_send(struct ast_sip_outbound_publish_client *client, const struct ast_sip_body *body)
Send an outgoing PUBLISH message using a client.
static int outbound_auth_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
#define MAX_UNLOAD_TIMEOUT_TIME
static struct sip_outbound_publisher * sip_outbound_publisher_alloc(struct ast_sip_outbound_publish_client *client, const char *user)
static int pjsip_max_url_size
static int outbound_publish_state_cmp(void *obj, void *arg, int flags)
comparator function for client objects
static int cancel_refresh_timer_task(void *data)
Task for cancelling a refresh timer.
static int reload_module(void)
static int explicit_publish_destroy(void *data)
static void schedule_publish_refresh(struct sip_outbound_publisher *publisher, int expiration)
Helper function which sets up the timer to send publication.
int ast_sip_register_event_publisher_handler(struct ast_sip_event_publisher_handler *handler)
Register an event publisher handler.
static int validate_publish_config(struct ast_sip_outbound_publish *publish)
static int sip_publisher_service_queue(void *data)
static int sip_outbound_publish_apply(const struct ast_sorcery *sorcery, void *obj)
Apply function which finds or allocates a state structure.
const char * ast_sip_publish_client_get_from_uri(struct ast_sip_outbound_publish_client *client)
Get the From URI the client will use.
void ast_sip_publish_client_remove(struct ast_sip_outbound_publish_client *client, const char *user)
Remove the user from the client (stopping it from publishing)
static void sip_outbound_publisher_destroy(void *obj)
Destructor function for publish client.
int ast_sip_publish_client_user_send(struct ast_sip_outbound_publish_client *client, const char *user, const struct ast_sip_body *body)
Send an outgoing PUBLISH message based on the user.
struct ast_sip_outbound_publish_client * ast_sip_publish_client_get(const char *name)
Find a publish client using its name.
static struct ast_sip_outbound_publish_state * sip_outbound_publish_state_alloc(struct ast_sip_outbound_publish *publish)
Allocator function for publish client.
static void sip_outbound_publish_client_destroy(void *obj)
Destructor function for publish client.
static void sip_outbound_publish_state_destroy(void *obj)
Destructor function for publish state.
static int sip_outbound_publisher_reinit(void *obj, void *arg, int flags)
static void * sip_outbound_publish_alloc(const char *name)
Allocator function for publish information.
static void cancel_publish_refresh(struct sip_outbound_publisher *publisher)
Helper function which cancels the refresh timer on a publisher.
static struct sip_outbound_publisher * sip_outbound_publish_client_get_publisher(struct ast_sip_outbound_publish_client *client, const char *user)
static struct ao2_container * new_states
Used on [re]loads to hold new state data.
static struct sip_outbound_publisher * sip_outbound_publish_client_add_publisher(struct ast_sip_outbound_publish_client *client, const char *user)
static int load_module(void)
static int datastore_cmp(void *obj, void *arg, int flags)
const char * ast_sip_publish_client_get_to_uri(struct ast_sip_outbound_publish_client *client)
Get the To URI the client will use.
static void sip_outbound_publish_timer_cb(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry)
Publish client timer callback function.
struct ast_datastore * ast_sip_publish_client_get_datastore(struct ast_sip_outbound_publish_client *client, const char *name)
Retrieve an event publisher datastore.
static int sip_outbound_publisher_set_uri(pj_pool_t *pool, const char *uri, const char *user, pj_str_t *res_uri)
#define DESTROY_CLIENT()
static int unload_module(void)
#define ADD_TO_NEW_STATES(__obj)
static int datastore_hash(const void *obj, int flags)
static int send_unpublish_task(void *data)
Task for sending an unpublish.
static void set_transport(struct sip_outbound_publisher *publisher, pjsip_tx_data *tdata)
static int publisher_client_send(void *obj, void *arg, void *data, int flags)
static struct ao2_container * get_publishes_and_update_state(void)
static int sip_outbound_publisher_init(void *data)
Helper function that allocates a pjsip publish client and configures it.
static int cancel_and_unpublish(void *obj, void *arg, int flags)
Helper function which cancels and un-publishes a no longer used client.
int ast_sip_validate_uri_length(const char *uri)
Definition location.c:529
#define NULL
Definition resample.c:96
int ast_serializer_shutdown_group_join(struct ast_serializer_shutdown_group *shutdown_group, int timeout)
Wait for the serializers in the group to shutdown with timeout.
struct ast_serializer_shutdown_group * ast_serializer_shutdown_group_alloc(void)
Create a serializer group shutdown control object.
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition sorcery.c:2381
@ AST_RETRIEVE_FLAG_MULTIPLE
Return all matching objects.
Definition sorcery.h:120
@ AST_RETRIEVE_FLAG_ALL
Perform no matching, return all objects.
Definition sorcery.h:123
#define ast_sorcery_object_register(sorcery, type, alloc, transform, apply)
Register an object type.
Definition sorcery.h:837
#define ast_sorcery_object_field_register_custom(sorcery, type, name, default_val, config_handler, sorcery_handler, multiple_handler, flags,...)
Register a field within an object with custom handlers.
Definition sorcery.h:1005
void * ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor)
Allocate a generic sorcery capable object.
Definition sorcery.c:1792
int ast_sorcery_object_unregister(struct ast_sorcery *sorcery, const char *type)
Unregister an object type.
Definition sorcery.c:1125
#define ast_sorcery_apply_config(sorcery, name)
Definition sorcery.h:455
#define ast_sorcery_object_field_register(sorcery, type, name, default_val, opt_type, flags,...)
Register a field within an object.
Definition sorcery.h:955
#define ast_sorcery_apply_default(sorcery, type, name, data)
Definition sorcery.h:476
void ast_sorcery_reload_object(const struct ast_sorcery *sorcery, const char *type)
Inform any wizards of a specific object type to reload persistent objects.
Definition sorcery.c:1506
void * ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const char *type, unsigned int flags, struct ast_variable *fields)
Retrieve an object or multiple objects using specific fields.
Definition sorcery.c:1961
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
#define AST_STRING_FIELD(name)
Declare a string field.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one.
Definition strings.h:80
static force_inline int attribute_pure ast_str_hash(const char *str)
Compute a hash value on a string.
Definition strings.h:1259
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition strings.h:65
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition strings.h:425
Generic container type.
When we need to walk through a container, we use an ao2_iterator to keep track of the current positio...
Definition astobj2.h:1821
Structure for a data store type.
Definition datastore.h:31
void(* destroy)(void *data)
Definition datastore.h:34
Structure for a data store object.
Definition datastore.h:64
const struct ast_datastore_info * info
Definition datastore.h:67
const char * uid
Definition datastore.h:65
void * data
Definition datastore.h:66
SIP body description.
Definition res_pjsip.h:2469
const char * type
Definition res_pjsip.h:2471
const char * body_text
Definition res_pjsip.h:2475
const char * subtype
Definition res_pjsip.h:2473
Callbacks that event publisher handlers will define.
struct ast_sip_event_publisher_handler * next
const char * event_name
The name of the event this handler deals with.
Outbound publish client state information (persists for lifetime of a publish)
struct ao2_container * datastores
Publisher datastores set up by handlers.
unsigned int started
Publishing has been fully started and event type informed.
struct ast_sip_outbound_publish * publish
Outbound publish information.
struct ao2_container * publishers
Container of all the client publishing objects.
Outbound publish state information (persists for lifetime of a publish)
struct ast_sip_outbound_publish_client * client
Outbound publish client.
Outbound publish information.
struct ast_sip_auth_vector outbound_auths
Configured authentication credentials.
SORCERY_OBJECT(details)
Sorcery object details.
unsigned int expiration
Requested expiration time.
const ast_string_field outbound_proxy
unsigned int multi_user
The publishing client is used for multiple users when true.
unsigned int max_auth_attempts
Maximum number of auth attempts before stopping the publish client.
Full structure for sorcery.
Definition sorcery.c:231
A ast_taskprocessor structure is a singleton by name.
Structure for variables, used for configurations and for channel variables.
char * event
Definition astman.c:223
Queued outbound publish message.
struct ast_sip_body body
Optional body.
char body_contents[0]
Extra space for body contents.
struct sip_outbound_publish_message::@501 entry
Linked list information.
char * from_uri
The From URI for this specific publisher.
struct sip_outbound_publisher::@502 queue
Queue of outgoing publish messages to send.
char user[0]
User, if any, associated with the publisher.
pjsip_publishc * client
Underlying publish client.
struct ast_sip_outbound_publish_client * owner
The client object that 'owns' this client.
unsigned int auth_attempts
The number of auth attempts done.
struct sip_outbound_publish_message * sending
The message currently being sent.
unsigned int destroy
Publish client should be destroyed.
struct ast_taskprocessor * serializer
Serializer for stuff and things.
char * to_uri
The To URI for this specific publisher.
pj_timer_entry timer
Timer entry for refreshing publish.
structure to hold users read from phoneprov_users.conf
An API for managing task processing threads that can be shared across modules.
void * ast_taskprocessor_unreference(struct ast_taskprocessor *tps)
Unreference the specified taskprocessor and its reference count will decrement.
void ast_taskprocessor_build_name(char *buf, unsigned int size, const char *format,...)
Build a taskprocessor name with a sequence number on the end.
#define AST_TASKPROCESSOR_MAX_NAME
Suggested maximum taskprocessor name length (less null terminator).
static void handler(const char *name, int response_code, struct ast_variable *get_params, struct ast_variable *path_vars, struct ast_variable *headers, struct ast_json *body, struct ast_ari_response *response)
Definition test_ari.c:59
static char server_uri[512]
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition utils.h:981
#define ast_assert(a)
Definition utils.h:779
#define AST_UUID_STR_LEN
Definition uuid.h:27
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition uuid.c:141
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition vector.h:620
#define AST_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition vector.h:691