Asterisk - The Open Source Telephony Project  21.4.1
res_pjsip_authenticator_digest.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 
23 #include "asterisk/res_pjsip.h"
24 #include "asterisk/logger.h"
25 #include "asterisk/module.h"
26 #include "asterisk/strings.h"
27 #include "asterisk/test.h"
28 
29 /*** MODULEINFO
30  <depend>pjproject</depend>
31  <depend>res_pjsip</depend>
32  <support_level>core</support_level>
33  ***/
34 
35 static char default_realm[AST_SIP_AUTH_MAX_REALM_LENGTH + 1];
36 
37 AO2_GLOBAL_OBJ_STATIC(entity_id);
38 
39 /*!
40  * \brief Determine if authentication is required
41  *
42  * Authentication is required if the endpoint has at least one auth
43  * section specified
44  */
45 static int digest_requires_authentication(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
46 {
47  RAII_VAR(struct ast_sip_endpoint *, artificial, ast_sip_get_artificial_endpoint(), ao2_cleanup);
48 
49  return endpoint == artificial || AST_VECTOR_SIZE(&endpoint->inbound_auths) > 0;
50 }
51 
52 static void auth_store_cleanup(void *data)
53 {
54  struct ast_sip_auth **auth = data;
55 
56  ao2_cleanup(*auth);
57  ast_free(data);
58 }
59 
60 /*!
61  * \brief Thread-local storage for \ref ast_sip_auth
62  *
63  * The PJSIP authentication API is a bit annoying. When you set
64  * up an authentication server, you specify a lookup callback to
65  * call into when verifying incoming credentials. The problem
66  * with this callback is that it only gives you the realm and
67  * authentication username. In 2.0.5, there is a new version of
68  * the callback you can use that gives the pjsip_rx_data in
69  * addition.
70  *
71  * Unfortunately, the data we actually \b need is the
72  * \ref ast_sip_auth we are currently observing. So we have two
73  * choices:
74  * 1) Use the current PJSIP API and use thread-local storage
75  * to temporarily store our SIP authentication information. Then
76  * in the callback, we can retrieve the authentication info and
77  * use as needed. Given our threading model, this is safe.
78  * 2) Use the 2.0.5 API and temporarily store the authentication
79  * information in the rdata's endpoint_info. Then in the callback,
80  * we can retrieve the authentication info from the rdata.
81  *
82  * I've chosen option 1 since it does not require backporting
83  * any APIs from future versions of PJSIP, plus I feel the
84  * thread-local option is a bit cleaner.
85  */
86 AST_THREADSTORAGE_CUSTOM(auth_store, NULL, auth_store_cleanup);
87 
88 /*!
89  * \brief Store shallow copy authentication information in thread-local storage
90  */
91 static int store_auth(const struct ast_sip_auth *auth)
92 {
93  const struct ast_sip_auth **pointing;
94 
95  pointing = ast_threadstorage_get(&auth_store, sizeof(pointing));
96  if (!pointing) {
97  return -1;
98  }
99 
100  *pointing = auth;
101  return 0;
102 }
103 
104 /*!
105  * \brief Remove shallow copy authentication information from thread-local storage
106  */
107 static int remove_auth(void)
108 {
109  struct ast_sip_auth **pointing;
110 
111  pointing = ast_threadstorage_get(&auth_store, sizeof(pointing));
112  if (!pointing) {
113  return -1;
114  }
115 
116  *pointing = NULL;
117  return 0;
118 }
119 
120 /*!
121  * \brief Retrieve shallow copy authentication information from thread-local storage
122  */
123 static const struct ast_sip_auth *get_auth(void)
124 {
125  struct ast_sip_auth **auth;
126 
127  auth = ast_threadstorage_get(&auth_store, sizeof(auth));
128  if (auth) {
129  return *auth;
130  }
131  return NULL;
132 }
133 
134 /*!
135  * \brief Lookup callback for authentication verification
136  *
137  * This function is called when we call pjsip_auth_srv_verify(). It
138  * expects us to verify that the realm and account name from the
139  * Authorization header is correct. We are then supposed to supply
140  * a password or MD5 sum of credentials.
141  *
142  * \param pool A memory pool we can use for allocations
143  * \param realm The realm from the Authorization header
144  * \param acc_name the user from the Authorization header
145  * \param[out] info The credentials we need to fill in
146  * \retval PJ_SUCCESS Successful authentication
147  * \retval other Unsuccessful
148  */
149 static pj_status_t digest_lookup(pj_pool_t *pool, const pj_str_t *realm,
150  const pj_str_t *acc_name, pjsip_cred_info *info)
151 {
152  const struct ast_sip_auth *auth;
153 
154  auth = get_auth();
155  if (!auth) {
156  return PJSIP_SC_FORBIDDEN;
157  }
158 
159  if (auth->type == AST_SIP_AUTH_TYPE_ARTIFICIAL) {
160  return PJSIP_SC_FORBIDDEN;
161  }
162 
163  if (pj_strcmp2(realm, auth->realm)) {
164  return PJSIP_SC_FORBIDDEN;
165  }
166  if (pj_strcmp2(acc_name, auth->auth_user)) {
167  return PJSIP_SC_FORBIDDEN;
168  }
169 
170  pj_strdup2(pool, &info->realm, auth->realm);
171  pj_strdup2(pool, &info->username, auth->auth_user);
172 
173  switch (auth->type) {
174  case AST_SIP_AUTH_TYPE_USER_PASS:
175  pj_strdup2(pool, &info->data, auth->auth_pass);
176  info->data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
177  break;
178  case AST_SIP_AUTH_TYPE_MD5:
179  pj_strdup2(pool, &info->data, auth->md5_creds);
180  info->data_type = PJSIP_CRED_DATA_DIGEST;
181  break;
182  default:
183  return PJSIP_SC_FORBIDDEN;
184  }
185  return PJ_SUCCESS;
186 }
187 
188 /*!
189  * \brief Calculate a nonce
190  *
191  * We use this in order to create authentication challenges. We also use this in order
192  * to verify that an incoming request with credentials could be in response to one
193  * of our challenges.
194  *
195  * The nonce is calculated from a timestamp, the source IP address, the source port, a
196  * unique ID for us, and the realm. This helps to ensure that the incoming request
197  * is from the same source that the nonce was calculated for. Including the realm
198  * ensures that multiple challenges to the same request have different nonces.
199  *
200  * \param nonce
201  * \param timestamp A UNIX timestamp expressed as a string
202  * \param rdata The incoming request
203  * \param realm The realm for which authentication should occur
204  */
205 static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsip_rx_data *rdata, const char *realm)
206 {
207  struct ast_str *str = ast_str_alloca(256);
208  RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup);
209  char hash[33];
210 
211  /*
212  * Note you may be tempted to think why not include the port. The reason
213  * is that when using TCP the port can potentially differ from before.
214  */
215  ast_str_append(&str, 0, "%s", timestamp);
216  ast_str_append(&str, 0, ":%s", rdata->pkt_info.src_name);
217  ast_str_append(&str, 0, ":%s", eid);
218  ast_str_append(&str, 0, ":%s", realm);
219  ast_md5_hash(hash, ast_str_buffer(str));
220 
221  ast_str_append(nonce, 0, "%s/%s", timestamp, hash);
222  return 0;
223 }
224 
225 /*!
226  * \brief Ensure that a nonce on an incoming request is sane.
227  *
228  * The nonce in an incoming Authorization header needs to pass some scrutiny in order
229  * for us to consider accepting it. What we do is re-build a nonce based on request
230  * data and a realm and see if it matches the nonce they sent us.
231  * \param candidate The nonce on an incoming request
232  * \param rdata The incoming request
233  * \param auth The auth credentials we are trying to match against.
234  * \retval 0 Nonce does not pass validity checks
235  * \retval 1 Nonce passes validity check
236  */
237 static int check_nonce(const char *candidate, const pjsip_rx_data *rdata, const struct ast_sip_auth *auth)
238 {
239  char *copy = ast_strdupa(candidate);
240  char *timestamp = strsep(&copy, "/");
241  int timestamp_int;
242  time_t now = time(NULL);
243  struct ast_str *calculated = ast_str_alloca(64);
244 
245  if (!copy) {
246  /* Clearly a bad nonce! */
247  return 0;
248  }
249 
250  if (sscanf(timestamp, "%30d", &timestamp_int) != 1) {
251  return 0;
252  }
253 
254  if ((int) now - timestamp_int > auth->nonce_lifetime) {
255  return 0;
256  }
257 
258  build_nonce(&calculated, timestamp, rdata, auth->realm);
259  ast_debug(3, "Calculated nonce %s. Actual nonce is %s\n", ast_str_buffer(calculated), candidate);
260  if (strcmp(ast_str_buffer(calculated), candidate)) {
261  return 0;
262  }
263  return 1;
264 }
265 
266 static int find_challenge(const pjsip_rx_data *rdata, const struct ast_sip_auth *auth)
267 {
268  struct pjsip_authorization_hdr *auth_hdr = (pjsip_authorization_hdr *) &rdata->msg_info.msg->hdr;
269  int challenge_found = 0;
270  char nonce[64];
271 
272  while ((auth_hdr = (pjsip_authorization_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, auth_hdr->next))) {
273  ast_copy_pj_str(nonce, &auth_hdr->credential.digest.nonce, sizeof(nonce));
274  if (check_nonce(nonce, rdata, auth) && !pj_strcmp2(&auth_hdr->credential.digest.realm, auth->realm)) {
275  challenge_found = 1;
276  break;
277  }
278  }
279 
280  return challenge_found;
281 }
282 
283 /*!
284  * \brief Common code for initializing a pjsip_auth_srv
285  */
286 static void setup_auth_srv(pj_pool_t *pool, pjsip_auth_srv *auth_server, const char *realm)
287 {
288  pj_str_t realm_str;
289  pj_cstr(&realm_str, realm);
290 
291  pjsip_auth_srv_init(pool, auth_server, &realm_str, digest_lookup, 0);
292 }
293 
294 /*!
295  * \brief Result of digest verification
296  */
297 enum digest_verify_result {
298  /*! Authentication credentials incorrect */
299  AUTH_FAIL = 0,
300  /*! Authentication credentials correct */
301  AUTH_SUCCESS,
302  /*! Authentication credentials correct but nonce mismatch */
303  AUTH_STALE,
304  /*! Authentication credentials were not provided */
305  AUTH_NOAUTH,
306 };
307 
308 static char *verify_result_str[] = {
309  "FAIL",
310  "SUCCESS",
311  "STALE",
312  "NOAUTH"
313 };
314 /*!
315  * \brief astobj2 callback for verifying incoming credentials
316  *
317  * \param auth The ast_sip_auth to check against
318  * \param rdata The incoming request
319  * \param pool A pool to use for the auth server
320  * \return CMP_MATCH on successful authentication
321  * \return 0 on failed authentication
322  */
323 static int verify(const struct ast_sip_auth *auth, pjsip_rx_data *rdata, pj_pool_t *pool)
324 {
325  pj_status_t authed;
326  int response_code;
327  pjsip_auth_srv auth_server;
328  int stale = 0;
329  int res = AUTH_FAIL;
330 
331  if (!find_challenge(rdata, auth)) {
332  /* Couldn't find a challenge with a sane nonce.
333  * Nonce mismatch may just be due to staleness.
334  */
335  stale = 1;
336  }
337 
338  setup_auth_srv(pool, &auth_server, auth->realm);
339 
340  store_auth(auth);
341  authed = pjsip_auth_srv_verify(&auth_server, rdata, &response_code);
342  remove_auth();
343 
344  if (authed == PJ_SUCCESS) {
345  if (stale) {
346  res = AUTH_STALE;
347  } else {
348  res = AUTH_SUCCESS;
349  }
350  }
351 
352  if (authed == PJSIP_EAUTHNOAUTH) {
353  res = AUTH_NOAUTH;
354  }
355 
356  ast_debug(3, "Realm: %s Username: %s Result: %s\n",
357  auth->realm, auth->auth_user, verify_result_str[res]);
358 
359  ast_test_suite_event_notify("INCOMING_AUTH_VERIFY_RESULT",
360  "Realm: %s\r\n"
361  "Username: %s\r\n"
362  "Status: %s",
363  auth->realm, auth->auth_user, verify_result_str[res]);
364 
365  return res;
366 }
367 
368 /*!
369  * \brief astobj2 callback for adding digest challenges to responses
370  *
371  * \param realm An auth's realm to build a challenge from
372  * \param tdata The response to add the challenge to
373  * \param rdata The request the challenge is in response to
374  * \param is_stale Indicates whether nonce on incoming request was stale
375  */
376 static void challenge(const char *realm, pjsip_tx_data *tdata, const pjsip_rx_data *rdata, int is_stale)
377 {
378  pj_str_t qop;
379  pj_str_t pj_nonce;
380  pjsip_auth_srv auth_server;
381  struct ast_str *nonce = ast_str_alloca(256);
382  char time_buf[32];
383  time_t timestamp = time(NULL);
384  snprintf(time_buf, sizeof(time_buf), "%d", (int) timestamp);
385 
386  build_nonce(&nonce, time_buf, rdata, realm);
387 
388  setup_auth_srv(tdata->pool, &auth_server, realm);
389 
390  pj_cstr(&pj_nonce, ast_str_buffer(nonce));
391  pj_cstr(&qop, "auth");
392  pjsip_auth_srv_challenge(&auth_server, &qop, &pj_nonce, NULL, is_stale ? PJ_TRUE : PJ_FALSE, tdata);
393 }
394 
395 /*!
396  * \brief Check authentication using Digest scheme
397  *
398  * This function will check an incoming message against configured authentication
399  * options. If \b any of the incoming Authorization headers result in successful
400  * authentication, then authentication is considered successful.
401  *
402  * \see ast_sip_check_authentication
403  */
404 static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint *endpoint,
405  pjsip_rx_data *rdata, pjsip_tx_data *tdata)
406 {
407  struct ast_sip_auth **auths;
408  struct ast_sip_auth **auths_shallow;
409  enum digest_verify_result *verify_res;
410  struct ast_sip_endpoint *artificial_endpoint;
411  enum ast_sip_check_auth_result res;
412  int idx;
413  int is_artificial;
414  int failures = 0;
415  size_t auth_size;
416 
417  auth_size = AST_VECTOR_SIZE(&endpoint->inbound_auths);
418  ast_assert(0 < auth_size);
419 
420  auths = ast_alloca(auth_size * sizeof(*auths));
421  verify_res = ast_alloca(auth_size * sizeof(*verify_res));
422 
423  artificial_endpoint = ast_sip_get_artificial_endpoint();
424  if (!artificial_endpoint) {
425  /* Should not happen except possibly if we are shutting down. */
426  return AST_SIP_AUTHENTICATION_ERROR;
427  }
428 
429  is_artificial = endpoint == artificial_endpoint;
430  ao2_ref(artificial_endpoint, -1);
431  if (is_artificial) {
432  ast_assert(auth_size == 1);
433  auths[0] = ast_sip_get_artificial_auth();
434  if (!auths[0]) {
435  /* Should not happen except possibly if we are shutting down. */
436  return AST_SIP_AUTHENTICATION_ERROR;
437  }
438  } else {
439  memset(auths, 0, auth_size * sizeof(*auths));
440  if (ast_sip_retrieve_auths(&endpoint->inbound_auths, auths)) {
441  res = AST_SIP_AUTHENTICATION_ERROR;
442  goto cleanup;
443  }
444  }
445 
446  /* Setup shallow copy of auths */
447  if (ast_strlen_zero(default_realm)) {
448  auths_shallow = auths;
449  } else {
450  /*
451  * Set default realm on a shallow copy of the authentication
452  * objects that don't have a realm set.
453  */
454  auths_shallow = ast_alloca(auth_size * sizeof(*auths_shallow));
455  for (idx = 0; idx < auth_size; ++idx) {
456  if (ast_strlen_zero(auths[idx]->realm)) {
457  /*
458  * Make a shallow copy and set the default realm on it.
459  *
460  * The stack allocation is OK here. Normally this will
461  * loop one time. If you have multiple auths then you
462  * shouldn't need more auths than the normal complement
463  * of fingers and toes. Otherwise, you should check
464  * your sanity for setting up your system up that way.
465  */
466  auths_shallow[idx] = ast_alloca(sizeof(**auths_shallow));
467  memcpy(auths_shallow[idx], auths[idx], sizeof(**auths_shallow));
468  *((char **) (&auths_shallow[idx]->realm)) = default_realm;
469  ast_debug(3, "Using default realm '%s' on incoming auth '%s'.\n",
470  default_realm, ast_sorcery_object_get_id(auths_shallow[idx]));
471  } else {
472  auths_shallow[idx] = auths[idx];
473  }
474  }
475  }
476 
477  for (idx = 0; idx < auth_size; ++idx) {
478  verify_res[idx] = verify(auths_shallow[idx], rdata, tdata->pool);
479  if (verify_res[idx] == AUTH_SUCCESS) {
480  res = AST_SIP_AUTHENTICATION_SUCCESS;
481  goto cleanup;
482  }
483  if (verify_res[idx] == AUTH_FAIL) {
484  failures++;
485  }
486  }
487 
488  for (idx = 0; idx < auth_size; ++idx) {
489  challenge(auths_shallow[idx]->realm, tdata, rdata, verify_res[idx] == AUTH_STALE);
490  }
491 
492  if (failures == auth_size) {
493  res = AST_SIP_AUTHENTICATION_FAILED;
494  } else {
495  res = AST_SIP_AUTHENTICATION_CHALLENGE;
496  }
497 
498 cleanup:
499  ast_sip_cleanup_auths(auths, auth_size);
500  return res;
501 }
502 
503 static struct ast_sip_authenticator digest_authenticator = {
504  .requires_authentication = digest_requires_authentication,
505  .check_authentication = digest_check_auth,
506 };
507 
508 static int build_entity_id(void)
509 {
510  char *eid;
511 
512  eid = ao2_alloc(AST_UUID_STR_LEN, NULL);
513  if (!eid) {
514  return -1;
515  }
516 
517  ast_uuid_generate_str(eid, AST_UUID_STR_LEN);
518  ao2_global_obj_replace_unref(entity_id, eid);
519  ao2_ref(eid, -1);
520  return 0;
521 }
522 
523 static void global_loaded(const char *object_type)
524 {
525  ast_sip_get_default_realm(default_realm, sizeof(default_realm));
526 }
527 
528 /*! \brief Observer which is used to update our default_realm when the global setting changes */
529 static struct ast_sorcery_observer global_observer = {
530  .loaded = global_loaded,
531 };
532 
533 static int reload_module(void)
534 {
535  if (build_entity_id()) {
536  return -1;
537  }
538  return 0;
539 }
540 
541 static int load_module(void)
542 {
543  if (build_entity_id()) {
545  }
546 
547  ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
548  ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
549 
550  if (ast_sip_register_authenticator(&digest_authenticator)) {
551  ao2_global_obj_release(entity_id);
553  }
555 }
556 
557 static int unload_module(void)
558 {
559  ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
560  ast_sip_unregister_authenticator(&digest_authenticator);
561  ao2_global_obj_release(entity_id);
562  return 0;
563 }
564 
565 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP authentication resource",
566  .support_level = AST_MODULE_SUPPORT_CORE,
567  .load = load_module,
568  .unload = unload_module,
569  .reload = reload_module,
570  .load_pri = AST_MODPRI_CHANNEL_DEPEND - 5,
571  .requires = "res_pjsip",
572 );
Asterisk main include file. File version handling, generic pbx functions.
void * ast_threadstorage_get(struct ast_threadstorage *ts, size_t init_size)
Retrieve thread storage.
String manipulation functions.
const ast_string_field md5_creds
Definition: res_pjsip.h:587
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
static pj_pool_t * pool
Global memory pool for configuration and timers.
Test Framework API.
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 ao2_global_obj_ref(holder)
Get a reference to the object stored in the global holder.
Definition: astobj2.h:918
static int copy(char *infile, char *outfile)
Utility function to copy a file.
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition: res_stasis.c:327
#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
unsigned int nonce_lifetime
Definition: res_pjsip.h:595
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
int(* requires_authentication)(struct ast_sip_endpoint *endpoint, pjsip_rx_data *rdata)
Check if a request requires authentication See ast_sip_requires_authentication for more details...
Definition: res_pjsip.h:1236
#define ast_debug(level,...)
Log a DEBUG message.
An entity with which Asterisk communicates.
Definition: res_pjsip.h:949
int ast_sorcery_observer_add(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks)
Add an observer to a specific object type.
Definition: sorcery.c:2391
#define ast_test_suite_event_notify(s, f,...)
Definition: test.h:189
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
struct ast_sip_auth_vector inbound_auths
Definition: res_pjsip.h:992
Support for dynamic strings.
Definition: strings.h:623
const ast_string_field realm
Definition: res_pjsip.h:581
Interface for a sorcery object type observer.
Definition: sorcery.h:332
#define ao2_global_obj_release(holder)
Release the ao2 object held in the global holder.
Definition: astobj2.h:859
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition: uuid.c:141
const ast_string_field auth_pass
Definition: res_pjsip.h:585
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.
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
void(* loaded)(const char *object_type)
Callback for when an object type is loaded/reloaded.
Definition: sorcery.h:343
#define AST_THREADSTORAGE_CUSTOM(a, b, c)
Define a thread storage variable, with custom initialization and cleanup.
#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
void ast_sorcery_observer_remove(const struct ast_sorcery *sorcery, const char *type, const struct ast_sorcery_observer *callbacks)
Remove an observer from a specific object type.
Definition: sorcery.c:2423
An interchangeable way of handling digest authentication for SIP.
Definition: res_pjsip.h:1231
void ast_md5_hash(char *output, const char *input)
Produces MD5 hash based on input string.
Definition: utils.c:250
#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
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
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
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:1442