Asterisk - The Open Source Telephony Project GIT-master-2de1a68
res_phoneprov.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 1999 - 2008, Digium, Inc.
5 * Copyright (C) 2014, Fairview 5 Engineering, LLC
6 *
7 * Mark Spencer <markster@digium.com>
8 * Matthew Brooks <mbrooks@digium.com>
9 * Terry Wilson <twilson@digium.com>
10 * George Joseph <george.joseph@fairview5.com>
11 *
12 * See http://www.asterisk.org for more information about
13 * the Asterisk project. Please do not directly contact
14 * any of the maintainers of this project for assistance;
15 * the project provides a web site, mailing lists and IRC
16 * channels for your use.
17 *
18 * This program is free software, distributed under the terms of
19 * the GNU General Public License Version 2. See the LICENSE file
20 * at the top of the source tree.
21 */
22
23/*! \file
24 *
25 * \brief Phone provisioning application for the asterisk internal http server
26 *
27 * \author Matthew Brooks <mbrooks@digium.com>
28 * \author Terry Wilson <twilson@digium.com>
29 * \author George Joseph <george.joseph@fairview5.com>
30 */
31
32/*! \li \ref res_phoneprov.c uses the configuration file \ref phoneprov.conf and \ref users.conf
33 * \addtogroup configuration_file Configuration Files
34 */
35
36/*!
37 * \page phoneprov.conf phoneprov.conf
38 * \verbinclude phoneprov.conf.sample
39 */
40
41/*** MODULEINFO
42 <support_level>extended</support_level>
43 ***/
44
45#define AST_API_MODULE
46
47#include "asterisk.h"
48
49#include <sys/ioctl.h>
50#include <sys/socket.h>
51#include <net/if.h>
52#ifdef SOLARIS
53#include <sys/sockio.h>
54#endif
55
56#include "asterisk/channel.h"
57#include "asterisk/file.h"
58#include "asterisk/paths.h"
59#include "asterisk/pbx.h"
60#include "asterisk/cli.h"
61#include "asterisk/module.h"
62#include "asterisk/http.h"
63#include "asterisk/utils.h"
64#include "asterisk/app.h"
65#include "asterisk/strings.h"
67#include "asterisk/options.h"
68#include "asterisk/config.h"
69#include "asterisk/acl.h"
70#include "asterisk/astobj2.h"
72#include "asterisk/phoneprov.h"
73
74#ifdef LOW_MEMORY
75#define MAX_PROVIDER_BUCKETS 1
76#define MAX_PROFILE_BUCKETS 1
77#define MAX_ROUTE_BUCKETS 1
78#define MAX_USER_BUCKETS 1
79#else
80#define MAX_PROVIDER_BUCKETS 17
81#define MAX_PROFILE_BUCKETS 17
82#define MAX_ROUTE_BUCKETS 563
83#define MAX_USER_BUCKETS 563
84#endif /* LOW_MEMORY */
85
86#define VAR_BUF_SIZE 4096
87
88/*** DOCUMENTATION
89 <function name="PP_EACH_EXTENSION" language="en_US">
90 <synopsis>
91 Execute specified template for each extension.
92 </synopsis>
93 <syntax>
94 <parameter name="mac" required="true" />
95 <parameter name="template_file" required="true" />
96 </syntax>
97 <description>
98 <para>Output the specified template for each extension associated with the specified MAC address.</para>
99 </description>
100 </function>
101 <function name="PP_EACH_USER" language="en_US">
102 <synopsis>
103 Generate a string for each phoneprov user.
104 </synopsis>
105 <syntax>
106 <parameter name="string" required="true" />
107 <parameter name="exclude_mac" required="true" />
108 </syntax>
109 <description>
110 <para>Pass in a string, with phoneprov variables you want substituted in the format of
111 %{VARNAME}, and you will get the string rendered for each user in phoneprov
112 excluding ones with MAC address <replaceable>exclude_mac</replaceable>. Probably not
113 useful outside of res_phoneprov.</para>
114 <para>Example: ${PP_EACH_USER(&lt;item&gt;&lt;fn&gt;%{DISPLAY_NAME}&lt;/fn&gt;&lt;/item&gt;|${MAC})</para>
115 </description>
116 </function>
117 ***/
118
119/*!
120 * \brief Creates a hash function for a structure string field.
121 * \param fname The name to use for the function
122 * \param stype The structure type
123 * \param field The field in the structure to hash
124 *
125 * SIMPLE_HASH_FN(mystruct, myfield) will produce a function
126 * named mystruct_hash_fn which hashes mystruct->myfield.
127 */
128#define SIMPLE_HASH_FN(fname, stype, field) \
129static int fname(const void *obj, const int flags) \
130{ \
131 const struct stype *provider = obj; \
132 const char *key; \
133 switch (flags & OBJ_SEARCH_MASK) { \
134 case OBJ_SEARCH_KEY: \
135 key = obj; \
136 break; \
137 case OBJ_SEARCH_OBJECT: \
138 provider = obj; \
139 key = provider->field; \
140 break; \
141 default: \
142 ast_assert(0); \
143 return 0; \
144 } \
145 return ast_str_hash(key); \
146}
147
148/*!
149 * \brief Creates a compare function for a structure string field.
150 * \param fname The name to use for the function
151 * \param stype The structure type
152 * \param field The field in the structure to compare
153 *
154 * SIMPLE_CMP_FN(mystruct, myfield) will produce a function
155 * named mystruct_cmp_fn which compares mystruct->myfield.
156 */
157#define SIMPLE_CMP_FN(fname, stype, field) \
158static int fname(void *obj, void *arg, int flags) \
159{ \
160 const struct stype *object_left = obj, *object_right = arg; \
161 const char *right_key = arg; \
162 int cmp; \
163 switch (flags & OBJ_SEARCH_MASK) { \
164 case OBJ_SEARCH_OBJECT: \
165 right_key = object_right->field; \
166 case OBJ_SEARCH_KEY: \
167 cmp = strcmp(object_left->field, right_key); \
168 break; \
169 case OBJ_SEARCH_PARTIAL_KEY: \
170 cmp = strncmp(object_left->field, right_key, strlen(right_key)); \
171 break; \
172 default: \
173 cmp = 0; \
174 break; \
175 } \
176 if (cmp) { \
177 return 0; \
178 } \
179 return CMP_MATCH; \
180}
181
182static const char *variable_lookup[] = {
183 [AST_PHONEPROV_STD_MAC] = "MAC",
184 [AST_PHONEPROV_STD_PROFILE] = "PROFILE",
185 [AST_PHONEPROV_STD_USERNAME] = "USERNAME",
186 [AST_PHONEPROV_STD_DISPLAY_NAME] = "DISPLAY_NAME",
187 [AST_PHONEPROV_STD_SECRET] = "SECRET",
188 [AST_PHONEPROV_STD_LABEL] = "LABEL",
189 [AST_PHONEPROV_STD_CALLERID] = "CALLERID",
190 [AST_PHONEPROV_STD_TIMEZONE] = "TIMEZONE",
192 [AST_PHONEPROV_STD_LINEKEYS] = "LINEKEYS",
193 [AST_PHONEPROV_STD_SERVER] = "SERVER",
194 [AST_PHONEPROV_STD_SERVER_PORT] = "SERVER_PORT",
195 [AST_PHONEPROV_STD_SERVER_IFACE] = "SERVER_IFACE",
196 [AST_PHONEPROV_STD_VOICEMAIL_EXTEN] = "VOICEMAIL_EXTEN",
197 [AST_PHONEPROV_STD_EXTENSION_LENGTH] = "EXTENSION_LENGTH",
198 [AST_PHONEPROV_STD_TZOFFSET] = "TZOFFSET",
199 [AST_PHONEPROV_STD_DST_ENABLE] = "DST_ENABLE",
200 [AST_PHONEPROV_STD_DST_START_MONTH] = "DST_START_MONTH",
201 [AST_PHONEPROV_STD_DST_START_MDAY] = "DST_START_MDAY",
202 [AST_PHONEPROV_STD_DST_START_HOUR] = "DST_START_HOUR",
203 [AST_PHONEPROV_STD_DST_END_MONTH] = "DST_END_MONTH",
204 [AST_PHONEPROV_STD_DST_END_MDAY] = "DST_END_MDAY",
205 [AST_PHONEPROV_STD_DST_END_HOUR] = "DST_END_HOUR",
206};
207
208/* Translate the standard variables to their users.conf equivalents. */
209static const char *pp_user_lookup[] = {
210 [AST_PHONEPROV_STD_MAC] = "macaddress",
211 [AST_PHONEPROV_STD_PROFILE] = "profile",
212 [AST_PHONEPROV_STD_USERNAME] = "username",
213 [AST_PHONEPROV_STD_DISPLAY_NAME] = "fullname",
214 [AST_PHONEPROV_STD_SECRET] = "secret",
215 [AST_PHONEPROV_STD_LABEL] = "label",
216 [AST_PHONEPROV_STD_CALLERID] = "cid_number",
217 [AST_PHONEPROV_STD_TIMEZONE] = "timezone",
218 [AST_PHONEPROV_STD_LINENUMBER] = "linenumber",
219 [AST_PHONEPROV_STD_LINEKEYS] = "linekeys",
224 [AST_PHONEPROV_STD_EXTENSION_LENGTH] = "localextenlength",
233};
234
235/* Translate the standard variables to their phoneprov.conf [general] equivalents. */
236static const char *pp_general_lookup[] = {
238 [AST_PHONEPROV_STD_PROFILE] = "default_profile",
247 [AST_PHONEPROV_STD_SERVER] = "serveraddr",
248 [AST_PHONEPROV_STD_SERVER_PORT] = "serverport",
249 [AST_PHONEPROV_STD_SERVER_IFACE] = "serveriface",
260};
261
262/*! \brief for use in lookup_iface */
263static struct in_addr __ourip = { .s_addr = 0x00000000, };
264
265/*! \brief structure to hold config providers */
269 );
271};
273SIMPLE_HASH_FN(phoneprov_provider_hash_fn, phoneprov_provider, provider_name)
274SIMPLE_CMP_FN(phoneprov_provider_cmp_fn, phoneprov_provider, provider_name)
275
276/*! \brief structure to hold file data */
279 AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */
280 AST_STRING_FIELD(template); /*!< Template/physical file location */
281 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
282 );
284};
285
286/*! \brief structure to hold extensions */
287struct extension {
290 );
291 int index;
292 struct varshead *headp; /*!< List of variables to substitute into templates */
294};
295
296/*! \brief structure to hold phone profiles read from phoneprov.conf */
299 AST_STRING_FIELD(name); /*!< Name of phone profile */
300 AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */
301 AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */
302 );
303 struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
304 AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */
305 AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */
306};
308SIMPLE_HASH_FN(phone_profile_hash_fn, phone_profile, name)
309SIMPLE_CMP_FN(phone_profile_cmp_fn, phone_profile, name)
310
311/*! \brief structure to hold users read from users.conf */
312struct user {
314 AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */
315 AST_STRING_FIELD(provider_name); /*!< Name of the provider who registered this mac */
316 );
317 struct phone_profile *profile; /*!< Profile the phone belongs to */
319};
321SIMPLE_HASH_FN(user_hash_fn, user, macaddress)
322SIMPLE_CMP_FN(user_cmp_fn, user, macaddress)
323
324/*! \brief structure to hold http routes (valid URIs, and the files they link to) */
327 AST_STRING_FIELD(uri); /*!< The URI requested */
328 );
329 struct phoneprov_file *file; /*!< The file that links to the URI */
330 struct user *user; /*!< The user that has variables to substitute into the file
331 * NULL in the case of a static route */
333};
335SIMPLE_HASH_FN(http_route_hash_fn, http_route, uri)
336SIMPLE_CMP_FN(http_route_cmp_fn, http_route, uri)
337
338#define SIPUSERS_PROVIDER_NAME "sipusers"
339
340/* iface is the interface (e.g. eth0); address is the return value */
341static int lookup_iface(const char *iface, struct in_addr *address)
342{
343 int mysock, res = 0;
344 struct ifreq ifr;
345 struct sockaddr_in *sin;
346
347 memset(&ifr, 0, sizeof(ifr));
348 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
349
350 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
351 if (mysock < 0) {
352 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
353 return -1;
354 }
355
356 res = ioctl(mysock, SIOCGIFADDR, &ifr);
357
358 close(mysock);
359
360 if (res < 0) {
361 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
362 memcpy(address, &__ourip, sizeof(__ourip));
363 return -1;
364 } else {
365 sin = (struct sockaddr_in *)&ifr.ifr_addr;
366 memcpy(address, &sin->sin_addr, sizeof(*address));
367 return 0;
368 }
369}
370
372{
374}
375
376/*! \brief Delete all providers */
377static void delete_providers(void)
378{
379 if (!providers) {
380 return;
381 }
382
384}
385
386static void provider_destructor(void *obj)
387{
388 struct phoneprov_provider *provider = obj;
390}
391
392static void delete_file(struct phoneprov_file *file)
393{
395 ast_free(file);
396}
397
398/*! \brief Read a TEXT file into a string and return the length */
399static int load_file(const char *filename, char **ret)
400{
401 int len = 0;
402 FILE *f;
403
404 if (!(f = fopen(filename, "r"))) {
405 *ret = NULL;
406 return -1;
407 }
408
409 fseek(f, 0, SEEK_END);
410 len = ftell(f);
411 fseek(f, 0, SEEK_SET);
412 if (!(*ret = ast_malloc(len + 1))) {
413 fclose(f);
414 return -2;
415 }
416
417 if (len != fread(*ret, sizeof(char), len, f)) {
418 fclose(f);
419 ast_free(*ret);
420 *ret = NULL;
421 return -3;
422 }
423
424 fclose(f);
425
426 (*ret)[len] = '\0';
427
428 return len;
429}
430
431/*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
432 \param headp pointer to list of user variables
433 \param zone A time zone. NULL sets variables based on timezone of the machine
434*/
435static void set_timezone_variables(struct varshead *headp, const char *zone)
436{
437 time_t utc_time;
438 int dstenable;
439 time_t dststart;
440 time_t dstend;
441 struct ast_tm tm_info;
442 int tzoffset;
443 char buffer[21];
444 struct timeval when;
445
446 time(&utc_time);
447 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
448 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
449 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("TZOFFSET", buffer));
450
451 if (!dstenable) {
452 return;
453 }
454
455 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_ENABLE", "1"));
456
457 when.tv_sec = dststart;
458 ast_localtime(&when, &tm_info, zone);
459
460 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
461 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_MONTH", buffer));
462
463 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
464 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_MDAY", buffer));
465
466 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
467 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_HOUR", buffer));
468
469 when.tv_sec = dstend;
470 ast_localtime(&when, &tm_info, zone);
471
472 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
473 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_MONTH", buffer));
474
475 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
476 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_MDAY", buffer));
477
478 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
479 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_HOUR", buffer));
480}
481
482static struct http_route *unref_route(struct http_route *route)
483{
484 ao2_cleanup(route);
485
486 return NULL;
487}
488
489static void route_destructor(void *obj)
490{
491 struct http_route *route = obj;
492
494}
495
496/*! \brief Delete all http routes, freeing their memory */
497static void delete_routes(void)
498{
499 if (!http_routes) {
500 return;
501 }
502
504}
505
506/*! \brief Build a route structure and add it to the list of available http routes
507 \param pp_file File to link to the route
508 \param profile
509 \param user User to link to the route (NULL means static route)
510 \param uri URI of the route
511*/
512static void build_route(struct phoneprov_file *pp_file, struct phone_profile *profile, struct user *user, char *uri)
513{
514 struct http_route *route;
515
516 if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
517 return;
518 }
519
520 if (ast_string_field_init(route, 32)) {
521 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
522 route = unref_route(route);
523 return;
524 }
525
526 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
527 route->user = user;
528 route->file = pp_file;
529 route->profile = profile;
530
531 ao2_link(http_routes, route);
532
533 route = unref_route(route);
534}
535
536static struct phone_profile *unref_profile(struct phone_profile *prof)
537{
538 ao2_cleanup(prof);
539
540 return NULL;
541}
542
543/*! \brief Return a phone profile looked up by name */
544static struct phone_profile *find_profile(const char *name)
545{
547}
548
549static void profile_destructor(void *obj)
550{
551 struct phone_profile *profile = obj;
552 struct phoneprov_file *file;
553 struct ast_var_t *var;
554
555 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry))) {
557 }
558
559 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry))) {
561 }
562
563 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries))) {
565 }
566
567 ast_free(profile->headp);
569}
570
571/*! \brief Delete all phone profiles, freeing their memory */
572static void delete_profiles(void)
573{
574 if (!profiles) {
575 return;
576 }
577
579}
580
581/*! \brief Build a phone profile and add it to the list of phone profiles
582 \param name the name of the profile
583 \param v ast_variable from parsing phoneprov.conf
584*/
585static void build_profile(const char *name, struct ast_variable *v)
586{
587 struct phone_profile *profile;
588
589 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
590 return;
591 }
592
593 if (ast_string_field_init(profile, 32)) {
594 profile = unref_profile(profile);
595 return;
596 }
597
598 if (!(profile->headp = ast_var_list_create())) {
599 profile = unref_profile(profile);
600 return;
601 }
602
605
606 ast_string_field_set(profile, name, name);
607 for (; v; v = v->next) {
608 if (!strcasecmp(v->name, "mime_type")) {
610 } else if (!strcasecmp(v->name, "setvar")) {
611 char value_copy[strlen(v->value) + 1];
612
614 AST_APP_ARG(varname);
615 AST_APP_ARG(varval);
616 );
617
618 strcpy(value_copy, v->value); /* safe */
619 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
620 do {
621 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
622 break;
623 args.varname = ast_strip(args.varname);
624 args.varval = ast_strip(args.varval);
625 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
626 break;
627 AST_VAR_LIST_INSERT_TAIL(profile->headp, ast_var_assign(args.varname, args.varval));
628 } while (0);
629 } else if (!strcasecmp(v->name, "staticdir")) {
631 } else {
632 struct phoneprov_file *pp_file;
633 char *file_extension;
634 char value_copy[strlen(v->value) + 1];
635
637 AST_APP_ARG(filename);
638 AST_APP_ARG(mimetype);
639 );
640
641 if (!(pp_file = ast_calloc_with_stringfields(1, struct phoneprov_file, 32))) {
642 profile = unref_profile(profile);
643 return;
644 }
645
646 if ((file_extension = strrchr(pp_file->format, '.')))
647 file_extension++;
648
649 strcpy(value_copy, v->value); /* safe */
650 AST_STANDARD_APP_ARGS(args, value_copy);
651
652 /* Mime type order of preference
653 * 1) Specific mime-type defined for file in profile
654 * 2) Mime determined by extension
655 * 3) Default mime type specified in profile
656 * 4) text/plain
657 */
658 ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype,
659 (S_OR(S_OR(ast_http_ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
660
661 if (!strcasecmp(v->name, "static_file")) {
662 ast_string_field_set(pp_file, format, args.filename);
663 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
664 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
665 /* Add a route for the static files, as their filenames won't change per-user */
666 build_route(pp_file, profile, NULL, NULL);
667 } else {
668 ast_string_field_set(pp_file, format, v->name);
669 ast_string_field_set(pp_file, template, args.filename);
670 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
671 }
672 }
673 }
674
675 ao2_link(profiles, profile);
676
677 profile = unref_profile(profile);
678}
679
680static struct extension *delete_extension(struct extension *exten)
681{
684 ast_free(exten);
685
686 return NULL;
687}
688
689static struct extension *build_extension(const char *name, struct varshead *vars)
690{
691 struct extension *exten;
692 const char *tmp;
693
694 if (!(exten = ast_calloc_with_stringfields(1, struct extension, 32))) {
695 return NULL;
696 }
697
699
700 exten->headp = ast_var_list_clone(vars);
701 if (!exten->headp) {
702 ast_log(LOG_ERROR, "Unable to clone variables for extension '%s'\n", name);
703 delete_extension(exten);
704 return NULL;
705 }
706
708 if (!tmp) {
711 exten->index = 1;
712 } else {
713 sscanf(tmp, "%d", &exten->index);
714 }
715
719 }
720
723
724 return exten;
725}
726
727static struct user *unref_user(struct user *user)
728{
730
731 return NULL;
732}
733
734/*! \brief Return a user looked up by name */
735static struct user *find_user(const char *macaddress)
736{
738}
739
740static int routes_delete_cb(void *obj, void *arg, int flags)
741{
742 struct http_route *route = obj;
743 struct user *user = route->user;
744 char *macaddress = arg;
745
746 if (user && !strcmp(user->macaddress, macaddress)) {
747 return CMP_MATCH;
748 }
749 return 0;
750}
751
752/*! \brief Free all memory associated with a user */
753static void user_destructor(void *obj)
754{
755 struct user *user = obj;
756 struct extension *exten;
757
758 while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
759 exten = delete_extension(exten);
760 }
761
762 if (user->profile) {
764 }
765
766 if (http_routes) {
768 }
769
771}
772
773/*! \brief Delete all users */
774static void delete_users(void)
775{
776 if (!users) {
777 return;
778 }
779
781}
782
783/*! \brief Build and return a user structure based on gathered config data */
784static struct user *build_user(const char *mac, struct phone_profile *profile, char *provider_name)
785{
786 struct user *user;
787
788 if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
789 return NULL;
790 }
791
792 if (ast_string_field_init(user, 64)) {
794 return NULL;
795 }
796
800 ao2_ref(profile, 1);
801
802 return user;
803}
804
805/*! \brief Add an extension to a user ordered by index/linenumber */
806static int add_user_extension(struct user *user, struct extension *exten)
807{
808 struct ast_var_t *pvar, *var2;
809 struct ast_str *str = ast_str_create(16);
810
811 if (!str) {
812 return -1;
813 }
814
815 /* Append profile variables here, and substitute variables on profile
816 * setvars, so that we can use user specific variables in them */
818 if (ast_var_find(exten->headp, pvar->name)) {
819 continue;
820 }
821
823 if ((var2 = ast_var_assign(pvar->name, ast_str_buffer(str)))) {
824 AST_VAR_LIST_INSERT_TAIL(exten->headp, var2);
825 }
826 }
827 ast_free(str);
828
831 } else {
832 struct extension *exten_iter;
833
835 if (exten->index < exten_iter->index) {
837 } else if (exten->index == exten_iter->index) {
838 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
839 return -1;
840 } else if (!AST_LIST_NEXT(exten_iter, entry)) {
842 }
843 }
845 }
846
847 return 0;
848}
849
850/*! \brief Add an http route for dynamic files attached to the profile of the user */
851static int build_user_routes(struct user *user)
852{
853 struct phoneprov_file *pp_file;
854 struct ast_str *str;
855
856 if (!(str = ast_str_create(16))) {
857 return -1;
858 }
859
863 }
864
865 ast_free(str);
866 return 0;
867}
868
869/*! \brief Callback that is executed everytime an http request is received by this module */
870static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers)
871{
872 struct http_route *route;
873 struct ast_str *result;
874 char path[PATH_MAX];
875 char *file = NULL;
876 char *server;
877 char *newserver = NULL;
878 struct extension *exten_iter;
879 int len;
880 int fd;
881 struct ast_str *http_header;
882
884 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
885 return 0;
886 }
887
888 if (!(route = ao2_find(http_routes, uri, OBJ_SEARCH_KEY))) {
889 goto out404;
890 }
891
892 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
893
894 if (!route->user) { /* Static file */
895
896 fd = open(path, O_RDONLY);
897 if (fd < 0) {
898 goto out500;
899 }
900
901 len = lseek(fd, 0, SEEK_END);
902 lseek(fd, 0, SEEK_SET);
903 if (len < 0) {
904 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
905 close(fd);
906 goto out500;
907 }
908
909 http_header = ast_str_create(80);
910 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
911 route->file->mime_type);
912
913 ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
914
915 close(fd);
916 route = unref_route(route);
917 return 0;
918 } else { /* Dynamic file */
919 struct ast_str *tmp;
920
921 len = load_file(path, &file);
922 if (len < 0) {
923 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
924 if (file) {
925 ast_free(file);
926 }
927
928 goto out500;
929 }
930
931 if (!file) {
932 goto out500;
933 }
934
935 if (!(tmp = ast_str_create(len))) {
936 if (file) {
937 ast_free(file);
938 }
939
940 goto out500;
941 }
942
943 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
944 * the IP address we are listening on that the phone contacted for this config file */
945
946 server = ast_var_find(AST_LIST_FIRST(&route->user->extensions)->headp,
948
949 if (!server) {
950 union {
951 struct sockaddr sa;
952 struct sockaddr_in sa_in;
953 } name;
954 socklen_t namelen = sizeof(name.sa);
955 int res;
956
957 if ((res = getsockname(ast_iostream_get_fd(ser->stream), &name.sa, &namelen))) {
958 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
959 } else {
960 newserver = ast_strdupa(ast_inet_ntoa(name.sa_in.sin_addr));
961
962 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
965 }
966 }
967 }
968
970
971 /* Do not retain dynamic SERVER address because next request from the phone might arrive on
972 * different interface IP address eg. if this is a multi-homed server on multiple subnets */
973 if (newserver) {
974 struct ast_var_t *varns;
975 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
976 AST_LIST_TRAVERSE_SAFE_BEGIN(exten_iter->headp, varns, entries) {
979 ast_var_delete(varns);
980 }
981 }
983 }
984 }
985
986 ast_free(file);
987
988 http_header = ast_str_create(80);
989 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
990 route->file->mime_type);
991
992 if (!(result = ast_str_create(512))) {
993 ast_log(LOG_ERROR, "Could not create result string!\n");
994 if (tmp) {
995 ast_free(tmp);
996 }
997 ast_free(http_header);
998 goto out500;
999 }
1001
1002 ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
1003 ast_free(tmp);
1004
1005 route = unref_route(route);
1006
1007 return 0;
1008 }
1009
1010out404:
1011 ast_http_error(ser, 404, "Not Found", uri);
1012 return 0;
1013
1014out500:
1015 route = unref_route(route);
1016 ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
1017 return 0;
1018}
1019
1020/*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1021static int pp_each_user_helper(struct ast_channel *chan, char *data, char *buf, struct ast_str **bufstr, int len)
1022{
1023 char *tmp;
1024 struct ao2_iterator i;
1025 struct user *user;
1026 struct ast_str *str;
1028 AST_APP_ARG(string);
1029 AST_APP_ARG(exclude_mac);
1030 );
1032
1033 if (!(str = ast_str_create(16))) {
1034 return -1;
1035 }
1036
1037 /* Fix data by turning %{ into ${ */
1038 while ((tmp = strstr(args.string, "%{")))
1039 *tmp = '$';
1040
1041 i = ao2_iterator_init(users, 0);
1042 while ((user = ao2_iterator_next(&i))) {
1043 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1044 continue;
1045 }
1047 if (buf) {
1048 size_t slen = len;
1049 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1050 } else {
1051 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1052 }
1053 user = unref_user(user);
1054 }
1056
1057 ast_free(str);
1058 return 0;
1059}
1060
1061static int pp_each_user_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1062{
1063 return pp_each_user_helper(chan, data, buf, NULL, len);
1064}
1065
1066static int pp_each_user_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1067{
1068 return pp_each_user_helper(chan, data, NULL, buf, len);
1069}
1070
1072 .name = "PP_EACH_USER",
1073 .read = pp_each_user_read,
1074 .read2 = pp_each_user_read2,
1075};
1076
1077/*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1078static int pp_each_extension_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, int len)
1079{
1080 struct user *user;
1081 struct extension *exten;
1082 char path[PATH_MAX];
1083 char *file;
1084 int filelen;
1085 struct ast_str *str;
1087 AST_APP_ARG(mac);
1088 AST_APP_ARG(template);
1089 );
1090
1092
1093 if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1094 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requires both a macaddress and template filename.\n");
1095 return 0;
1096 }
1097
1098 if (!(user = find_user(args.mac))) {
1099 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1100 return 0;
1101 }
1102
1103 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1104 filelen = load_file(path, &file);
1105 if (filelen < 0) {
1106 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1107 if (file) {
1108 ast_free(file);
1109 }
1110 return 0;
1111 }
1112
1113 if (!file) {
1114 return 0;
1115 }
1116
1117 if (!(str = ast_str_create(filelen))) {
1118 return 0;
1119 }
1120
1123 if (buf) {
1124 size_t slen = len;
1125 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1126 } else {
1127 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1128 }
1129 }
1130
1131 ast_free(file);
1132 ast_free(str);
1133
1134 user = unref_user(user);
1135
1136 return 0;
1137}
1138
1139static int pp_each_extension_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1140{
1141 return pp_each_extension_helper(chan, cmd, data, buf, NULL, len);
1142}
1143
1144static int pp_each_extension_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1145{
1146 return pp_each_extension_helper(chan, cmd, data, NULL, buf, len);
1147}
1148
1150 .name = "PP_EACH_EXTENSION",
1151 .read = pp_each_extension_read,
1152 .read2 = pp_each_extension_read2,
1153};
1154
1155#define FORMATS "%-20.20s %-40.40s %-30.30s\n"
1156#define FORMATD "%-20.20s %-20.20s %-40.40s %-30.30s\n"
1157static int route_list_cb(void *obj, void *arg, void *data, int flags)
1158{
1159 int fd = *(int *)arg;
1160 struct http_route *route = obj;
1161
1162 if (data && route->user) {
1163 ast_cli(fd, FORMATD, route->user->provider_name, route->profile->name, route->uri, route->file->template);
1164 }
1165 if (!data && !route->user) {
1166 ast_cli(fd, FORMATS, route->profile->name, route->uri, route->file->template);
1167 }
1168
1169 return CMP_MATCH;
1170}
1171
1172/*! \brief CLI command to list static and dynamic routes */
1173static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1174{
1175 int fd = a->fd;
1176 switch(cmd) {
1177 case CLI_INIT:
1178 e->command = "phoneprov show routes";
1179 e->usage =
1180 "Usage: phoneprov show routes\n"
1181 " Lists all registered phoneprov http routes.\n";
1182 return NULL;
1183 case CLI_GENERATE:
1184 return NULL;
1185 }
1186
1187 /* This currently iterates over routes twice, but it is the only place I've needed
1188 * to really separate static an dynamic routes, so I've just left it this way. */
1189 ast_cli(a->fd, "Static routes\n\n");
1190 ast_cli(a->fd, FORMATS, "Profile", "Relative URI", "Physical location");
1191
1193
1194 ast_cli(a->fd, "\nDynamic routes\n\n");
1195 ast_cli(a->fd, FORMATD, "Provider", "Profile", "Relative URI", "Template");
1196
1198
1199 return CLI_SUCCESS;
1200}
1201
1202static struct ast_cli_entry pp_cli[] = {
1203 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1204};
1205
1208 .description = "Asterisk HTTP Phone Provisioning Tool",
1209 .uri = "phoneprov",
1210 .has_subtree = 1,
1211 .data = NULL,
1212 .key = __FILE__,
1213};
1214
1215static struct varshead *get_defaults(void)
1216{
1217 struct ast_config *phoneprov_cfg, *cfg = CONFIG_STATUS_FILEINVALID;
1218 const char *value;
1219 struct ast_variable *v;
1220 struct ast_var_t *var;
1221 struct ast_flags config_flags = { 0 };
1222 struct varshead *defaults = ast_var_list_create();
1223
1224 if (!defaults) {
1225 ast_log(LOG_ERROR, "Unable to create default var list.\n");
1226 return NULL;
1227 }
1228
1229 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
1230 || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
1231 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
1232 ast_var_list_destroy(defaults);
1233 return NULL;
1234 }
1235
1237 if (!value) {
1238 struct in_addr addr;
1240 if (value) {
1241 lookup_iface(value, &addr);
1242 value = ast_inet_ntoa(addr);
1243 }
1244 }
1245 if (value) {
1247 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1248 } else {
1249 ast_log(LOG_WARNING, "Unable to find a valid server address or name.\n");
1250 }
1251
1254 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
1255 ast_config_destroy(cfg);
1256 }
1257 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1258
1260 if (!value) {
1261 ast_log(LOG_ERROR, "Unable to load default profile.\n");
1262 ast_config_destroy(phoneprov_cfg);
1263 ast_var_list_destroy(defaults);
1264 return NULL;
1265 }
1267 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1268 ast_config_destroy(phoneprov_cfg);
1269
1270 if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
1271 ast_log(LOG_ERROR, "Unable to load users.conf\n");
1272 ast_var_list_destroy(defaults);
1273 return NULL;
1274 }
1275
1276 /* Go ahead and load global variables from users.conf so we can append to profiles */
1277 for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
1280 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1281 }
1284 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1285 }
1286 }
1287 ast_config_destroy(cfg);
1288
1289 return defaults;
1290}
1291
1292static int load_users(void)
1293{
1294 struct ast_config *cfg;
1295 char *cat;
1296 const char *value;
1297 struct ast_flags config_flags = { 0 };
1298 struct varshead *defaults = get_defaults();
1299
1300 if (!defaults) {
1301 ast_log(LOG_WARNING, "Unable to load default variables.\n");
1302 return -1;
1303 }
1304
1305 if (!(cfg = ast_config_load("users.conf", config_flags))
1306 || cfg == CONFIG_STATUS_FILEINVALID) {
1307 ast_log(LOG_WARNING, "Unable to load users.conf\n");
1308 ast_var_list_destroy(defaults);
1309 return -1;
1310 }
1311
1312 cat = NULL;
1313 while ((cat = ast_category_browse(cfg, cat))) {
1314 const char *tmp;
1315 int i;
1316 struct ast_var_t *varx;
1317 struct ast_var_t *vard;
1318
1319 if (strcasecmp(cat, "general") && strcasecmp(cat, "authentication")) {
1320 struct varshead *variables = ast_var_list_create();
1321
1322 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp))) {
1323 ast_var_list_destroy(variables);
1324 continue;
1325 }
1326
1327 /* Transfer the standard variables */
1328 for (i = 0; i < AST_PHONEPROV_STD_VAR_LIST_LENGTH; i++) {
1329 if (pp_user_lookup[i]) {
1331 if (value) {
1333 value);
1334 AST_VAR_LIST_INSERT_TAIL(variables, varx);
1335 }
1336 }
1337 }
1338
1340 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
1341 ast_var_list_destroy(variables);
1342 continue;
1343 }
1344
1345 /* Apply defaults */
1346 AST_VAR_LIST_TRAVERSE(defaults, vard) {
1347 if (ast_var_find(variables, vard->name)) {
1348 continue;
1349 }
1350 varx = ast_var_assign(vard->name, vard->value);
1351 AST_VAR_LIST_INSERT_TAIL(variables, varx);
1352 }
1353
1355 }
1356 }
1357 ast_config_destroy(cfg);
1358 ast_var_list_destroy(defaults);
1359 return 0;
1360}
1361
1362static int load_common(void)
1363{
1364 struct ast_config *phoneprov_cfg;
1365 struct ast_flags config_flags = { 0 };
1366 char *cat;
1367
1368 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
1369 || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
1370 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
1371 return -1;
1372 }
1373
1374 cat = NULL;
1375 while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
1376 if (!strcasecmp(cat, "general")) {
1377 continue;
1378 }
1379 build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
1380 }
1381 ast_config_destroy(phoneprov_cfg);
1382
1384 ast_log(LOG_ERROR, "There are no provisioning profiles in phoneprov.conf.\n");
1385 return -1;
1386 }
1387
1388 return 0;
1389}
1390
1391static int unload_module(void)
1392{
1397
1398 /* This cleans up the users.conf provider (called specifically for clarity) */
1400
1401 /* This cleans up the framework which also cleans up the providers. */
1404 profiles = NULL;
1405 delete_routes();
1406 delete_users();
1408 http_routes = NULL;
1410 users = NULL;
1413 providers = NULL;
1414
1415 return 0;
1416}
1417
1418/*!
1419 * \brief Load the module
1420 *
1421 * Module loading including tests for configuration or dependencies.
1422 * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
1423 * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
1424 * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
1425 * configuration file or other non-critical problem return
1426 * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
1427 */
1428static int load_module(void)
1429{
1431 phone_profile_hash_fn, NULL, phone_profile_cmp_fn);
1432 if (!profiles) {
1433 ast_log(LOG_ERROR, "Unable to allocate profiles container.\n");
1435 }
1436
1438 http_route_hash_fn, NULL, http_route_cmp_fn);
1439 if (!http_routes) {
1440 ast_log(LOG_ERROR, "Unable to allocate routes container.\n");
1441 goto error;
1442 }
1443
1444 if (load_common()) {
1445 ast_log(LOG_ERROR, "Unable to load provisioning profiles.\n");
1446 goto error;
1447 }
1448
1450 user_hash_fn, NULL, user_cmp_fn);
1451 if (!users) {
1452 ast_log(LOG_ERROR, "Unable to allocate users container.\n");
1453 goto error;
1454 }
1455
1457 MAX_PROVIDER_BUCKETS, phoneprov_provider_hash_fn, NULL, phoneprov_provider_cmp_fn);
1458 if (!providers) {
1459 ast_log(LOG_ERROR, "Unable to allocate providers container.\n");
1460 goto error;
1461 }
1462
1463 /* Register ourselves as the provider for users.conf */
1465 ast_log(LOG_WARNING, "Unable register users config provider. Others may succeed.\n");
1466 }
1467
1469
1473
1475
1476error:
1477 unload_module();
1479}
1480
1481static int reload(void)
1482{
1483 struct ao2_iterator i;
1485
1486 /* Clean everything except the providers */
1487 delete_routes();
1488 delete_users();
1490
1491 /* Reload the profiles */
1492 if (load_common()) {
1493 ast_log(LOG_ERROR, "Unable to reload provisioning profiles.\n");
1494 unload_module();
1496 }
1497
1498 /* For each provider, reload the users */
1501 for(; (provider = ao2_iterator_next(&i)); ao2_ref(provider, -1)) {
1502 if (provider->load_users()) {
1503 ast_log(LOG_ERROR, "Unable to load provider '%s' users. Reload aborted.\n", provider->provider_name);
1504 continue;
1505 }
1506 }
1509
1511}
1512
1514 .support_level = AST_MODULE_SUPPORT_EXTENDED,
1515 .load = load_module,
1516 .unload = unload_module,
1517 .reload = reload,
1518 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
1519 .requires = "http",
1521
1522/**** Public API for register/unregister, set defaults, and add extension. ****/
1523
1525{
1527 return NULL;
1528 }
1529
1530 return variable_lookup[var];
1531}
1532
1535{
1537
1539 ast_log(LOG_ERROR, "Provider name can't be empty.\n");
1540 return -1;
1541 }
1542
1543 if (!providers) {
1544 ast_log(LOG_WARNING, "Provider '%s' cannot be registered: res_phoneprov not loaded.\n", provider_name);
1545 return -1;
1546 }
1547
1549 if (provider) {
1550 ast_log(LOG_ERROR, "There is already a provider registered named '%s'.\n", provider_name);
1551 ao2_ref(provider, -1);
1552 return -1;
1553 }
1554
1556 if (!provider) {
1557 ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s'.\n", provider_name);
1558 return -1;
1559 }
1560
1562 ao2_ref(provider, -1);
1563 ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s' stringfields.\n", provider_name);
1564 return -1;
1565 }
1566
1568 provider->load_users = load_users;
1569
1571 ao2_ref(provider, -1);
1572
1573 if (provider->load_users()) {
1574 ast_log(LOG_ERROR, "Unable to load provider '%s' users. Register aborted.\n", provider_name);
1576 return -1;
1577 }
1578
1579 return 0;
1580}
1581
1582static int extensions_delete_cb(void *obj, void *arg, int flags)
1583{
1584 char *provider_name = arg;
1585 struct user *user = obj;
1586 if (strcmp(user->provider_name, provider_name)) {
1587 return 0;
1588 }
1589 return CMP_MATCH;
1590}
1591
1592static int extension_delete_cb(void *obj, void *arg, void *data, int flags)
1593{
1594 struct user *user = obj;
1595 char *provider_name = data;
1596 char *macaddress = arg;
1597
1598 if (!strcmp(user->provider_name, provider_name) && !strcasecmp(user->macaddress, macaddress)) {
1599 return CMP_MATCH;
1600 }
1601 return 0;
1602}
1603
1605{
1606 if (!users) {
1607 return;
1608 }
1609
1612}
1613
1615{
1616 if (!users) {
1617 return;
1618 }
1619
1621}
1622
1624{
1625 if (!providers) {
1626 return;
1627 }
1628
1631}
1632
1634{
1636 RAII_VAR(struct user *, user, NULL, ao2_cleanup);
1638 struct extension *exten;
1639 char *profile_name;
1640 char *mac;
1641 char *username;
1642
1643 if (ast_strlen_zero(provider_name)) {
1644 ast_log(LOG_ERROR, "Provider name can't be empty.\n");
1645 return -1;
1646 }
1647 if (!vars) {
1648 ast_log(LOG_ERROR, "Variable list can't be empty.\n");
1649 return -1;
1650 }
1651
1653 if (!username) {
1654 ast_log(LOG_ERROR, "Extension name can't be empty.\n");
1655 return -1;
1656 }
1657
1659 if (!mac) {
1660 ast_log(LOG_ERROR, "MAC Address can't be empty.\n");
1661 return -1;
1662 }
1663
1664 provider = find_provider(provider_name);
1665 if (!provider) {
1666 ast_log(LOG_ERROR, "Provider '%s' wasn't found in the registry.\n", provider_name);
1667 return -1;
1668 }
1669
1670 profile_name = ast_var_find(vars,
1672 if (!profile_name) {
1673 ast_log(LOG_ERROR, "No profile could be found for user '%s' - skipping.\n", username);
1674 return -1;
1675 }
1676 if (!(profile = find_profile(profile_name))) {
1677 ast_log(LOG_ERROR, "Could not look up profile '%s' - skipping.\n", profile_name);
1678 return -1;
1679 }
1680
1681 if (!(user = find_user(mac))) {
1682
1683 if (!(user = build_user(mac, profile, provider_name))) {
1684 ast_log(LOG_ERROR, "Could not create user for '%s' - skipping\n", mac);
1685 return -1;
1686 }
1687
1688 if (!(exten = build_extension(username, vars))) {
1689 ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
1690 return -1;
1691 }
1692
1693 if (add_user_extension(user, exten)) {
1694 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1695 exten = delete_extension(exten);
1696 return -1;
1697 }
1698
1699 if (build_user_routes(user)) {
1700 ast_log(LOG_WARNING, "Could not create http routes for '%s' - skipping\n", user->macaddress);
1701 return -1;
1702 }
1704
1705 } else {
1706 if (strcmp(provider_name, user->provider_name)) {
1707 ast_log(LOG_ERROR, "MAC address '%s' was already added by provider '%s' - skipping\n", user->macaddress, user->provider_name);
1708 return -1;
1709 }
1710
1711 if (!(exten = build_extension(username, vars))) {
1712 ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
1713 return -1;
1714 }
1715
1716 if (add_user_extension(user, exten)) {
1717 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1718 exten = delete_extension(exten);
1719 return -1;
1720 }
1721 }
1722
1723 return 0;
1724}
Access Control of various sorts.
const char * str
Definition: app_jack.c:147
#define var
Definition: ast_expr2f.c:605
Asterisk version information.
Asterisk main include file. File version handling, generic pbx functions.
#define PATH_MAX
Definition: asterisk.h:40
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
#define ast_log
Definition: astobj2.c:42
#define ao2_iterator_next(iter)
Definition: astobj2.h:1911
#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_MUTEX
Definition: astobj2.h:363
#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
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_callback_data(container, flags, cb_fn, arg, data)
Definition: astobj2.h:1723
#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
void ao2_iterator_destroy(struct ao2_iterator *iter)
Destroy a container iterator.
@ OBJ_NODATA
Definition: astobj2.h:1044
@ OBJ_MULTIPLE
Definition: astobj2.h:1049
@ 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
static struct prometheus_metrics_provider provider
Definition: bridges.c:201
static int tmp()
Definition: bt_open.c:389
static PGresult * result
Definition: cel_pgsql.c:84
General Asterisk PBX channel definitions.
void ast_var_list_destroy(struct varshead *head)
Definition: chanvars.c:109
char * ast_var_find(const struct varshead *head, const char *name)
Definition: chanvars.c:85
static void AST_VAR_LIST_INSERT_TAIL(struct varshead *head, struct ast_var_t *var)
Definition: chanvars.h:51
const char * ast_var_name(const struct ast_var_t *var)
Definition: chanvars.c:60
#define AST_VAR_LIST_TRAVERSE(head, var)
Definition: chanvars.h:49
#define ast_var_assign(name, value)
Definition: chanvars.h:40
struct varshead * ast_var_list_clone(struct varshead *head)
Definition: chanvars.c:124
void ast_var_delete(struct ast_var_t *var)
Definition: extconf.c:2471
struct varshead * ast_var_list_create(void)
Definition: chanvars.c:97
Standard Command Line Interface.
#define CLI_SUCCESS
Definition: cli.h:44
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
char * address
Definition: f2c.h:59
Generic File Format Support. Should be included by clients of the file handling routines....
static const char name[]
Definition: format_mp3.c:68
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
Support for Private Asterisk HTTP Servers.
void ast_http_send(struct ast_tcptls_session_instance *ser, enum ast_http_method method, int status_code, const char *status_title, struct ast_str *http_header, struct ast_str *out, int fd, unsigned int static_content)
Generic function for sending HTTP/1.1 response.
Definition: http.c:459
ast_http_method
HTTP Request methods known by Asterisk.
Definition: http.h:58
@ AST_HTTP_GET
Definition: http.h:60
@ AST_HTTP_HEAD
Definition: http.h:62
const char * ast_http_ftype2mtype(const char *ftype) attribute_pure
Return mime type based on extension.
Definition: http.c:206
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
Definition: http.c:708
void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text)
Send HTTP error message and close socket.
Definition: http.c:651
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition: http.c:676
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
#define AST_APP_ARG(name)
Define an application argument.
#define AST_DECLARE_APP_ARGS(name, arglist)
Declare a structure to hold an application's arguments.
#define AST_STANDARD_APP_ARGS(args, parse)
Performs the 'standard' argument separation process for an application.
#define AST_NONSTANDARD_APP_ARGS(args, parse, sep)
Performs the 'nonstandard' argument separation process for an application.
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3326
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:783
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
#define LOG_ERROR
#define LOG_WARNING
int ast_iostream_get_fd(struct ast_iostream *stream)
Get an iostream's file descriptor.
Definition: iostream.c:84
#define AST_LIST_HEAD_INIT_NOLOCK(head)
Initializes a list head structure.
Definition: linkedlists.h:681
#define AST_LIST_HEAD_NOLOCK(name, type)
Defines a structure to be used to hold a list of specified type (with no lock).
Definition: linkedlists.h:225
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
Definition: linkedlists.h:450
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
#define AST_LIST_INSERT_BEFORE_CURRENT(elm, field)
Inserts a list entry before the current entry during a traversal.
Definition: linkedlists.h:599
#define AST_LIST_INSERT_HEAD(head, elm, field)
Inserts a list entry at the head of a list.
Definition: linkedlists.h:711
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
#define AST_LIST_FIRST(head)
Returns the first entry contained in a list.
Definition: linkedlists.h:421
#define AST_LIST_NEXT(elm, field)
Returns the next entry in the list after the given entry.
Definition: linkedlists.h:439
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
void ast_get_dst_info(const time_t *const timep, int *dst_enabled, time_t *dst_start, time_t *dst_end, int *gmt_off, const char *const zone)
Definition: localtime.c:1754
int errno
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:317
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition: module.h:316
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODPRI_CHANNEL_DEPEND
Definition: module.h:326
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#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
const char * ast_inet_ntoa(struct in_addr ia)
thread-safe replacement for inet_ntoa().
Definition: utils.c:928
Options provided by main asterisk program.
Asterisk file paths, configured in asterisk.conf.
const char * ast_config_AST_DATA_DIR
Definition: options.c:158
Core PBX routines and definitions.
void ast_str_substitute_variables_varshead(struct ast_str **buf, ssize_t maxlen, struct varshead *headp, const char *templ)
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
ast_phoneprov_std_variables
Definition: phoneprov.h:29
@ AST_PHONEPROV_STD_DST_END_MDAY
Definition: phoneprov.h:51
@ AST_PHONEPROV_STD_SERVER_IFACE
Definition: phoneprov.h:42
@ AST_PHONEPROV_STD_VAR_LIST_LENGTH
Definition: phoneprov.h:53
@ AST_PHONEPROV_STD_SERVER_PORT
Definition: phoneprov.h:41
@ AST_PHONEPROV_STD_DST_START_MDAY
Definition: phoneprov.h:48
@ AST_PHONEPROV_STD_MAC
Definition: phoneprov.h:30
@ AST_PHONEPROV_STD_CALLERID
Definition: phoneprov.h:36
@ AST_PHONEPROV_STD_DST_START_MONTH
Definition: phoneprov.h:47
@ AST_PHONEPROV_STD_TZOFFSET
Definition: phoneprov.h:45
@ AST_PHONEPROV_STD_USERNAME
Definition: phoneprov.h:32
@ AST_PHONEPROV_STD_DST_END_MONTH
Definition: phoneprov.h:50
@ AST_PHONEPROV_STD_LINENUMBER
Definition: phoneprov.h:38
@ AST_PHONEPROV_STD_TIMEZONE
Definition: phoneprov.h:37
@ AST_PHONEPROV_STD_SERVER
Definition: phoneprov.h:40
@ AST_PHONEPROV_STD_LABEL
Definition: phoneprov.h:35
@ AST_PHONEPROV_STD_PROFILE
Definition: phoneprov.h:31
@ AST_PHONEPROV_STD_EXTENSION_LENGTH
Definition: phoneprov.h:44
@ AST_PHONEPROV_STD_DST_END_HOUR
Definition: phoneprov.h:52
@ AST_PHONEPROV_STD_DST_ENABLE
Definition: phoneprov.h:46
@ AST_PHONEPROV_STD_DISPLAY_NAME
Definition: phoneprov.h:33
@ AST_PHONEPROV_STD_SECRET
Definition: phoneprov.h:34
@ AST_PHONEPROV_STD_DST_START_HOUR
Definition: phoneprov.h:49
@ AST_PHONEPROV_STD_VOICEMAIL_EXTEN
Definition: phoneprov.h:43
@ AST_PHONEPROV_STD_LINEKEYS
Definition: phoneprov.h:39
int(* ast_phoneprov_load_users_cb)(void)
Causes the provider to load its users.
Definition: phoneprov.h:75
static char user[512]
#define MAX_USER_BUCKETS
Definition: res_phoneprov.c:83
#define FORMATS
static int load_file(const char *filename, char **ret)
Read a TEXT file into a string and return the length.
static void profile_destructor(void *obj)
#define MAX_ROUTE_BUCKETS
Definition: res_phoneprov.c:82
static struct user * build_user(const char *mac, struct phone_profile *profile, char *provider_name)
Build and return a user structure based on gathered config data.
#define MAX_PROFILE_BUCKETS
Definition: res_phoneprov.c:81
static void delete_providers(void)
Delete all providers.
static struct in_addr __ourip
for use in lookup_iface
static void provider_destructor(void *obj)
#define MAX_PROVIDER_BUCKETS
Definition: res_phoneprov.c:80
void ast_phoneprov_delete_extensions(char *provider_name)
Deletes all extensions for this provider.
void ast_phoneprov_provider_unregister(char *provider_name)
Unegisters a config provider from phoneprov and frees its resources.
static int pp_each_extension_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
static struct ast_custom_function pp_each_extension_function
static struct user * find_user(const char *macaddress)
Return a user looked up by name.
static int load_common(void)
const char * ast_phoneprov_std_variable_lookup(enum ast_phoneprov_std_variables var)
Returns the string respresentation of a phoneprov standard variable.
static struct ast_cli_entry pp_cli[]
static struct phone_profile * find_profile(const char *name)
Return a phone profile looked up by name.
static struct ast_custom_function pp_each_user_function
static void set_timezone_variables(struct varshead *headp, const char *zone)
Set all timezone-related variables based on a zone (i.e. America/New_York)
#define SIMPLE_CMP_FN(fname, stype, field)
Creates a compare function for a structure string field.
int ast_phoneprov_add_extension(char *provider_name, struct varshead *vars)
Adds an extension.
static struct phoneprov_provider * find_provider(char *name)
static int build_user_routes(struct user *user)
Add an http route for dynamic files attached to the profile of the user.
static int pp_each_user_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
static const char * pp_general_lookup[]
static void user_destructor(void *obj)
Free all memory associated with a user.
static int route_list_cb(void *obj, void *arg, void *data, int flags)
static int pp_each_extension_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
static int routes_delete_cb(void *obj, void *arg, int flags)
static int extensions_delete_cb(void *obj, void *arg, int flags)
struct ao2_container * users
static const char * pp_user_lookup[]
static struct phone_profile * unref_profile(struct phone_profile *prof)
struct ao2_container * http_routes
static int lookup_iface(const char *iface, struct in_addr *address)
static int pp_each_user_helper(struct ast_channel *chan, char *data, char *buf, struct ast_str **bufstr, int len)
A dialplan function that can be used to print a string for each phoneprov user.
#define FORMATD
static const char * variable_lookup[]
static void delete_profiles(void)
Delete all phone profiles, freeing their memory.
static struct user * unref_user(struct user *user)
#define SIMPLE_HASH_FN(fname, stype, field)
Creates a hash function for a structure string field.
static void delete_file(struct phoneprov_file *file)
static struct extension * delete_extension(struct extension *exten)
static struct ast_http_uri phoneprovuri
struct ao2_container * profiles
static void delete_routes(void)
Delete all http routes, freeing their memory.
static int pp_each_user_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
static int extension_delete_cb(void *obj, void *arg, void *data, int flags)
static struct varshead * get_defaults(void)
void ast_phoneprov_delete_extension(char *provider_name, char *macaddress)
Deletes an extension.
static int load_module(void)
Load the module.
static void delete_users(void)
Delete all users.
static int load_users(void)
#define SIPUSERS_PROVIDER_NAME
static int unload_module(void)
static int reload(void)
static int add_user_extension(struct user *user, struct extension *exten)
Add an extension to a user ordered by index/linenumber.
static int phoneprov_callback(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers)
Callback that is executed everytime an http request is received by this module.
struct ao2_container * providers
static struct extension * build_extension(const char *name, struct varshead *vars)
static char * handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
CLI command to list static and dynamic routes.
static void route_destructor(void *obj)
static struct http_route * unref_route(struct http_route *route)
static void build_profile(const char *name, struct ast_variable *v)
Build a phone profile and add it to the list of phone profiles.
int ast_phoneprov_provider_register(char *provider_name, ast_phoneprov_load_users_cb load_users)
Registers a config provider to phoneprov.
static int pp_each_extension_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, int len)
A dialplan function that can be used to output a template for each extension attached to a user.
static void build_route(struct phoneprov_file *pp_file, struct phone_profile *profile, struct user *user, char *uri)
Build a route structure and add it to the list of available http routes.
const char * method
Definition: res_pjsip.c:1279
#define NULL
Definition: resample.c:96
#define AST_DECLARE_STRING_FIELDS(field_list)
Declare the fields needed in a structure.
Definition: stringfields.h:341
#define ast_calloc_with_stringfields(n, type, size)
Allocate a structure with embedded stringfields in a single allocation.
Definition: stringfields.h:432
#define AST_STRING_FIELD(name)
Declare a string field.
Definition: stringfields.h:303
#define ast_string_field_set(x, field, data)
Set a field to a simple string value.
Definition: stringfields.h:521
#define ast_string_field_init(x, size)
Initialize a field pool and fields.
Definition: stringfields.h:359
#define ast_string_field_build(x, field, fmt, args...)
Set a field to a complex (built) value.
Definition: stringfields.h:555
#define ast_string_field_free_memory(x)
free all memory - to be called before destroying the object
Definition: stringfields.h:374
String manipulation functions.
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
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
#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
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: utils.c:2199
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
int ast_build_string(char **buffer, size_t *space, const char *fmt,...)
Build a string in a buffer, designed to be called repeatedly.
Definition: utils.c:2167
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
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
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
char * ast_strip(char *s)
Strip leading/trailing whitespace from a string.
Definition: strings.h:223
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
Main Channel structure associated with a channel.
descriptor for a cli entry.
Definition: cli.h:171
char * command
Definition: cli.h:186
const char * usage
Definition: cli.h:177
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
const char * name
Definition: pbx.h:119
Structure used to handle boolean flags.
Definition: utils.h:199
Definition of a URI handler.
Definition: http.h:102
ast_http_callback callback
Definition: http.h:107
Support for dynamic strings.
Definition: strings.h:623
describes a server instance
Definition: tcptls.h:150
struct ast_iostream * stream
Definition: tcptls.h:161
int tm_mday
Definition: localtime.h:39
int tm_hour
Definition: localtime.h:38
int tm_mon
Definition: localtime.h:40
char name[0]
Definition: chanvars.h:31
char * value
Definition: chanvars.h:30
struct ast_var_t::@211 entries
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
Definition: search.h:40
structure to hold extensions
struct extension::@446 entry
struct varshead * headp
const ast_string_field name
structure to hold http routes (valid URIs, and the files they link to)
const ast_string_field uri
struct phone_profile * profile
struct phoneprov_file * file
struct user * user
structure to hold phone profiles read from phoneprov.conf
struct phone_profile::@448 dynamic_files
const ast_string_field staticdir
const ast_string_field default_mime_type
struct varshead * headp
const ast_string_field name
struct phone_profile::@447 static_files
structure to hold file data
const ast_string_field mime_type
struct phoneprov_file::@445 entry
const ast_string_field template
const ast_string_field format
structure to hold config providers
ast_phoneprov_load_users_cb load_users
const ast_string_field provider_name
structure to hold users read from users.conf
const ast_string_field macaddress
struct phone_profile * profile
struct user::@449 extensions
const ast_string_field provider_name
list of users found in the config file
int value
Definition: syslog.c:37
const char * args
static struct test_val a
int error(const char *format,...)
Definition: utils/frame.c:999
Utility functions.
#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 ARRAY_LEN(a)
Definition: utils.h:666