Asterisk - The Open Source Telephony Project  21.4.1
res_pjsip_notify.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Kevin Harwell <kharwell@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  <support_level>core</support_level>
23  ***/
24 
25 #include "asterisk.h"
26 
27 #include <pjsip.h>
28 #include <pjsip_ua.h>
29 
30 #include "asterisk/cli.h"
31 #include "asterisk/config.h"
32 #include "asterisk/manager.h"
33 #include "asterisk/module.h"
34 #include "asterisk/pbx.h"
35 #include "asterisk/res_pjsip.h"
36 #include "asterisk/res_pjsip_session.h"
37 #include "asterisk/sorcery.h"
38 
39 /*** DOCUMENTATION
40  <manager name="PJSIPNotify" language="en_US">
41  <synopsis>
42  Send a NOTIFY to either an endpoint, an arbitrary URI, or inside a SIP dialog.
43  </synopsis>
44  <syntax>
45  <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
46  <parameter name="Endpoint" required="false">
47  <para>The endpoint to which to send the NOTIFY.</para>
48  </parameter>
49  <parameter name="URI" required="false">
50  <para>Abritrary URI to which to send the NOTIFY.</para>
51  </parameter>
52  <parameter name="channel" required="false">
53  <para>Channel name to send the NOTIFY. Must be a PJSIP channel.</para>
54  </parameter>
55  <parameter name="Option" required="false">
56  <para>The config section name from <literal>pjsip_notify.conf</literal> to use.</para>
57  <para>One of Option or Variable must be specified.</para>
58  </parameter>
59  <parameter name="Variable" required="false">
60  <para>Appends variables as headers/content to the NOTIFY. If the variable is
61  named <literal>Content</literal>, then the value will compose the body
62  of the message if another variable sets <literal>Content-Type</literal>.
63  <replaceable>name</replaceable>=<replaceable>value</replaceable></para>
64  <para>One of Option or Variable must be specified.</para>
65  </parameter>
66  </syntax>
67  <description>
68  <para>Sends a NOTIFY to an endpoint, an arbitrary URI, or inside a SIP dialog.</para>
69  <para>All parameters for this event must be specified in the body of this
70  request via multiple <literal>Variable: name=value</literal> sequences.</para>
71  <note><para>One (and only one) of <literal>Endpoint</literal>,
72  <literal>URI</literal>, or <literal>Channel</literal> must be specified.
73  If <literal>URI</literal> is used, the default outbound endpoint will be used
74  to send the message. If the default outbound endpoint isn't configured, this command
75  can not send to an arbitrary URI.</para></note>
76  </description>
77  </manager>
78  <configInfo name="res_pjsip_notify" language="en_US">
79  <synopsis>Module that supports sending NOTIFY requests to endpoints from external sources</synopsis>
80  <configFile name="pjsip_notify.conf">
81  <configObject name="general">
82  <synopsis>Unused, but reserved.</synopsis>
83  </configObject>
84  <configObject name="notify">
85  <synopsis>Configuration of a NOTIFY request.</synopsis>
86  <description>
87  <para>Each key-value pair in a <literal>notify</literal>
88  configuration section defines either a SIP header to send
89  in the request or a line of content in the request message
90  body. A key of <literal>Content</literal> is treated
91  as part of the message body and is appended in sequential
92  order; any other header is treated as part of the SIP
93  request.</para>
94  </description>
95  <configOption name="">
96  <synopsis>A key/value pair to add to a NOTIFY request.</synopsis>
97  <description>
98  <para>If the key is <literal>Content</literal>,
99  it will be treated as part of the message body. Otherwise,
100  it will be added as a header in the NOTIFY request.</para>
101  <para>The following headers are reserved and cannot be
102  specified:</para>
103  <enumlist>
104  <enum name="Call-ID" />
105  <enum name="Contact" />
106  <enum name="CSeq" />
107  <enum name="To" />
108  <enum name="From" />
109  <enum name="Record-Route" />
110  <enum name="Route" />
111  <enum name="Via" />
112  </enumlist>
113  </description>
114  </configOption>
115  </configObject>
116  </configFile>
117  </configInfo>
118  ***/
119 
120 #define CONTENT_TYPE_SIZE 64
121 #define CONTENT_SIZE 512
122 
123 /*!
124  * \internal
125  * \brief The configuration file containing NOTIFY payload types to send.
126  */
127 static const char notify_config[] = "pjsip_notify.conf";
128 
130  const char *name;
131  const char *value;
132  char buf[0];
133 };
134 
136  /*! Contains header and/or content information */
138  /*! The name of the notify option */
139  char name[0];
140 };
141 
142 static int notify_option_hash(const void *obj, int flags)
143 {
144  const struct notify_option *option = obj;
145  return ast_str_case_hash(flags & OBJ_KEY ? obj : option->name);
146 }
147 
148 static int notify_option_cmp(void *obj, void *arg, int flags)
149 {
150  struct notify_option *option1 = obj;
151  struct notify_option *option2 = arg;
152  const char *key = flags & OBJ_KEY ? arg : option2->name;
153 
154  return strcasecmp(option1->name, key) ? 0 : CMP_MATCH;
155 }
156 
157 static void notify_option_destroy(void *obj)
158 {
159  struct notify_option *option = obj;
160  ao2_cleanup(option->items);
161 }
162 
163 static void *notify_option_alloc(const char *category)
164 {
165  int category_size = strlen(category) + 1;
166 
167  struct notify_option *option = ao2_alloc(
168  sizeof(*option) + category_size, notify_option_destroy);
169 
170  if (!option) {
171  return NULL;
172  }
173 
174  ast_copy_string(option->name, category, category_size);
175 
176  if (!(option->items = ao2_container_alloc_list(
178  AO2_CONTAINER_ALLOC_OPT_DUPS_ALLOW, NULL, NULL))) {
179  ao2_cleanup(option);
180  return NULL;
181  }
182 
183  return option;
184 }
185 
186 static void *notify_option_find(struct ao2_container *container, const char *category)
187 {
188  return ao2_find(container, category, OBJ_KEY);
189 }
190 
191 static int notify_option_handler(const struct aco_option *opt,
192  struct ast_variable *var, void *obj)
193 {
194  struct notify_option *option = obj;
195 
196  int name_size = strlen(var->name) + 1;
197  int value_size = strlen(var->value) + 1;
198 
199  RAII_VAR(struct notify_option_item *, item,
200  ao2_alloc(sizeof(*item) + name_size + value_size,
201  NULL), ao2_cleanup);
202 
203  item->name = item->buf;
204  item->value = item->buf + name_size;
205 
206  ast_copy_string(item->buf, var->name, name_size);
207  ast_copy_string(item->buf + name_size, var->value, value_size);
208 
209  if (!ao2_link(option->items, item)) {
210  return -1;
211  }
212 
213  return 0;
214 }
215 
216 struct notify_cfg {
217  struct ao2_container *notify_options;
218 };
219 
220 static void notify_cfg_destroy(void *obj)
221 {
222  struct notify_cfg *cfg = obj;
223  ao2_cleanup(cfg->notify_options);
224 }
225 
226 static void *notify_cfg_alloc(void)
227 {
228  struct notify_cfg *cfg;
229 
230  if (!(cfg = ao2_alloc(sizeof(*cfg), notify_cfg_destroy))) {
231  return NULL;
232  }
233 
234  cfg->notify_options = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
235  20, notify_option_hash, NULL, notify_option_cmp);
236  if (!cfg->notify_options) {
237  ao2_cleanup(cfg);
238  return NULL;
239  }
240 
241  return cfg;
242 }
243 
244 static struct aco_type notify_option = {
245  .type = ACO_ITEM,
246  .name = "notify",
247  .category_match = ACO_BLACKLIST_EXACT,
248  .category = "general",
249  .item_offset = offsetof(struct notify_cfg, notify_options),
250  .item_alloc = notify_option_alloc,
251  .item_find = notify_option_find
252 };
253 
254 static struct aco_type *notify_options[] = ACO_TYPES(&notify_option);
255 
256 static struct aco_file module_conf = {
257  .filename = notify_config,
258  .types = ACO_TYPES(&notify_option),
259 };
260 
261 AO2_GLOBAL_OBJ_STATIC(globals);
262 
263 CONFIG_INFO_STANDARD(notify_cfg, globals, notify_cfg_alloc,
264  .files = ACO_FILES(&module_conf)
265 );
266 
267 /*!
268  * \internal
269  * \brief Structure to hold task data for notifications.
270  */
271 struct notify_data {
272  /*! The endpoint being notified */
274  /*! The info of headers, types and content */
275  void *info;
276  /*! Function to help build notify request */
277  void (*build_notify)(pjsip_tx_data *, void *);
278 };
279 
280 /*!
281  * \internal
282  * \brief Destroy the notify CLI data releasing any resources.
283  */
284 static void notify_cli_data_destroy(void *obj)
285 {
286  struct notify_data *data = obj;
287 
288  ao2_cleanup(data->endpoint);
289  ao2_cleanup(data->info);
290 }
291 
292 /*!
293  * \internal
294  * \brief Structure to hold task data for notifications (URI variant)
295  */
297  char *uri;
298  void *info;
299  void (*build_notify)(pjsip_tx_data *, void *);
300 };
301 
302 /*!
303  * \internal
304  * \brief Structure to hold task data for notifications (channel variant)
305  */
307  struct ast_sip_session *session;
308  void *info;
309  void (*build_notify)(pjsip_tx_data *, void *);
310 };
311 
312 static void notify_cli_uri_data_destroy(void *obj)
313 {
314  struct notify_uri_data *data = obj;
315 
316  ast_free(data->uri);
317  ao2_cleanup(data->info);
318 }
319 
320 /*!
321  * \internal
322  * \brief Destroy the notify CLI data releasing any resources (URI variant)
323  */
324 static void build_cli_notify(pjsip_tx_data *tdata, void *info);
325 
326 /*!
327  * \internal
328  * \brief Construct a notify data object for CLI.
329  */
330 static struct notify_data* notify_cli_data_create(
331  struct ast_sip_endpoint *endpoint, void *info)
332 {
333  struct notify_data *data = ao2_alloc(sizeof(*data),
334  notify_cli_data_destroy);
335  if (!data) {
336  return NULL;
337  }
338 
339  data->endpoint = endpoint;
340  ao2_ref(data->endpoint, +1);
341 
342  data->info = info;
343  ao2_ref(data->info, +1);
344 
345  data->build_notify = build_cli_notify;
346 
347  return data;
348 }
349 
350 /*!
351  * \internal
352  * \brief Construct a notify URI data object for CLI.
353  */
354 static struct notify_uri_data* notify_cli_uri_data_create(
355  const char *uri, void *info)
356 {
357  struct notify_uri_data *data = ao2_alloc(sizeof(*data),
358  notify_cli_uri_data_destroy);
359 
360  if (!data) {
361  return NULL;
362  }
363 
364  data->uri = ast_strdup(uri);
365  if (!data->uri) {
366  ao2_ref(data, -1);
367  return NULL;
368  }
369 
370  data->info = info;
371  ao2_ref(data->info, +1);
372 
373  data->build_notify = build_cli_notify;
374 
375  return data;
376 }
377 
378 /*!
379  * \internal
380  * \brief Destroy the notify AMI data releasing any resources.
381  */
382 static void notify_ami_data_destroy(void *obj)
383 {
384  struct notify_data *data = obj;
385  struct ast_variable *info = data->info;
386 
387  ao2_cleanup(data->endpoint);
388  ast_variables_destroy(info);
389 }
390 
391 /*!
392  * \internal
393  * \brief Destroy the notify AMI URI data releasing any resources.
394  */
395 static void notify_ami_uri_data_destroy(void *obj)
396 {
397  struct notify_uri_data *data = obj;
398  struct ast_variable *info = data->info;
399 
400  ast_free(data->uri);
401  ast_variables_destroy(info);
402 }
403 
404 /*!
405  * \internal
406  * \brief Destroy the notify AMI channel data releasing any resources.
407  */
408 static void notify_ami_channel_data_destroy(void *obj)
409 {
410  struct notify_channel_data *data = obj;
411  struct ast_variable *info = data->info;
412 
413  ao2_cleanup(data->session);
414  ast_variables_destroy(info);
415 }
416 
417 static void build_ami_notify(pjsip_tx_data *tdata, void *info);
418 
419 /*!
420  * \internal
421  * \brief Construct a notify data object for AMI.
422  */
423 static struct notify_data* notify_ami_data_create(
424  struct ast_sip_endpoint *endpoint, void *info)
425 {
426  struct notify_data *data = ao2_alloc(sizeof(*data),
427  notify_ami_data_destroy);
428  if (!data) {
429  return NULL;
430  }
431 
432  data->endpoint = endpoint;
433  ao2_ref(data->endpoint, +1);
434 
435  data->info = info;
436  data->build_notify = build_ami_notify;
437 
438  return data;
439 }
440 
441 /*!
442  * \internal
443  * \brief Construct a notify URI data object for AMI.
444  */
445 static struct notify_uri_data* notify_ami_uri_data_create(
446  const char *uri, void *info)
447 {
448  struct notify_uri_data *data = ao2_alloc(sizeof(*data),
449  notify_ami_uri_data_destroy);
450  if (!data) {
451  return NULL;
452  }
453 
454  data->uri = ast_strdup(uri);
455  if (!data->uri) {
456  ao2_ref(data, -1);
457  return NULL;
458  }
459 
460  data->info = info;
461  data->build_notify = build_ami_notify;
462 
463  return data;
464 }
465 
466 /*!
467  * \internal
468  * \brief Construct a notify channel data object for AMI.
469  */
470 static struct notify_channel_data *notify_ami_channel_data_create(
471  struct ast_sip_session *session, void *info)
472 {
473  struct notify_channel_data *data;
474 
475  data = ao2_alloc_options(sizeof(*data), notify_ami_channel_data_destroy,
477  if (!data) {
478  return NULL;
479  }
480 
481  data->session = session;
482  data->info = info;
483  data->build_notify = build_ami_notify;
484 
485  return data;
486 }
487 
488 /*!
489  * \internal
490  * \brief Checks if the given header name is not allowed.
491  *
492  * \details Some headers are not allowed to be set by the user within the
493  * scope of a NOTIFY request. If the given var header name is
494  * found in the "not allowed" list then return true.
495  */
496 static int not_allowed(const char *name)
497 {
498  int i;
499  static const char *names[] = {
500  "Call-ID",
501  "Contact",
502  "CSeq",
503  "To",
504  "From",
505  "Record-Route",
506  "Route",
507  "Request-URI",
508  "Via",
509  };
510 
511  for (i = 0; i < ARRAY_LEN(names); ++i) {
512  if (!strcasecmp(name, names[i])) {
513  return 1;
514  }
515  }
516  return 0;
517 }
518 
519 /*!
520  * \internal
521  * \brief Check if the given header can be added to a message more than once.
522  */
523 static int multiple_headers_allowed(const char *name)
524 {
525  /* This can be extended to include additional headers */
526  return strcasecmp("Event", name);
527 }
528 
529 /*!
530  * \internal
531  * \brief If a content type was specified add it and the content body to the
532  * NOTIFY request.
533  */
534 static void build_notify_body(pjsip_tx_data *tdata, struct ast_str *content_type,
535  struct ast_str *content)
536 {
537  if (content_type) {
538  char *p;
539  struct ast_sip_body body;
540 
541  if (content) {
542  body.body_text = ast_str_buffer(content);
543  }
544 
545  body.type = ast_str_buffer(content_type);
546  if ((p = strchr(body.type, '/'))) {
547  *p++ = '\0';
548  body.subtype = p;
549  }
550  ast_sip_add_body(tdata, &body);
551  }
552 }
553 
554 /*!
555  * \internal
556  * \brief Build the NOTIFY request adding content or header info.
557  */
558 static void build_notify(pjsip_tx_data *tdata, const char *name, const char *value,
559  struct ast_str **content_type, struct ast_str **content)
560 {
561  if (not_allowed(name)) {
562  ast_log(LOG_WARNING, "Cannot specify %s header, "
563  "ignoring\n", name);
564  return;
565  }
566 
567  if (!strcasecmp(name, "Content-type")) {
568  if (!(*content_type)) {
569  *content_type = ast_str_create(CONTENT_TYPE_SIZE);
570  }
571  ast_str_set(content_type, 0,"%s", value);
572  } else if (!strcasecmp(name, "Content")) {
573  if (!(*content)) {
574  *content = ast_str_create(CONTENT_SIZE);
575  }
576 
577  if (ast_str_strlen(*content)) {
578  ast_str_append(content, 0, "\r\n");
579  }
580  ast_str_append(content, 0, "%s", value);
581  } else {
582  /* See if there is an existing one */
583  if (!multiple_headers_allowed(name)) {
584  pj_str_t hdr_name;
585  pj_cstr(&hdr_name, name);
586 
587  if (pjsip_msg_find_hdr_by_name(tdata->msg, &hdr_name, NULL)) {
588  ast_log(LOG_ERROR, "Only one '%s' header can be added to a NOTIFY, "
589  "ignoring \"%s: %s\"\n", name, name, value);
590  return;
591  }
592  }
593 
594  ast_sip_add_header(tdata, name, value);
595  }
596 }
597 
598 /*!
599  * \internal
600  * \brief Build the NOTIFY request from CLI info adding header and content
601  * when specified.
602  */
603 static void build_cli_notify(pjsip_tx_data *tdata, void *info)
604 {
605  struct notify_option *option = info;
606  RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
607  RAII_VAR(struct ast_str *, content, NULL, ast_free);
608 
609  struct notify_option_item *item;
610  struct ao2_iterator i = ao2_iterator_init(option->items, 0);
611 
612  while ((item = ao2_iterator_next(&i))) {
613  build_notify(tdata, item->name, item->value,
614  &content_type, &content);
615  ao2_cleanup(item);
616  }
618 
619  build_notify_body(tdata, content_type, content);
620 }
621 
622 /*!
623  * \internal
624  * \brief Build the NOTIFY request from AMI info adding header and content
625  * when specified.
626  */
627 static void build_ami_notify(pjsip_tx_data *tdata, void *info)
628 {
629  struct ast_variable *vars = info;
630  RAII_VAR(struct ast_str *, content_type, NULL, ast_free);
631  RAII_VAR(struct ast_str *, content, NULL, ast_free);
632  struct ast_variable *i;
633 
634  for (i = vars; i; i = i->next) {
635  if (!strcasecmp(i->name, "Content-Length")) {
636  ast_log(LOG_NOTICE, "It is not necessary to specify Content-Length, ignoring.\n");
637  continue;
638  }
639  build_notify(tdata, i->name, i->value,
640  &content_type, &content);
641  }
642 
643  build_notify_body(tdata, content_type, content);
644 }
645 
646 /*!
647  * \internal
648  * \brief Build and send a NOTIFY request to a contact.
649  */
650 static int notify_contact(void *obj, void *arg, int flags)
651 {
652  struct ast_sip_contact *contact = obj;
653  struct notify_data *data = arg;
654  pjsip_tx_data *tdata;
655 
656  if (ast_sip_create_request("NOTIFY", NULL, data->endpoint,
657  NULL, contact, &tdata)) {
658  ast_log(LOG_WARNING, "SIP NOTIFY - Unable to create request for "
659  "contact %s\n", contact->uri);
660  return -1;
661  }
662 
663  ast_sip_add_header(tdata, "Subscription-State", "terminated");
664  data->build_notify(tdata, data->info);
665 
666  if (ast_sip_send_request(tdata, NULL, data->endpoint, NULL, NULL)) {
667  ast_log(LOG_ERROR, "SIP NOTIFY - Unable to send request for "
668  "contact %s\n", contact->uri);
669  return -1;
670  }
671 
672  return 0;
673 }
674 
675 /*!
676  * \internal
677  * \brief Send a NOTIFY request to the endpoint.
678  *
679  * \details Iterates over an endpoint's AORs sending a NOTIFY request
680  * with the appropriate payload information to each contact.
681  */
682 static int notify_endpoint(void *obj)
683 {
684  RAII_VAR(struct notify_data *, data, obj, ao2_cleanup);
685  char *aor_name, *aors;
686 
687  if (ast_strlen_zero(data->endpoint->aors)) {
688  ast_log(LOG_WARNING, "Unable to NOTIFY - "
689  "endpoint has no configured AORs\n");
690  return -1;
691  }
692 
693  aors = ast_strdupa(data->endpoint->aors);
694 
695  while ((aor_name = ast_strip(strsep(&aors, ",")))) {
696  RAII_VAR(struct ast_sip_aor *, aor,
697  ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
698  RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
699 
700  if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
701  continue;
702  }
703 
704  ao2_callback(contacts, OBJ_NODATA, notify_contact, data);
705  }
706 
707  return 0;
708 }
709 
710 /*!
711  * \internal
712  * \brief Send a notify request to the URI.
713  */
714 static int notify_uri(void *obj)
715 {
716  RAII_VAR(struct notify_uri_data *, data, obj, ao2_cleanup);
717  RAII_VAR(struct ast_sip_endpoint *, endpoint,
718  ast_sip_default_outbound_endpoint(), ao2_cleanup);
719  pjsip_tx_data *tdata;
720 
721  if (!endpoint) {
722  ast_log(LOG_WARNING, "No default outbound endpoint set, can not send "
723  "NOTIFY requests to arbitrary URIs.\n");
724  return -1;
725  }
726 
727  if (ast_strlen_zero(data->uri)) {
728  ast_log(LOG_WARNING, "Unable to NOTIFY - URI is blank.\n");
729  return -1;
730  }
731 
732  if (ast_sip_create_request("NOTIFY", NULL, endpoint,
733  data->uri, NULL, &tdata)) {
734  ast_log(LOG_WARNING, "SIP NOTIFY - Unable to create request for "
735  "uri %s\n", data->uri);
736  return -1;
737  }
738 
739  ast_sip_add_header(tdata, "Subscription-State", "terminated");
740 
741  data->build_notify(tdata, data->info);
742 
743  if (ast_sip_send_request(tdata, NULL, endpoint, NULL, NULL)) {
744  ast_log(LOG_ERROR, "SIP NOTIFY - Unable to send request for "
745  "uri %s\n", data->uri);
746  return -1;
747  }
748 
749  return 0;
750 }
751 
752 /*!
753  * \internal
754  * \brief Send a notify request to a channel.
755  */
756 static int notify_channel(void *obj)
757 {
758  RAII_VAR(struct notify_channel_data *, data, obj, ao2_cleanup);
759  pjsip_tx_data *tdata;
760  struct pjsip_dialog *dlg;
761 
762  if (!data->session->channel
763  || !data->session->inv_session
764  || data->session->inv_session->state < PJSIP_INV_STATE_EARLY
765  || data->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
766  return -1;
767  }
768 
769  ast_debug(1, "Sending notify on channel %s\n", ast_channel_name(data->session->channel));
770 
771  dlg = data->session->inv_session->dlg;
772 
773  if (ast_sip_create_request("NOTIFY", dlg, NULL, NULL, NULL, &tdata)) {
774  return -1;
775  }
776 
777  ast_sip_add_header(tdata, "Subscription-State", "terminated");
778  data->build_notify(tdata, data->info);
779 
780  if (ast_sip_send_request(tdata, dlg, NULL, NULL, NULL)) {
781  return -1;
782  }
783 
784  return 0;
785 }
786 
787 enum notify_result {
788  SUCCESS,
789  INVALID_ENDPOINT,
790  INVALID_CHANNEL,
791  ALLOC_ERROR,
792  TASK_PUSH_ERROR
793 };
794 
795 typedef struct notify_data *(*task_data_create)(
796  struct ast_sip_endpoint *, void *info);
797 
798 typedef struct notify_uri_data *(*task_uri_data_create)(
799  const char *uri, void *info);
800 
801 typedef struct notify_channel_data *(*task_channel_data_create)(
802  struct ast_sip_session *session, void *info);
803 
804 /*!
805  * \internal
806  * \brief Send a NOTIFY request to the endpoint within a threaded task.
807  */
808 static enum notify_result push_notify(const char *endpoint_name, void *info,
809  task_data_create data_create)
810 {
811  RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
812  struct notify_data *data;
813 
814  if (!(endpoint = ast_sorcery_retrieve_by_id(
815  ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
816  return INVALID_ENDPOINT;
817  }
818 
819  if (!(data = data_create(endpoint, info))) {
820  return ALLOC_ERROR;
821  }
822 
823  if (ast_sip_push_task(NULL, notify_endpoint, data)) {
824  ao2_cleanup(data);
825  return TASK_PUSH_ERROR;
826  }
827 
828  return SUCCESS;
829 }
830 
831 /*!
832  * \internal
833  * \brief Send a NOTIFY request to the URI within an threaded task.
834  */
835 static enum notify_result push_notify_uri(const char *uri, void *info,
836  task_uri_data_create data_create)
837 {
838  struct notify_uri_data *data;
839 
840  if (!(data = data_create(uri, info))) {
841  return ALLOC_ERROR;
842  }
843 
844  if (ast_sip_push_task(NULL, notify_uri, data)) {
845  ao2_cleanup(data);
846  return TASK_PUSH_ERROR;
847  }
848 
849  return SUCCESS;
850 }
851 
852 /*!
853  * \internal
854  * \brief Send a NOTIFY request in a channel within an threaded task.
855  */
856 static enum notify_result push_notify_channel(const char *channel_name, void *info,
857  task_channel_data_create data_create)
858 {
859  struct notify_channel_data *data;
860  struct ast_channel *ch;
861  struct ast_sip_session *session;
862  struct ast_sip_channel_pvt *ch_pvt;
863 
864  /* note: this increases the refcount of the channel */
865  ch = ast_channel_get_by_name(channel_name);
866  if (!ch) {
867  ast_debug(1, "No channel found with name %s", channel_name);
868  return INVALID_CHANNEL;
869  }
870 
871  if (strcmp(ast_channel_tech(ch)->type, "PJSIP")) {
872  ast_log(LOG_WARNING, "Channel was a non-PJSIP channel: %s\n", channel_name);
873  ast_channel_unref(ch);
874  return INVALID_CHANNEL;
875  }
876 
877  ast_channel_lock(ch);
878  ch_pvt = ast_channel_tech_pvt(ch);
879  session = ch_pvt->session;
880 
881  if (!session || !session->inv_session
882  || session->inv_session->state < PJSIP_INV_STATE_EARLY
883  || session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
884  ast_debug(1, "No active session for channel %s\n", channel_name);
885  ast_channel_unlock(ch);
886  ast_channel_unref(ch);
887  return INVALID_CHANNEL;
888  }
889 
890  ao2_ref(session, +1);
891  ast_channel_unlock(ch);
892 
893  /* don't keep a reference to the channel, we've got a reference to the session */
894  ast_channel_unref(ch);
895 
896  /*
897  * data_create will take ownership of the session,
898  * and take care of releasing the ref.
899  */
900  data = data_create(session, info);
901  if (!data) {
902  ao2_ref(session, -1);
903  return ALLOC_ERROR;
904  }
905 
906  if (ast_sip_push_task(session->serializer, notify_channel, data)) {
907  ao2_ref(data, -1);
908  return TASK_PUSH_ERROR;
909  }
910 
911  return SUCCESS;
912 }
913 
914 /*!
915  * \internal
916  * \brief Do completion on the endpoint.
917  */
918 static char *cli_complete_endpoint(const char *word)
919 {
920  int wordlen = strlen(word);
921  struct ao2_container * endpoints;
922  struct ast_sip_endpoint *endpoint;
923  struct ao2_iterator i;
924 
925  endpoints = ast_sorcery_retrieve_by_prefix(ast_sip_get_sorcery(),
926  "endpoint", word, wordlen);
927  if (endpoints == NULL) {
928  return NULL;
929  }
930 
931  i = ao2_iterator_init(endpoints, 0);
932  while ((endpoint = ao2_iterator_next(&i))) {
935  ao2_cleanup(endpoint);
936  }
938 
939  ao2_ref(endpoints, -1);
940 
941  return NULL;
942 }
943 
944 /*!
945  * \internal
946  * \brief Do completion on the notify CLI command.
947  */
948 static char *cli_complete_notify(const char *line, const char *word,
949  int pos, int state, int using_uri)
950 {
951  char *c = NULL;
952 
953  if (pos == 3) {
954  int which = 0;
955  int wordlen = strlen(word);
956 
957  RAII_VAR(struct notify_cfg *, cfg,
958  ao2_global_obj_ref(globals), ao2_cleanup);
959  struct notify_option *option;
960 
961  /* do completion for notify type */
962  struct ao2_iterator i = ao2_iterator_init(cfg->notify_options, 0);
963  while ((option = ao2_iterator_next(&i))) {
964  if (!strncasecmp(word, option->name, wordlen) && ++which > state) {
965  c = ast_strdup(option->name);
966  }
967 
968  ao2_cleanup(option);
969  if (c) {
970  break;
971  }
972  }
974  return c;
975  }
976 
977  if (pos == 4) {
978  int wordlen = strlen(word);
979 
980  if (ast_strlen_zero(word)) {
981  if (state == 0) {
982  c = ast_strdup("endpoint");
983  } else if (state == 1) {
984  c = ast_strdup("uri");
985  }
986  } else if (state == 0) {
987  if (!strncasecmp(word, "endpoint", wordlen)) {
988  c = ast_strdup("endpoint");
989  } else if (!strncasecmp(word, "uri", wordlen)) {
990  c = ast_strdup("uri");
991  }
992  }
993 
994  return c;
995  }
996 
997  return pos > 4 && !using_uri ? cli_complete_endpoint(word) : NULL;
998 }
999 
1000 /*!
1001  * \internal
1002  * \brief CLI command to send a SIP notify to an endpoint.
1003  *
1004  * \details Attempts to match the "type" given in the CLI command to a
1005  * configured one. If found, sends a NOTIFY to the endpoint
1006  * with the associated payload.
1007  */
1008 static char *cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1009 {
1010  RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
1011  RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
1012 
1013  int i;
1014  int using_uri = 0;
1015 
1016  switch (cmd) {
1017  case CLI_INIT:
1018  e->command = "pjsip send notify";
1019  e->usage =
1020  "Usage: pjsip send notify <type> {endpoint|uri} <peer> [<peer>...]\n"
1021  " Send a NOTIFY request to an endpoint\n"
1022  " Message types are defined in pjsip_notify.conf\n";
1023  return NULL;
1024  case CLI_GENERATE:
1025  if (a->argc > 4 && (!strcasecmp(a->argv[4], "uri"))) {
1026  using_uri = 1;
1027  }
1028 
1029  return cli_complete_notify(a->line, a->word, a->pos, a->n, using_uri);
1030  }
1031 
1032  if (a->argc < 6) {
1033  return CLI_SHOWUSAGE;
1034  }
1035 
1036  if (!strcasecmp(a->argv[4], "uri")) {
1037  using_uri = 1;
1038  } else if (strcasecmp(a->argv[4], "endpoint")) {
1039  return CLI_SHOWUSAGE;
1040  }
1041 
1042  cfg = ao2_global_obj_ref(globals);
1043 
1044  if (!(option = notify_option_find(cfg->notify_options, a->argv[3])))
1045  {
1046  ast_cli(a->fd, "Unable to find notify type '%s'\n",
1047  a->argv[3]);
1048  return CLI_FAILURE;
1049  }
1050 
1051  for (i = 5; i < a->argc; ++i) {
1052  ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n",
1053  a->argv[3], a->argv[i]);
1054 
1055  switch (using_uri ? push_notify_uri(a->argv[i], option, notify_cli_uri_data_create) :
1056  push_notify(a->argv[i], option, notify_cli_data_create)) {
1057  case INVALID_ENDPOINT:
1058  ast_cli(a->fd, "Unable to retrieve endpoint %s\n",
1059  a->argv[i]);
1060  break;
1061  case ALLOC_ERROR:
1062  ast_cli(a->fd, "Unable to allocate NOTIFY task data\n");
1063  return CLI_FAILURE;
1064  case TASK_PUSH_ERROR:
1065  ast_cli(a->fd, "Unable to push NOTIFY task\n");
1066  return CLI_FAILURE;
1067  default:
1068  break;
1069  }
1070  }
1071 
1072  return CLI_SUCCESS;
1073 }
1074 
1075 static struct ast_cli_entry cli_options[] = {
1076  AST_CLI_DEFINE(cli_notify, "Send a NOTIFY request to a SIP endpoint")
1077 };
1078 
1079 enum notify_type {
1080  NOTIFY_ENDPOINT,
1081  NOTIFY_URI,
1082  NOTIFY_CHANNEL,
1083 };
1084 
1085 static void manager_send_response(struct mansession *s, const struct message *m, enum notify_type type, enum notify_result res, struct ast_variable *vars, const char *endpoint_name)
1086 {
1087  switch (res) {
1088  case INVALID_CHANNEL:
1089  if (type == NOTIFY_CHANNEL) {
1090  ast_variables_destroy(vars);
1091  astman_send_error(s, m, "Channel not found");
1092  } else {
1093  /* Shouldn't be possible. */
1094  ast_assert(0);
1095  }
1096  break;
1097  case INVALID_ENDPOINT:
1098  if (type == NOTIFY_ENDPOINT) {
1099  ast_variables_destroy(vars);
1100  astman_send_error_va(s, m, "Unable to retrieve endpoint %s", endpoint_name);
1101  } else {
1102  /* Shouldn't be possible. */
1103  ast_assert(0);
1104  }
1105  break;
1106  case ALLOC_ERROR:
1107  ast_variables_destroy(vars);
1108  astman_send_error(s, m, "Unable to allocate NOTIFY task data");
1109  break;
1110  case TASK_PUSH_ERROR:
1111  /* Don't need to destroy vars since it is handled by cleanup in push_notify, push_notify_uri, etc. */
1112  astman_send_error(s, m, "Unable to push Notify task");
1113  break;
1114  case SUCCESS:
1115  astman_send_ack(s, m, "NOTIFY sent");
1116  break;
1117  }
1118 }
1119 
1120 /*!
1121  * \internal
1122  * \brief Completes SIPNotify AMI command in Endpoint mode.
1123  */
1124 static void manager_notify_endpoint(struct mansession *s,
1125  const struct message *m, const char *endpoint_name)
1126 {
1127  RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
1128  RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
1129  struct ast_variable *vars = NULL;
1130  enum notify_result res;
1131  const char *option_name = astman_get_header(m, "Option");
1132 
1133  if (!ast_strlen_zero(option_name) && (cfg = ao2_global_obj_ref(globals)) && !(option = notify_option_find(cfg->notify_options, option_name))) {
1134  astman_send_error_va(s, m, "Unable to find notify type '%s'\n", option_name);
1135  return;
1136  }
1137  if (!option) {
1138  vars = astman_get_variables_order(m, ORDER_NATURAL);
1139  }
1140 
1141  if (!strncasecmp(endpoint_name, "sip/", 4)) {
1142  endpoint_name += 4;
1143  }
1144 
1145  if (!strncasecmp(endpoint_name, "pjsip/", 6)) {
1146  endpoint_name += 6;
1147  }
1148 
1149  if (option) {
1150  res = push_notify(endpoint_name, option, notify_cli_data_create); /* The CLI version happens to be suitable for options. */
1151  } else {
1152  res = push_notify(endpoint_name, vars, notify_ami_data_create);
1153  }
1154 
1155  manager_send_response(s, m, NOTIFY_ENDPOINT, res, vars, endpoint_name);
1156 }
1157 
1158 /*!
1159  * \internal
1160  * \brief Completes SIPNotify AMI command in URI mode.
1161  */
1162 static void manager_notify_uri(struct mansession *s,
1163  const struct message *m, const char *uri)
1164 {
1165  RAII_VAR(struct notify_cfg *, cfg, NULL, ao2_cleanup);
1166  RAII_VAR(struct notify_option *, option, NULL, ao2_cleanup);
1167  enum notify_result res;
1168  const char *option_name = astman_get_header(m, "Option");
1169  struct ast_variable *vars = NULL;
1170 
1171  if (!ast_strlen_zero(option_name) && (cfg = ao2_global_obj_ref(globals)) && !(option = notify_option_find(cfg->notify_options, option_name))) {
1172  astman_send_error_va(s, m, "Unable to find notify type '%s'\n", option_name);
1173  return;
1174  }
1175  if (!option) {
1176  vars = astman_get_variables_order(m, ORDER_NATURAL);
1177  }
1178 
1179  if (option) {
1180  res = push_notify_uri(uri, option, notify_cli_uri_data_create);
1181  } else {
1182  res = push_notify_uri(uri, vars, notify_ami_uri_data_create);
1183  }
1184 
1185  manager_send_response(s, m, NOTIFY_URI, res, vars, NULL);
1186 }
1187 
1188 /*!
1189  * \internal
1190  * \brief Completes SIPNotify AMI command in channel mode.
1191  */
1192 static void manager_notify_channel(struct mansession *s,
1193  const struct message *m, const char *channel)
1194 {
1195  enum notify_result res;
1196  struct ast_variable *vars = NULL;
1197 
1198  vars = astman_get_variables_order(m, ORDER_NATURAL);
1199  res = push_notify_channel(channel, vars, notify_ami_channel_data_create);
1200 
1201  manager_send_response(s, m, NOTIFY_CHANNEL, res, vars, NULL);
1202 }
1203 
1204 /*!
1205  * \internal
1206  * \brief AMI entry point to send a SIP notify to an endpoint.
1207  */
1208 static int manager_notify(struct mansession *s, const struct message *m)
1209 {
1210  const char *endpoint_name = astman_get_header(m, "Endpoint");
1211  const char *uri = astman_get_header(m, "URI");
1212  const char *channel = astman_get_header(m, "Channel");
1213  const char *variables = astman_get_header(m, "Variable");
1214  const char *option = astman_get_header(m, "Option");
1215  int count = 0;
1216 
1217  if (!ast_strlen_zero(endpoint_name)) {
1218  ++count;
1219  }
1220  if (!ast_strlen_zero(uri)) {
1221  ++count;
1222  }
1223  if (!ast_strlen_zero(channel)) {
1224  ++count;
1225  }
1226 
1227  if ((!ast_strlen_zero(option) && !ast_strlen_zero(variables)) || (ast_strlen_zero(option) && ast_strlen_zero(variables))) {
1228  astman_send_error(s, m,
1229  "PJSIPNotify requires either an Option or Variable(s)."
1230  "You must use only one of them.");
1231  } else if (1 < count) {
1232  astman_send_error(s, m,
1233  "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel. "
1234  "You must use only one of them.");
1235  } else if (!ast_strlen_zero(endpoint_name)) {
1236  manager_notify_endpoint(s, m, endpoint_name);
1237  } else if (!ast_strlen_zero(uri)) {
1238  manager_notify_uri(s, m, uri);
1239  } else if (!ast_strlen_zero(channel)) {
1240  manager_notify_channel(s, m, channel);
1241  } else {
1242  astman_send_error(s, m,
1243  "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel.");
1244  }
1245 
1246  return 0;
1247 }
1248 
1249 static int load_module(void)
1250 {
1251  if (aco_info_init(&notify_cfg)) {
1252  return AST_MODULE_LOAD_DECLINE;
1253  }
1254 
1255  aco_option_register_custom(&notify_cfg, "", ACO_PREFIX, notify_options,
1256  "", notify_option_handler, 0);
1257 
1258  if (aco_process_config(&notify_cfg, 0)) {
1260  return AST_MODULE_LOAD_DECLINE;
1261  }
1262 
1263  ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
1264  ast_manager_register_xml("PJSIPNotify", EVENT_FLAG_SYSTEM, manager_notify);
1265 
1266  return AST_MODULE_LOAD_SUCCESS;
1267 }
1268 
1269 static int reload_module(void)
1270 {
1272  return AST_MODULE_LOAD_DECLINE;
1273  }
1274 
1275  return 0;
1276 }
1277 
1278 static int unload_module(void)
1279 {
1280  ast_manager_unregister("PJSIPNotify");
1281  ast_cli_unregister_multiple(cli_options, ARRAY_LEN(cli_options));
1283  ao2_global_obj_release(globals);
1284 
1285  return 0;
1286 }
1287 
1288 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "CLI/AMI PJSIP NOTIFY Support",
1289  .support_level = AST_MODULE_SUPPORT_CORE,
1290  .load = load_module,
1291  .reload = reload_module,
1292  .unload = unload_module,
1293  .load_pri = AST_MODPRI_APP_DEPEND,
1294  .requires = "res_pjsip",
1295 );
struct ast_variable * next
const char * body_text
Definition: res_pjsip.h:2315
Main Channel structure associated with a channel.
Asterisk main include file. File version handling, generic pbx functions.
A SIP address of record.
Definition: res_pjsip.h:478
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
#define aco_option_register_custom(info, name, matchtype, types, default_val, handler, flags)
Register a config option.
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define ast_channel_unref(c)
Decrease channel reference count.
Definition: channel.h:2958
#define OBJ_KEY
Definition: astobj2.h:1151
descriptor for a cli entry.
Definition: cli.h:171
struct ao2_container * items
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container, as described below.
Definition: astobj2.h:1693
Allow objects with duplicate keys in container.
Definition: astobj2.h:1181
#define ao2_container_alloc_list(ao2_options, container_options, sort_fn, cmp_fn)
Allocate and initialize a list container.
Definition: astobj2.h:1327
Structure for variables, used for configurations and for channel variables.
A structure which contains a channel implementation and session.
struct ast_sip_session * session
Pointer to session.
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
enum aco_process_status aco_process_config(struct aco_info *info, int reload)
Process a config info via the options registered with an aco_info.
#define ao2_global_obj_ref(holder)
Get a reference to the object stored in the global holder.
Definition: astobj2.h:918
void ao2_iterator_destroy(struct ao2_iterator *iter)
Destroy a container iterator.
void astman_send_ack(struct mansession *s, const struct message *m, char *msg)
Send ack in manager transaction.
Definition: manager.c:3421
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
struct ao2_container * ast_sorcery_retrieve_by_prefix(const struct ast_sorcery *sorcery, const char *type, const char *prefix, const size_t prefix_len)
Retrieve multiple objects whose id begins with the specified prefix.
Definition: sorcery.c:1989
void(* build_notify)(pjsip_tx_data *, void *)
The representation of a single configuration file to be processed.
struct pjsip_inv_session * inv_session
#define ACO_TYPES(...)
A helper macro to ensure that aco_info types always have a sentinel.
struct ao2_container * c
Definition: astobj2.h:1823
void astman_send_error_va(struct mansession *s, const struct message *m, const char *fmt,...)
Send error in manager transaction (with va_args support)
Definition: manager.c:3394
A structure describing a SIP session.
const char * astman_get_header(const struct message *m, char *var)
Get header from manager transaction.
Definition: manager.c:3050
void * ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *type, const char *id)
Retrieve an object using its unique identifier.
Definition: sorcery.c:1853
struct ast_sip_endpoint * endpoint
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
Configuration File Parser.
int aco_info_init(struct aco_info *info)
Initialize an aco_info structure.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
In case you didn't read that giant block of text above the mansession_session struct, the mansession is named this solely to keep the API the same in Asterisk. This structure really represents data that is different from Manager action to Manager action. The mansession_session pointer contained within points to session-specific data.
Definition: manager.c:1785
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
struct ao2_container * container
Definition: res_fax.c:501
#define ast_debug(level,...)
Log a DEBUG message.
An entity with which Asterisk communicates.
Definition: res_pjsip.h:949
Structure to describe a channel "technology", ie a channel driver See for examples: ...
Definition: channel.h:628
Core PBX routines and definitions.
struct ast_taskprocessor * serializer
static struct task_data * task_data_create(void)
Create a task_data object.
Their was an error and no changes were applied.
int ast_manager_unregister(const char *action)
Unregister a registered manager command.
Definition: manager.c:8057
The AMI - Asterisk Manager Interface - is a TCP protocol created to manage Asterisk with third-party ...
struct ast_sip_endpoint_info_configuration info
Definition: res_pjsip.h:988
#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
Support for dynamic strings.
Definition: strings.h:623
int ast_sip_push_task(struct ast_taskprocessor *serializer, int(*sip_task)(void *), void *task_data)
Pushes a task to SIP servants.
Definition: res_pjsip.c:2099
void aco_info_destroy(struct aco_info *info)
Destroy an initialized aco_info struct.
#define ao2_global_obj_release(holder)
Release the ao2 object held in the global holder.
Definition: astobj2.h:859
Contact associated with an address of record.
Definition: res_pjsip.h:392
static struct stasis_rest_handlers endpoints
REST handler for /api-docs/endpoints.json.
char * command
Definition: cli.h:186
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
const char * usage
Definition: cli.h:177
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
struct ast_variable * astman_get_variables_order(const struct message *m, enum variable_orders order)
Get a linked list of the Variable: headers with order specified.
Definition: manager.c:3141
When we need to walk through a container, we use an ao2_iterator to keep track of the current positio...
Definition: astobj2.h:1821
Standard Command Line Interface.
Type information about a category-level configurable object.
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
const char * filename
Generic container type.
int ast_cli_completion_add(char *value)
Add a result to a request for completion options.
Definition: main/cli.c:2761
const ast_string_field aors
Definition: res_pjsip.h:958
#define ast_manager_register_xml(action, authority, func)
Register a manager callback using XML documentation to describe the manager.
Definition: manager.h:191
#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 ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
const ast_string_field uri
Definition: res_pjsip.h:414
struct ast_channel * ast_channel_get_by_name(const char *name)
Find a channel by name.
Definition: channel.c:1454
void astman_send_error(struct mansession *s, const struct message *m, char *error)
Send error in manager transaction.
Definition: manager.c:3389
Asterisk module definitions.
#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:941
#define CONFIG_INFO_STANDARD(name, arr, alloc,...)
Declare an aco_info struct with default module and preload values.
struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags) attribute_warn_unused_result
Create an iterator for a container.
SIP body description.
Definition: res_pjsip.h:2309
static force_inline int attribute_pure ast_str_case_hash(const char *str)
Compute a hash value on a case-insensitive string.
Definition: strings.h:1303
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
Sorcery Data Access Layer API.
#define ao2_link(container, obj)
Add an object to a container.
Definition: astobj2.h:1532