Asterisk - The Open Source Telephony Project  21.4.1
config_auth.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Mark Michelson <mmichelson@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 #include "asterisk.h"
20 
21 #include <pjsip.h>
22 #include <pjlib.h>
23 #include "asterisk/res_pjsip.h"
24 #include "asterisk/logger.h"
25 #include "asterisk/sorcery.h"
26 #include "asterisk/cli.h"
27 #include "include/res_pjsip_private.h"
28 #include "asterisk/res_pjsip_cli.h"
29 
30 static void auth_destroy(void *obj)
31 {
32  struct ast_sip_auth *auth = obj;
34 }
35 
36 static void *auth_alloc(const char *name)
37 {
38  struct ast_sip_auth *auth = ast_sorcery_generic_alloc(sizeof(*auth), auth_destroy);
39 
40  if (!auth) {
41  return NULL;
42  }
43 
44  if (ast_string_field_init(auth, 64)) {
45  ao2_cleanup(auth);
46  return NULL;
47  }
48 
49  return auth;
50 }
51 
52 static int auth_type_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
53 {
54  struct ast_sip_auth *auth = obj;
55  if (!strcasecmp(var->value, "userpass")) {
56  auth->type = AST_SIP_AUTH_TYPE_USER_PASS;
57  } else if (!strcasecmp(var->value, "md5")) {
58  auth->type = AST_SIP_AUTH_TYPE_MD5;
59  } else if (!strcasecmp(var->value, "google_oauth")) {
60 #ifdef HAVE_PJSIP_OAUTH_AUTHENTICATION
61  auth->type = AST_SIP_AUTH_TYPE_GOOGLE_OAUTH;
62 #else
63  ast_log(LOG_WARNING, "OAuth support is not available in the version of PJSIP in use\n");
64  return -1;
65 #endif
66  } else {
67  ast_log(LOG_WARNING, "Unknown authentication storage type '%s' specified for %s\n",
68  var->value, var->name);
69  return -1;
70  }
71  return 0;
72 }
73 
74 static const char *auth_types_map[] = {
75  [AST_SIP_AUTH_TYPE_USER_PASS] = "userpass",
76  [AST_SIP_AUTH_TYPE_MD5] = "md5",
77  [AST_SIP_AUTH_TYPE_GOOGLE_OAUTH] = "google_oauth"
78 };
79 
80 const char *ast_sip_auth_type_to_str(enum ast_sip_auth_type type)
81 {
82  return ARRAY_IN_BOUNDS(type, auth_types_map) ?
83  auth_types_map[type] : "";
84 }
85 
86 static int auth_type_to_str(const void *obj, const intptr_t *args, char **buf)
87 {
88  const struct ast_sip_auth *auth = obj;
89  *buf = ast_strdup(ast_sip_auth_type_to_str(auth->type));
90  return 0;
91 }
92 
93 static int auth_apply(const struct ast_sorcery *sorcery, void *obj)
94 {
95  struct ast_sip_auth *auth = obj;
96  int res = 0;
97 
98  if (ast_strlen_zero(auth->auth_user)) {
99  ast_log(LOG_ERROR, "No authentication username for auth '%s'\n",
101  return -1;
102  }
103 
104  switch (auth->type) {
105  case AST_SIP_AUTH_TYPE_MD5:
106  if (ast_strlen_zero(auth->md5_creds)) {
107  ast_log(LOG_ERROR, "'md5' authentication specified but no md5_cred "
108  "specified for auth '%s'\n", ast_sorcery_object_get_id(auth));
109  res = -1;
110  } else if (strlen(auth->md5_creds) != PJSIP_MD5STRLEN) {
111  ast_log(LOG_ERROR, "'md5' authentication requires digest of size '%d', but "
112  "digest is '%d' in size for auth '%s'\n", PJSIP_MD5STRLEN, (int)strlen(auth->md5_creds),
114  res = -1;
115  }
116  break;
117  case AST_SIP_AUTH_TYPE_GOOGLE_OAUTH:
118  if (ast_strlen_zero(auth->refresh_token)
119  || ast_strlen_zero(auth->oauth_clientid)
120  || ast_strlen_zero(auth->oauth_secret)) {
121  ast_log(LOG_ERROR, "'google_oauth' authentication specified but refresh_token,"
122  " oauth_clientid, or oauth_secret not specified for auth '%s'\n",
124  res = -1;
125  }
126  break;
127  case AST_SIP_AUTH_TYPE_USER_PASS:
128  case AST_SIP_AUTH_TYPE_ARTIFICIAL:
129  break;
130  }
131 
132  return res;
133 }
134 
135 int ast_sip_for_each_auth(const struct ast_sip_auth_vector *vector,
136  ao2_callback_fn on_auth, void *arg)
137 {
138  int i;
139 
140  if (!vector || !AST_VECTOR_SIZE(vector)) {
141  return 0;
142  }
143 
144  for (i = 0; i < AST_VECTOR_SIZE(vector); ++i) {
145  /* AST_VECTOR_GET is safe to use since the vector is immutable */
147  ast_sip_get_sorcery(), SIP_SORCERY_AUTH_TYPE,
148  AST_VECTOR_GET(vector,i)), ao2_cleanup);
149 
150  if (!auth) {
151  continue;
152  }
153 
154  if (on_auth(auth, arg, 0)) {
155  return -1;
156  }
157  }
158 
159  return 0;
160 }
161 
162 static int sip_auth_to_ami(const struct ast_sip_auth *auth,
163  struct ast_str **buf)
164 {
165  return ast_sip_sorcery_object_to_ami(auth, buf);
166 }
167 
168 static int format_ami_auth_handler(void *obj, void *arg, int flags)
169 {
170  const struct ast_sip_auth *auth = obj;
171  struct ast_sip_ami *ami = arg;
172  const struct ast_sip_endpoint *endpoint = ami->arg;
173  RAII_VAR(struct ast_str *, buf,
174  ast_sip_create_ami_event("AuthDetail", ami), ast_free);
175 
176  if (!buf) {
177  return -1;
178  }
179 
180  if (sip_auth_to_ami(auth, &buf)) {
181  return -1;
182  }
183 
184  if (endpoint) {
185  ast_str_append(&buf, 0, "EndpointName: %s\r\n",
186  ast_sorcery_object_get_id(endpoint));
187  }
188 
189  astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
190  ami->count++;
191 
192  return 0;
193 }
194 
195 int ast_sip_format_auths_ami(const struct ast_sip_auth_vector *auths,
196  struct ast_sip_ami *ami)
197 {
198  return ast_sip_for_each_auth(auths, format_ami_auth_handler, ami);
199 }
200 
201 static int format_ami_endpoint_auth(const struct ast_sip_endpoint *endpoint,
202  struct ast_sip_ami *ami)
203 {
204  ami->arg = (void *)endpoint;
205  if (ast_sip_format_auths_ami(&endpoint->inbound_auths, ami)) {
206  return -1;
207  }
208 
209  return ast_sip_format_auths_ami(&endpoint->outbound_auths, ami);
210 }
211 
212 static struct ast_sip_endpoint_formatter endpoint_auth_formatter = {
213  .format_ami = format_ami_endpoint_auth
214 };
215 
216 static struct ao2_container *cli_get_auths(void)
217 {
218  struct ao2_container *auths;
219 
220  auths = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "auth",
222 
223  return auths;
224 }
225 
226 static int format_ami_authlist_handler(void *obj, void *arg, int flags)
227 {
228  struct ast_sip_auth *auth = obj;
229  struct ast_sip_ami *ami = arg;
230  struct ast_str *buf;
231 
232  buf = ast_sip_create_ami_event("AuthList", ami);
233  if (!buf) {
234  return CMP_STOP;
235  }
236 
237  sip_auth_to_ami(auth, &buf);
238 
239  astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));
240  ami->count++;
241 
242  ast_free(buf);
243 
244  return 0;
245 }
246 
247 static int ami_show_auths(struct mansession *s, const struct message *m)
248 {
249  struct ast_sip_ami ami = { .s = s, .m = m, .action_id = astman_get_header(m, "ActionID"), };
250  struct ao2_container *auths;
251 
252  auths = cli_get_auths();
253  if (!auths) {
254  astman_send_error(s, m, "Could not get Auths\n");
255  return 0;
256  }
257 
258  if (!ao2_container_count(auths)) {
259  astman_send_error(s, m, "No Auths found\n");
260  ao2_ref(auths, -1);
261  return 0;
262  }
263 
264  astman_send_listack(s, m, "A listing of Auths follows, presented as AuthList events",
265  "start");
266 
267  ao2_callback(auths, OBJ_NODATA, format_ami_authlist_handler, &ami);
268 
269  astman_send_list_complete_start(s, m, "AuthListComplete", ami.count);
271 
272  ao2_ref(auths, -1);
273 
274  return 0;
275 }
276 
277 static struct ao2_container *cli_get_container(const char *regex)
278 {
279  RAII_VAR(struct ao2_container *, container, NULL, ao2_cleanup);
280  struct ao2_container *s_container;
281 
282  container = ast_sorcery_retrieve_by_regex(ast_sip_get_sorcery(), "auth", regex);
283  if (!container) {
284  return NULL;
285  }
286 
289  if (!s_container) {
290  return NULL;
291  }
292 
293  if (ao2_container_dup(s_container, container, 0)) {
294  ao2_ref(s_container, -1);
295  return NULL;
296  }
297 
298  return s_container;
299 }
300 
301 static int cli_iterator(void *container, ao2_callback_fn callback, void *args)
302 {
303  return ast_sip_for_each_auth(container, callback, args);
304 }
305 
306 static void *cli_retrieve_by_id(const char *id)
307 {
308  return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), SIP_SORCERY_AUTH_TYPE, id);
309 }
310 
311 static int cli_print_header(void *obj, void *arg, int flags)
312 {
313  struct ast_sip_cli_context *context = arg;
314  int indent = CLI_INDENT_TO_SPACES(context->indent_level);
315  int filler = CLI_MAX_WIDTH - indent - 20;
316 
317  ast_assert(context->output_buffer != NULL);
318 
319  ast_str_append(&context->output_buffer, 0,
320  "%*s: <AuthId/UserName%*.*s>\n", indent, "I/OAuth", filler, filler,
321  CLI_HEADER_FILLER);
322 
323  return 0;
324 }
325 
326 static int cli_print_body(void *obj, void *arg, int flags)
327 {
328  struct ast_sip_auth *auth = obj;
329  struct ast_sip_cli_context *context = arg;
330  char title[32];
331 
332  ast_assert(context->output_buffer != NULL);
333 
334  snprintf(title, sizeof(title), "%sAuth",
335  context->auth_direction ? context->auth_direction : "");
336 
337  ast_str_append(&context->output_buffer, 0, "%*s: %s/%s\n",
338  CLI_INDENT_TO_SPACES(context->indent_level), title,
339  ast_sorcery_object_get_id(auth), auth->auth_user);
340 
341  if (context->show_details
342  || (context->show_details_only_level_0 && context->indent_level == 0)) {
343  ast_str_append(&context->output_buffer, 0, "\n");
344  ast_sip_cli_print_sorcery_objectset(auth, context, 0);
345  }
346 
347  return 0;
348 }
349 
350 static struct ast_cli_entry cli_commands[] = {
351  AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "List PJSIP Auths",
352  .command = "pjsip list auths",
353  .usage = "Usage: pjsip list auths [ like <pattern> ]\n"
354  " List the configured PJSIP Auths\n"
355  " Optional regular expression pattern is used to filter the list.\n"),
356  AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Auths",
357  .command = "pjsip show auths",
358  .usage = "Usage: pjsip show auths [ like <pattern> ]\n"
359  " Show the configured PJSIP Auths\n"
360  " Optional regular expression pattern is used to filter the list.\n"),
361  AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Auth",
362  .command = "pjsip show auth",
363  .usage = "Usage: pjsip show auth <id>\n"
364  " Show the configured PJSIP Auth\n"),
365 };
366 
367 static struct ast_sip_cli_formatter_entry *cli_formatter;
368 
369 /*! \brief Initialize sorcery with auth support */
370 int ast_sip_initialize_sorcery_auth(void)
371 {
372  struct ast_sorcery *sorcery = ast_sip_get_sorcery();
373 
374  ast_sorcery_apply_default(sorcery, SIP_SORCERY_AUTH_TYPE, "config", "pjsip.conf,criteria=type=auth");
375 
376  if (ast_sorcery_object_register(sorcery, SIP_SORCERY_AUTH_TYPE, auth_alloc, NULL, auth_apply)) {
377  return -1;
378  }
379 
380  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "type", "",
381  OPT_NOOP_T, 0, 0);
382  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "username",
383  "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_user));
384  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "password",
385  "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, auth_pass));
386  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "refresh_token",
387  "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, refresh_token));
388  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_clientid",
389  "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_clientid));
390  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "oauth_secret",
391  "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, oauth_secret));
392  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "md5_cred",
393  "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, md5_creds));
394  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "realm",
395  "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_auth, realm));
396  ast_sorcery_object_field_register(sorcery, SIP_SORCERY_AUTH_TYPE, "nonce_lifetime",
397  "32", OPT_UINT_T, 0, FLDSET(struct ast_sip_auth, nonce_lifetime));
398  ast_sorcery_object_field_register_custom(sorcery, SIP_SORCERY_AUTH_TYPE, "auth_type",
399  "userpass", auth_type_handler, auth_type_to_str, NULL, 0, 0);
400 
401  ast_sip_register_endpoint_formatter(&endpoint_auth_formatter);
402 
403  cli_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
404  if (!cli_formatter) {
405  ast_log(LOG_ERROR, "Unable to allocate memory for cli formatter\n");
406  return -1;
407  }
408  cli_formatter->name = SIP_SORCERY_AUTH_TYPE;
409  cli_formatter->print_header = cli_print_header;
410  cli_formatter->print_body = cli_print_body;
411  cli_formatter->get_container = cli_get_container;
412  cli_formatter->iterate = cli_iterator;
413  cli_formatter->get_id = ast_sorcery_object_get_id;
414  cli_formatter->retrieve_by_id = cli_retrieve_by_id;
415 
416  ast_sip_register_cli_formatter(cli_formatter);
417  ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
418 
419  if (ast_manager_register_xml("PJSIPShowAuths", EVENT_FLAG_SYSTEM, ami_show_auths)) {
420  return -1;
421  }
422 
423  return 0;
424 }
425 
426 int ast_sip_destroy_sorcery_auth(void)
427 {
428  ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
429  ast_sip_unregister_cli_formatter(cli_formatter);
430  ast_sip_unregister_endpoint_formatter(&endpoint_auth_formatter);
431 
432  ast_manager_unregister("PJSIPShowAuths");
433 
434  return 0;
435 }
struct ast_str * output_buffer
Definition: res_pjsip_cli.h:36
struct ao2_container *(* get_container)(const char *regex)
Definition: res_pjsip_cli.h:64
#define ARRAY_IN_BOUNDS(v, a)
Checks to see if value is within the bounds of the given array.
Definition: utils.h:687
void astman_append(struct mansession *s, const char *fmt,...)
Definition: manager.c:3310
Asterisk main include file. File version handling, generic pbx functions.
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
An entity responsible formatting endpoint information.
Definition: res_pjsip.h:3057
CLI Formatter Registry Entry.
Definition: res_pjsip_cli.h:52
int( ao2_callback_fn)(void *obj, void *arg, int flags)
Type of a generic callback function.
Definition: astobj2.h:1226
const ast_string_field oauth_secret
Definition: res_pjsip.h:593
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
const struct message * m
Definition: res_pjsip.h:3035
const ast_string_field md5_creds
Definition: res_pjsip.h:587
void astman_send_list_complete_start(struct mansession *s, const struct message *m, const char *event_name, int count)
Start the list complete event.
Definition: manager.c:3467
descriptor for a cli entry.
Definition: cli.h:171
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
void * arg
Definition: res_pjsip.h:3039
AMI variable container.
Definition: res_pjsip.h:3031
#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.
struct ast_sip_auth_vector outbound_auths
Definition: res_pjsip.h:994
Perform no matching, return all objects.
Definition: sorcery.h:123
int ast_sorcery_object_id_compare(void *obj, void *arg, int flags)
ao2 object comparator based on sorcery id.
Definition: sorcery.c:2464
Full structure for sorcery.
Definition: sorcery.c:230
int(* iterate)(void *container, ao2_callback_fn callback, void *args)
Definition: res_pjsip_cli.h:66
Type for a default handler that should do nothing.
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
Return all matching objects.
Definition: sorcery.h:120
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
CLI Formatter Context passed to all formatters.
Definition: res_pjsip_cli.h:34
struct ao2_container * ast_sorcery_retrieve_by_regex(const struct ast_sorcery *sorcery, const char *type, const char *regex)
Retrieve multiple objects using a regular expression on their id.
Definition: sorcery.c:1954
const ast_string_field oauth_clientid
Definition: res_pjsip.h:591
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
#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
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
void astman_send_list_complete_end(struct mansession *s)
End the list complete event.
Definition: manager.c:3475
Type for default option handler for unsigned integers.
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
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
int ast_sorcery_object_id_sort(const void *obj, const void *arg, int flags)
ao2 object sorter based on sorcery id.
Definition: sorcery.c:2440
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
void *(* retrieve_by_id)(const char *id)
Definition: res_pjsip_cli.h:68
struct mansession * s
Definition: res_pjsip.h:3033
struct ao2_container * container
Definition: res_fax.c:501
An entity with which Asterisk communicates.
Definition: res_pjsip.h:949
#define ast_sorcery_object_register(sorcery, type, alloc, transform, apply)
Register an object type.
Definition: sorcery.h:837
int ast_manager_unregister(const char *action)
Unregister a registered manager command.
Definition: manager.c:8057
struct ast_sip_auth_vector inbound_auths
Definition: res_pjsip.h:992
Support for dynamic strings.
Definition: strings.h:623
int ao2_container_dup(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags)
Copy all object references in the src container into the dest container.
char * command
Definition: cli.h:186
int(* format_ami)(const struct ast_sip_endpoint *endpoint, struct ast_sip_ami *ami)
Callback used to format endpoint information over AMI.
Definition: res_pjsip.h:3061
#define STRFLDSET(type,...)
Convert a struct and a list of stringfield fields to an argument list of field offsets.
enum ast_sip_auth_type type
Definition: res_pjsip.h:597
Support for logging to various files, console and syslog Configuration in file logger.conf.
const char *(* get_id)(const void *obj)
Definition: res_pjsip_cli.h:70
const ast_string_field refresh_token
Definition: res_pjsip.h:589
const char * usage
Definition: cli.h:177
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:1897
#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_VECTOR_GET(vec, idx)
Get an element from a vector.
Definition: vector.h:680
Standard Command Line Interface.
Type for default option handler for stringfields.
ao2_callback_fn * print_header
Definition: res_pjsip_cli.h:60
Generic container type.
void * ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor)
Allocate a generic sorcery capable object.
Definition: sorcery.c:1728
#define ast_manager_register_xml(action, authority, func)
Register a manager callback using XML documentation to describe the manager.
Definition: manager.h:191
ao2_callback_fn * print_body
Definition: res_pjsip_cli.h:62
void astman_send_error(struct mansession *s, const struct message *m, char *error)
Send error in manager transaction.
Definition: manager.c:3389
const char * name
Definition: res_pjsip_cli.h:58
#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 ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
const ast_string_field auth_user
Definition: res_pjsip.h:583
#define AST_VECTOR_SIZE(vec)
Get the number of elements in a vector.
Definition: vector.h:609
Sorcery Data Access Layer API.
void astman_send_listack(struct mansession *s, const struct message *m, char *msg, char *listflag)
Send ack in manager transaction to begin a list.
Definition: manager.c:3431
unsigned show_details_only_level_0
Definition: res_pjsip_cli.h:46