Asterisk - The Open Source Telephony Project GIT-master-754dea3
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
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 <since>
91 <version>1.6.1.0</version>
92 </since>
93 <synopsis>
94 Execute specified template for each extension.
95 </synopsis>
96 <syntax>
97 <parameter name="mac" required="true" />
98 <parameter name="template_file" required="true" />
99 </syntax>
100 <description>
101 <para>Output the specified template for each extension associated with the specified MAC address.</para>
102 </description>
103 </function>
104 <function name="PP_EACH_USER" language="en_US">
105 <since>
106 <version>1.6.0</version>
107 </since>
108 <synopsis>
109 Generate a string for each phoneprov user.
110 </synopsis>
111 <syntax>
112 <parameter name="string" required="true" />
113 <parameter name="exclude_mac" required="true" />
114 </syntax>
115 <description>
116 <para>Pass in a string, with phoneprov variables you want substituted in the format of
117 %{VARNAME}, and you will get the string rendered for each user in phoneprov
118 excluding ones with MAC address <replaceable>exclude_mac</replaceable>. Probably not
119 useful outside of res_phoneprov.</para>
120 <para>Example: ${PP_EACH_USER(&lt;item&gt;&lt;fn&gt;%{DISPLAY_NAME}&lt;/fn&gt;&lt;/item&gt;|${MAC})</para>
121 </description>
122 </function>
123 ***/
124
125/*!
126 * \brief Creates a hash function for a structure string field.
127 * \param fname The name to use for the function
128 * \param stype The structure type
129 * \param field The field in the structure to hash
130 *
131 * SIMPLE_HASH_FN(mystruct, myfield) will produce a function
132 * named mystruct_hash_fn which hashes mystruct->myfield.
133 */
134#define SIMPLE_HASH_FN(fname, stype, field) \
135static int fname(const void *obj, const int flags) \
136{ \
137 const struct stype *provider = obj; \
138 const char *key; \
139 switch (flags & OBJ_SEARCH_MASK) { \
140 case OBJ_SEARCH_KEY: \
141 key = obj; \
142 break; \
143 case OBJ_SEARCH_OBJECT: \
144 provider = obj; \
145 key = provider->field; \
146 break; \
147 default: \
148 ast_assert(0); \
149 return 0; \
150 } \
151 return ast_str_hash(key); \
152}
153
154/*!
155 * \brief Creates a compare function for a structure string field.
156 * \param fname The name to use for the function
157 * \param stype The structure type
158 * \param field The field in the structure to compare
159 *
160 * SIMPLE_CMP_FN(mystruct, myfield) will produce a function
161 * named mystruct_cmp_fn which compares mystruct->myfield.
162 */
163#define SIMPLE_CMP_FN(fname, stype, field) \
164static int fname(void *obj, void *arg, int flags) \
165{ \
166 const struct stype *object_left = obj, *object_right = arg; \
167 const char *right_key = arg; \
168 int cmp; \
169 switch (flags & OBJ_SEARCH_MASK) { \
170 case OBJ_SEARCH_OBJECT: \
171 right_key = object_right->field; \
172 case OBJ_SEARCH_KEY: \
173 cmp = strcmp(object_left->field, right_key); \
174 break; \
175 case OBJ_SEARCH_PARTIAL_KEY: \
176 cmp = strncmp(object_left->field, right_key, strlen(right_key)); \
177 break; \
178 default: \
179 cmp = 0; \
180 break; \
181 } \
182 if (cmp) { \
183 return 0; \
184 } \
185 return CMP_MATCH; \
186}
187
188static const char *variable_lookup[] = {
189 [AST_PHONEPROV_STD_MAC] = "MAC",
190 [AST_PHONEPROV_STD_PROFILE] = "PROFILE",
191 [AST_PHONEPROV_STD_USERNAME] = "USERNAME",
192 [AST_PHONEPROV_STD_DISPLAY_NAME] = "DISPLAY_NAME",
193 [AST_PHONEPROV_STD_SECRET] = "SECRET",
194 [AST_PHONEPROV_STD_LABEL] = "LABEL",
195 [AST_PHONEPROV_STD_CALLERID] = "CALLERID",
196 [AST_PHONEPROV_STD_TIMEZONE] = "TIMEZONE",
198 [AST_PHONEPROV_STD_LINEKEYS] = "LINEKEYS",
199 [AST_PHONEPROV_STD_SERVER] = "SERVER",
200 [AST_PHONEPROV_STD_SERVER_PORT] = "SERVER_PORT",
201 [AST_PHONEPROV_STD_SERVER_IFACE] = "SERVER_IFACE",
202 [AST_PHONEPROV_STD_VOICEMAIL_EXTEN] = "VOICEMAIL_EXTEN",
203 [AST_PHONEPROV_STD_EXTENSION_LENGTH] = "EXTENSION_LENGTH",
204 [AST_PHONEPROV_STD_TZOFFSET] = "TZOFFSET",
205 [AST_PHONEPROV_STD_DST_ENABLE] = "DST_ENABLE",
206 [AST_PHONEPROV_STD_DST_START_MONTH] = "DST_START_MONTH",
207 [AST_PHONEPROV_STD_DST_START_MDAY] = "DST_START_MDAY",
208 [AST_PHONEPROV_STD_DST_START_HOUR] = "DST_START_HOUR",
209 [AST_PHONEPROV_STD_DST_END_MONTH] = "DST_END_MONTH",
210 [AST_PHONEPROV_STD_DST_END_MDAY] = "DST_END_MDAY",
211 [AST_PHONEPROV_STD_DST_END_HOUR] = "DST_END_HOUR",
212};
213
214/* Translate the standard variables to their users.conf equivalents. */
215static const char *pp_user_lookup[] = {
216 [AST_PHONEPROV_STD_MAC] = "macaddress",
217 [AST_PHONEPROV_STD_PROFILE] = "profile",
218 [AST_PHONEPROV_STD_USERNAME] = "username",
219 [AST_PHONEPROV_STD_DISPLAY_NAME] = "fullname",
220 [AST_PHONEPROV_STD_SECRET] = "secret",
221 [AST_PHONEPROV_STD_LABEL] = "label",
222 [AST_PHONEPROV_STD_CALLERID] = "cid_number",
223 [AST_PHONEPROV_STD_TIMEZONE] = "timezone",
224 [AST_PHONEPROV_STD_LINENUMBER] = "linenumber",
225 [AST_PHONEPROV_STD_LINEKEYS] = "linekeys",
230 [AST_PHONEPROV_STD_EXTENSION_LENGTH] = "localextenlength",
239};
240
241/* Translate the standard variables to their phoneprov.conf [general] equivalents. */
242static const char *pp_general_lookup[] = {
244 [AST_PHONEPROV_STD_PROFILE] = "default_profile",
253 [AST_PHONEPROV_STD_SERVER] = "serveraddr",
254 [AST_PHONEPROV_STD_SERVER_PORT] = "serverport",
255 [AST_PHONEPROV_STD_SERVER_IFACE] = "serveriface",
266};
267
268/*! \brief for use in lookup_iface */
269static struct in_addr __ourip = { .s_addr = 0x00000000, };
270
271/*! \brief structure to hold config providers */
275 );
277};
279SIMPLE_HASH_FN(phoneprov_provider_hash_fn, phoneprov_provider, provider_name)
280SIMPLE_CMP_FN(phoneprov_provider_cmp_fn, phoneprov_provider, provider_name)
281
282/*! \brief structure to hold file data */
285 AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */
286 AST_STRING_FIELD(template); /*!< Template/physical file location */
287 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
288 );
290};
291
292/*! \brief structure to hold extensions */
293struct extension {
296 );
297 int index;
298 struct varshead *headp; /*!< List of variables to substitute into templates */
300};
301
302/*! \brief structure to hold phone profiles read from phoneprov.conf */
305 AST_STRING_FIELD(name); /*!< Name of phone profile */
306 AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */
307 AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */
308 );
309 struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
310 AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */
311 AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */
312};
314SIMPLE_HASH_FN(phone_profile_hash_fn, phone_profile, name)
315SIMPLE_CMP_FN(phone_profile_cmp_fn, phone_profile, name)
316
317/*! \brief structure to hold users read from users.conf */
318struct user {
320 AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */
321 AST_STRING_FIELD(provider_name); /*!< Name of the provider who registered this mac */
322 );
323 struct phone_profile *profile; /*!< Profile the phone belongs to */
325};
327SIMPLE_HASH_FN(user_hash_fn, user, macaddress)
328SIMPLE_CMP_FN(user_cmp_fn, user, macaddress)
329
330/*! \brief structure to hold http routes (valid URIs, and the files they link to) */
333 AST_STRING_FIELD(uri); /*!< The URI requested */
334 );
335 struct phoneprov_file *file; /*!< The file that links to the URI */
336 struct user *user; /*!< The user that has variables to substitute into the file
337 * NULL in the case of a static route */
339};
341SIMPLE_HASH_FN(http_route_hash_fn, http_route, uri)
342SIMPLE_CMP_FN(http_route_cmp_fn, http_route, uri)
343
344#define SIPUSERS_PROVIDER_NAME "sipusers"
345
346/* iface is the interface (e.g. eth0); address is the return value */
347static int lookup_iface(const char *iface, struct in_addr *address)
348{
349 int mysock, res = 0;
350 struct ifreq ifr;
351 struct sockaddr_in *sin;
352
353 memset(&ifr, 0, sizeof(ifr));
354 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
355
356 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
357 if (mysock < 0) {
358 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
359 return -1;
360 }
361
362 res = ioctl(mysock, SIOCGIFADDR, &ifr);
363
364 close(mysock);
365
366 if (res < 0) {
367 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
368 memcpy(address, &__ourip, sizeof(__ourip));
369 return -1;
370 } else {
371 sin = (struct sockaddr_in *)&ifr.ifr_addr;
372 memcpy(address, &sin->sin_addr, sizeof(*address));
373 return 0;
374 }
375}
376
378{
380}
381
382/*! \brief Delete all providers */
383static void delete_providers(void)
384{
385 if (!providers) {
386 return;
387 }
388
390}
391
392static void provider_destructor(void *obj)
393{
394 struct phoneprov_provider *provider = obj;
396}
397
398static void delete_file(struct phoneprov_file *file)
399{
401 ast_free(file);
402}
403
404/*! \brief Read a TEXT file into a string and return the length */
405static int load_file(const char *filename, char **ret)
406{
407 int len = 0;
408 FILE *f;
409
410 if (!(f = fopen(filename, "r"))) {
411 *ret = NULL;
412 return -1;
413 }
414
415 fseek(f, 0, SEEK_END);
416 len = ftell(f);
417 fseek(f, 0, SEEK_SET);
418 if (!(*ret = ast_malloc(len + 1))) {
419 fclose(f);
420 return -2;
421 }
422
423 if (len != fread(*ret, sizeof(char), len, f)) {
424 fclose(f);
425 ast_free(*ret);
426 *ret = NULL;
427 return -3;
428 }
429
430 fclose(f);
431
432 (*ret)[len] = '\0';
433
434 return len;
435}
436
437/*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
438 \param headp pointer to list of user variables
439 \param zone A time zone. NULL sets variables based on timezone of the machine
440*/
441static void set_timezone_variables(struct varshead *headp, const char *zone)
442{
443 time_t utc_time;
444 int dstenable;
445 time_t dststart;
446 time_t dstend;
447 struct ast_tm tm_info;
448 int tzoffset;
449 char buffer[21];
450 struct timeval when;
451
452 time(&utc_time);
453 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
454 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
455 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("TZOFFSET", buffer));
456
457 if (!dstenable) {
458 return;
459 }
460
461 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_ENABLE", "1"));
462
463 when.tv_sec = dststart;
464 ast_localtime(&when, &tm_info, zone);
465
466 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
467 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_MONTH", buffer));
468
469 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
470 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_MDAY", buffer));
471
472 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
473 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_HOUR", buffer));
474
475 when.tv_sec = dstend;
476 ast_localtime(&when, &tm_info, zone);
477
478 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
479 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_MONTH", buffer));
480
481 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
482 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_MDAY", buffer));
483
484 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
485 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_HOUR", buffer));
486}
487
488static struct http_route *unref_route(struct http_route *route)
489{
490 ao2_cleanup(route);
491
492 return NULL;
493}
494
495static void route_destructor(void *obj)
496{
497 struct http_route *route = obj;
498
500}
501
502/*! \brief Delete all http routes, freeing their memory */
503static void delete_routes(void)
504{
505 if (!http_routes) {
506 return;
507 }
508
510}
511
512/*! \brief Build a route structure and add it to the list of available http routes
513 \param pp_file File to link to the route
514 \param profile
515 \param user User to link to the route (NULL means static route)
516 \param uri URI of the route
517*/
518static void build_route(struct phoneprov_file *pp_file, struct phone_profile *profile, struct user *user, char *uri)
519{
520 struct http_route *route;
521
522 if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
523 return;
524 }
525
526 if (ast_string_field_init(route, 32)) {
527 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
528 route = unref_route(route);
529 return;
530 }
531
532 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
533 route->user = user;
534 route->file = pp_file;
535 route->profile = profile;
536
537 ao2_link(http_routes, route);
538
539 route = unref_route(route);
540}
541
542static struct phone_profile *unref_profile(struct phone_profile *prof)
543{
544 ao2_cleanup(prof);
545
546 return NULL;
547}
548
549/*! \brief Return a phone profile looked up by name */
550static struct phone_profile *find_profile(const char *name)
551{
553}
554
555static void profile_destructor(void *obj)
556{
557 struct phone_profile *profile = obj;
558 struct phoneprov_file *file;
559 struct ast_var_t *var;
560
561 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry))) {
563 }
564
565 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry))) {
567 }
568
569 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries))) {
571 }
572
573 ast_free(profile->headp);
575}
576
577/*! \brief Delete all phone profiles, freeing their memory */
578static void delete_profiles(void)
579{
580 if (!profiles) {
581 return;
582 }
583
585}
586
587/*! \brief Build a phone profile and add it to the list of phone profiles
588 \param name the name of the profile
589 \param v ast_variable from parsing phoneprov.conf
590*/
591static void build_profile(const char *name, struct ast_variable *v)
592{
593 struct phone_profile *profile;
594
595 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
596 return;
597 }
598
599 if (ast_string_field_init(profile, 32)) {
600 profile = unref_profile(profile);
601 return;
602 }
603
604 if (!(profile->headp = ast_var_list_create())) {
605 profile = unref_profile(profile);
606 return;
607 }
608
611
612 ast_string_field_set(profile, name, name);
613 for (; v; v = v->next) {
614 if (!strcasecmp(v->name, "mime_type")) {
616 } else if (!strcasecmp(v->name, "setvar")) {
617 char value_copy[strlen(v->value) + 1];
618
620 AST_APP_ARG(varname);
621 AST_APP_ARG(varval);
622 );
623
624 strcpy(value_copy, v->value); /* safe */
625 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
626 do {
627 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
628 break;
629 args.varname = ast_strip(args.varname);
630 args.varval = ast_strip(args.varval);
631 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
632 break;
633 AST_VAR_LIST_INSERT_TAIL(profile->headp, ast_var_assign(args.varname, args.varval));
634 } while (0);
635 } else if (!strcasecmp(v->name, "staticdir")) {
637 } else {
638 struct phoneprov_file *pp_file;
639 char *file_extension;
640 char value_copy[strlen(v->value) + 1];
641
643 AST_APP_ARG(filename);
644 AST_APP_ARG(mimetype);
645 );
646
647 if (!(pp_file = ast_calloc_with_stringfields(1, struct phoneprov_file, 32))) {
648 profile = unref_profile(profile);
649 return;
650 }
651
652 if ((file_extension = strrchr(pp_file->format, '.')))
653 file_extension++;
654
655 strcpy(value_copy, v->value); /* safe */
656 AST_STANDARD_APP_ARGS(args, value_copy);
657
658 /* Mime type order of preference
659 * 1) Specific mime-type defined for file in profile
660 * 2) Mime determined by extension
661 * 3) Default mime type specified in profile
662 * 4) text/plain
663 */
664 ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype,
665 (S_OR(S_OR(ast_http_ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
666
667 if (!strcasecmp(v->name, "static_file")) {
668 ast_string_field_set(pp_file, format, args.filename);
669 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
670 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
671 /* Add a route for the static files, as their filenames won't change per-user */
672 build_route(pp_file, profile, NULL, NULL);
673 } else {
674 ast_string_field_set(pp_file, format, v->name);
675 ast_string_field_set(pp_file, template, args.filename);
676 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
677 }
678 }
679 }
680
681 ao2_link(profiles, profile);
682
683 profile = unref_profile(profile);
684}
685
686static struct extension *delete_extension(struct extension *exten)
687{
690 ast_free(exten);
691
692 return NULL;
693}
694
695static struct extension *build_extension(const char *name, struct varshead *vars)
696{
697 struct extension *exten;
698 const char *tmp;
699
700 if (!(exten = ast_calloc_with_stringfields(1, struct extension, 32))) {
701 return NULL;
702 }
703
705
706 exten->headp = ast_var_list_clone(vars);
707 if (!exten->headp) {
708 ast_log(LOG_ERROR, "Unable to clone variables for extension '%s'\n", name);
709 delete_extension(exten);
710 return NULL;
711 }
712
714 if (!tmp) {
717 exten->index = 1;
718 } else {
719 sscanf(tmp, "%d", &exten->index);
720 }
721
725 }
726
729
730 return exten;
731}
732
733static struct user *unref_user(struct user *user)
734{
736
737 return NULL;
738}
739
740/*! \brief Return a user looked up by name */
741static struct user *find_user(const char *macaddress)
742{
744}
745
746static int routes_delete_cb(void *obj, void *arg, int flags)
747{
748 struct http_route *route = obj;
749 struct user *user = route->user;
750 char *macaddress = arg;
751
752 if (user && !strcmp(user->macaddress, macaddress)) {
753 return CMP_MATCH;
754 }
755 return 0;
756}
757
758/*! \brief Free all memory associated with a user */
759static void user_destructor(void *obj)
760{
761 struct user *user = obj;
762 struct extension *exten;
763
764 while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
765 exten = delete_extension(exten);
766 }
767
768 if (user->profile) {
770 }
771
772 if (http_routes) {
774 }
775
777}
778
779/*! \brief Delete all users */
780static void delete_users(void)
781{
782 if (!users) {
783 return;
784 }
785
787}
788
789/*! \brief Build and return a user structure based on gathered config data */
790static struct user *build_user(const char *mac, struct phone_profile *profile, char *provider_name)
791{
792 struct user *user;
793
794 if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
795 return NULL;
796 }
797
798 if (ast_string_field_init(user, 64)) {
800 return NULL;
801 }
802
806 ao2_ref(profile, 1);
807
808 return user;
809}
810
811/*! \brief Add an extension to a user ordered by index/linenumber */
812static int add_user_extension(struct user *user, struct extension *exten)
813{
814 struct ast_var_t *pvar, *var2;
815 struct ast_str *str = ast_str_create(16);
816
817 if (!str) {
818 return -1;
819 }
820
821 /* Append profile variables here, and substitute variables on profile
822 * setvars, so that we can use user specific variables in them */
824 if (ast_var_find(exten->headp, pvar->name)) {
825 continue;
826 }
827
829 if ((var2 = ast_var_assign(pvar->name, ast_str_buffer(str)))) {
830 AST_VAR_LIST_INSERT_TAIL(exten->headp, var2);
831 }
832 }
833 ast_free(str);
834
836 AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
837 } else {
838 struct extension *exten_iter;
839
841 if (exten->index < exten_iter->index) {
843 } else if (exten->index == exten_iter->index) {
844 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
845 return -1;
846 } else if (!AST_LIST_NEXT(exten_iter, entry)) {
848 }
849 }
851 }
852
853 return 0;
854}
855
856/*! \brief Add an http route for dynamic files attached to the profile of the user */
857static int build_user_routes(struct user *user)
858{
859 struct phoneprov_file *pp_file;
860 struct ast_str *str;
861
862 if (!(str = ast_str_create(16))) {
863 return -1;
864 }
865
866 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
869 }
870
871 ast_free(str);
872 return 0;
873}
874
875/*! \brief Callback that is executed everytime an http request is received by this module */
876static 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)
877{
878 struct http_route *route;
879 struct ast_str *result;
880 char path[PATH_MAX];
881 char *file = NULL;
882 char *server;
883 char *newserver = NULL;
884 struct extension *exten_iter;
885 int len;
886 int fd;
887 struct ast_str *http_header;
888
890 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
891 return 0;
892 }
893
894 if (!(route = ao2_find(http_routes, uri, OBJ_SEARCH_KEY))) {
895 goto out404;
896 }
897
898 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
899
900 if (!route->user) { /* Static file */
901
902 fd = open(path, O_RDONLY);
903 if (fd < 0) {
904 goto out500;
905 }
906
907 len = lseek(fd, 0, SEEK_END);
908 lseek(fd, 0, SEEK_SET);
909 if (len < 0) {
910 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
911 close(fd);
912 goto out500;
913 }
914
915 http_header = ast_str_create(80);
916 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
917 route->file->mime_type);
918
919 ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
920
921 close(fd);
922 route = unref_route(route);
923 return 0;
924 } else { /* Dynamic file */
925 struct ast_str *tmp;
926
927 len = load_file(path, &file);
928 if (len < 0) {
929 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
930 if (file) {
931 ast_free(file);
932 }
933
934 goto out500;
935 }
936
937 if (!file) {
938 goto out500;
939 }
940
941 if (!(tmp = ast_str_create(len))) {
942 if (file) {
943 ast_free(file);
944 }
945
946 goto out500;
947 }
948
949 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
950 * the IP address we are listening on that the phone contacted for this config file */
951
952 server = ast_var_find(AST_LIST_FIRST(&route->user->extensions)->headp,
954
955 if (!server) {
956 union {
957 struct sockaddr sa;
958 struct sockaddr_in sa_in;
959 } name;
960 socklen_t namelen = sizeof(name.sa);
961 int res;
962
963 if ((res = getsockname(ast_iostream_get_fd(ser->stream), &name.sa, &namelen))) {
964 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
965 } else {
966 newserver = ast_strdupa(ast_inet_ntoa(name.sa_in.sin_addr));
967
968 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
971 }
972 }
973 }
974
976
977 /* Do not retain dynamic SERVER address because next request from the phone might arrive on
978 * different interface IP address eg. if this is a multi-homed server on multiple subnets */
979 if (newserver) {
980 struct ast_var_t *varns;
981 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
982 AST_LIST_TRAVERSE_SAFE_BEGIN(exten_iter->headp, varns, entries) {
985 ast_var_delete(varns);
986 }
987 }
989 }
990 }
991
992 ast_free(file);
993
994 http_header = ast_str_create(80);
995 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
996 route->file->mime_type);
997
998 if (!(result = ast_str_create(512))) {
999 ast_log(LOG_ERROR, "Could not create result string!\n");
1000 if (tmp) {
1001 ast_free(tmp);
1002 }
1003 ast_free(http_header);
1004 goto out500;
1005 }
1006 ast_str_append(&result, 0, "%s", ast_str_buffer(tmp));
1007
1008 ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
1009 ast_free(tmp);
1010
1011 route = unref_route(route);
1012
1013 return 0;
1014 }
1015
1016out404:
1017 ast_http_error(ser, 404, "Not Found", uri);
1018 return 0;
1019
1020out500:
1021 route = unref_route(route);
1022 ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
1023 return 0;
1024}
1025
1026/*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1027static int pp_each_user_helper(struct ast_channel *chan, char *data, char *buf, struct ast_str **bufstr, int len)
1028{
1029 char *tmp;
1030 struct ao2_iterator i;
1031 struct user *user;
1032 struct ast_str *str;
1034 AST_APP_ARG(string);
1035 AST_APP_ARG(exclude_mac);
1036 );
1038
1039 if (!(str = ast_str_create(16))) {
1040 return -1;
1041 }
1042
1043 /* Fix data by turning %{ into ${ */
1044 while ((tmp = strstr(args.string, "%{")))
1045 *tmp = '$';
1046
1047 i = ao2_iterator_init(users, 0);
1048 while ((user = ao2_iterator_next(&i))) {
1049 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1050 continue;
1051 }
1053 if (buf) {
1054 size_t slen = len;
1055 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1056 } else {
1057 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1058 }
1059 user = unref_user(user);
1060 }
1062
1063 ast_free(str);
1064 return 0;
1065}
1066
1067static int pp_each_user_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1068{
1069 return pp_each_user_helper(chan, data, buf, NULL, len);
1070}
1071
1072static int pp_each_user_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1073{
1074 return pp_each_user_helper(chan, data, NULL, buf, len);
1075}
1076
1078 .name = "PP_EACH_USER",
1079 .read = pp_each_user_read,
1080 .read2 = pp_each_user_read2,
1081};
1082
1083/*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1084static int pp_each_extension_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, int len)
1085{
1086 struct user *user;
1087 struct extension *exten;
1088 char path[PATH_MAX];
1089 char *file;
1090 int filelen;
1091 struct ast_str *str;
1093 AST_APP_ARG(mac);
1094 AST_APP_ARG(template);
1095 );
1096
1098
1099 if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1100 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requires both a macaddress and template filename.\n");
1101 return 0;
1102 }
1103
1104 if (!(user = find_user(args.mac))) {
1105 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1106 return 0;
1107 }
1108
1109 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1110 filelen = load_file(path, &file);
1111 if (filelen < 0) {
1112 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1113 if (file) {
1114 ast_free(file);
1115 }
1116 return 0;
1117 }
1118
1119 if (!file) {
1120 return 0;
1121 }
1122
1123 if (!(str = ast_str_create(filelen))) {
1124 return 0;
1125 }
1126
1127 AST_LIST_TRAVERSE(&user->extensions, exten, entry) {
1129 if (buf) {
1130 size_t slen = len;
1131 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1132 } else {
1133 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1134 }
1135 }
1136
1137 ast_free(file);
1138 ast_free(str);
1139
1140 user = unref_user(user);
1141
1142 return 0;
1143}
1144
1145static int pp_each_extension_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1146{
1147 return pp_each_extension_helper(chan, cmd, data, buf, NULL, len);
1148}
1149
1150static int pp_each_extension_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1151{
1152 return pp_each_extension_helper(chan, cmd, data, NULL, buf, len);
1153}
1154
1156 .name = "PP_EACH_EXTENSION",
1157 .read = pp_each_extension_read,
1158 .read2 = pp_each_extension_read2,
1159};
1160
1161#define FORMATS "%-20.20s %-40.40s %-30.30s\n"
1162#define FORMATD "%-20.20s %-20.20s %-40.40s %-30.30s\n"
1163static int route_list_cb(void *obj, void *arg, void *data, int flags)
1164{
1165 int fd = *(int *)arg;
1166 struct http_route *route = obj;
1167
1168 if (data && route->user) {
1169 ast_cli(fd, FORMATD, route->user->provider_name, route->profile->name, route->uri, route->file->template);
1170 }
1171 if (!data && !route->user) {
1172 ast_cli(fd, FORMATS, route->profile->name, route->uri, route->file->template);
1173 }
1174
1175 return CMP_MATCH;
1176}
1177
1178/*! \brief CLI command to list static and dynamic routes */
1179static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1180{
1181 int fd = a->fd;
1182 switch(cmd) {
1183 case CLI_INIT:
1184 e->command = "phoneprov show routes";
1185 e->usage =
1186 "Usage: phoneprov show routes\n"
1187 " Lists all registered phoneprov http routes.\n";
1188 return NULL;
1189 case CLI_GENERATE:
1190 return NULL;
1191 }
1192
1193 /* This currently iterates over routes twice, but it is the only place I've needed
1194 * to really separate static an dynamic routes, so I've just left it this way. */
1195 ast_cli(a->fd, "Static routes\n\n");
1196 ast_cli(a->fd, FORMATS, "Profile", "Relative URI", "Physical location");
1197
1199
1200 ast_cli(a->fd, "\nDynamic routes\n\n");
1201 ast_cli(a->fd, FORMATD, "Provider", "Profile", "Relative URI", "Template");
1202
1204
1205 return CLI_SUCCESS;
1206}
1207
1208static struct ast_cli_entry pp_cli[] = {
1209 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1210};
1211
1214 .description = "Asterisk HTTP Phone Provisioning Tool",
1215 .uri = "phoneprov",
1216 .has_subtree = 1,
1217 .data = NULL,
1218 .key = __FILE__,
1219};
1220
1221static struct varshead *get_defaults(void)
1222{
1223 struct ast_config *phoneprov_cfg, *cfg = CONFIG_STATUS_FILEINVALID;
1224 const char *value;
1225 struct ast_variable *v;
1226 struct ast_var_t *var;
1227 struct ast_flags config_flags = { 0 };
1228 struct varshead *defaults = ast_var_list_create();
1229
1230 if (!defaults) {
1231 ast_log(LOG_ERROR, "Unable to create default var list.\n");
1232 return NULL;
1233 }
1234
1235 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
1236 || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
1237 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
1238 ast_var_list_destroy(defaults);
1239 return NULL;
1240 }
1241
1243 if (!value) {
1244 struct in_addr addr;
1246 if (value) {
1247 lookup_iface(value, &addr);
1248 value = ast_inet_ntoa(addr);
1249 }
1250 }
1251 if (value) {
1253 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1254 } else {
1255 ast_log(LOG_WARNING, "Unable to find a valid server address or name.\n");
1256 }
1257
1260 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
1261 ast_config_destroy(cfg);
1262 }
1263 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1264
1266 if (!value) {
1267 ast_log(LOG_ERROR, "Unable to load default profile.\n");
1268 ast_config_destroy(phoneprov_cfg);
1269 ast_var_list_destroy(defaults);
1270 return NULL;
1271 }
1273 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1274 ast_config_destroy(phoneprov_cfg);
1275
1276 if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
1277 ast_log(LOG_ERROR, "Unable to load users.conf\n");
1278 ast_var_list_destroy(defaults);
1279 return NULL;
1280 }
1281
1282 /* Go ahead and load global variables from users.conf so we can append to profiles */
1283 for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
1286 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1287 }
1290 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1291 }
1292 }
1293 ast_config_destroy(cfg);
1294
1295 return defaults;
1296}
1297
1298static int load_users(void)
1299{
1300 struct ast_config *cfg;
1301 char *cat;
1302 const char *value;
1303 struct ast_flags config_flags = { 0 };
1304 struct varshead *defaults = get_defaults();
1305
1306 if (!defaults) {
1307 ast_log(LOG_WARNING, "Unable to load default variables.\n");
1308 return -1;
1309 }
1310
1311 if (!(cfg = ast_config_load("users.conf", config_flags))
1312 || cfg == CONFIG_STATUS_FILEINVALID) {
1313 ast_log(LOG_WARNING, "Unable to load users.conf\n");
1314 ast_var_list_destroy(defaults);
1315 return -1;
1316 }
1317
1318 cat = NULL;
1319 while ((cat = ast_category_browse(cfg, cat))) {
1320 const char *tmp;
1321 int i;
1322 struct ast_var_t *varx;
1323 struct ast_var_t *vard;
1324
1325 if (strcasecmp(cat, "general") && strcasecmp(cat, "authentication")) {
1326 struct varshead *variables = ast_var_list_create();
1327
1328 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp))) {
1329 ast_var_list_destroy(variables);
1330 continue;
1331 }
1332
1333 /* Transfer the standard variables */
1334 for (i = 0; i < AST_PHONEPROV_STD_VAR_LIST_LENGTH; i++) {
1335 if (pp_user_lookup[i]) {
1337 if (value) {
1339 value);
1340 AST_VAR_LIST_INSERT_TAIL(variables, varx);
1341 }
1342 }
1343 }
1344
1346 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
1347 ast_var_list_destroy(variables);
1348 continue;
1349 }
1350
1351 /* Apply defaults */
1352 AST_VAR_LIST_TRAVERSE(defaults, vard) {
1353 if (ast_var_find(variables, vard->name)) {
1354 continue;
1355 }
1356 varx = ast_var_assign(vard->name, vard->value);
1357 AST_VAR_LIST_INSERT_TAIL(variables, varx);
1358 }
1359
1361 }
1362 }
1363 ast_config_destroy(cfg);
1364 ast_var_list_destroy(defaults);
1365 return 0;
1366}
1367
1368static int load_common(void)
1369{
1370 struct ast_config *phoneprov_cfg;
1371 struct ast_flags config_flags = { 0 };
1372 char *cat;
1373
1374 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
1375 || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
1376 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
1377 return -1;
1378 }
1379
1380 cat = NULL;
1381 while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
1382 if (!strcasecmp(cat, "general")) {
1383 continue;
1384 }
1385 build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
1386 }
1387 ast_config_destroy(phoneprov_cfg);
1388
1390 ast_log(LOG_ERROR, "There are no provisioning profiles in phoneprov.conf.\n");
1391 return -1;
1392 }
1393
1394 return 0;
1395}
1396
1397static int unload_module(void)
1398{
1403
1404 /* This cleans up the users.conf provider (called specifically for clarity) */
1406
1407 /* This cleans up the framework which also cleans up the providers. */
1410 profiles = NULL;
1411 delete_routes();
1412 delete_users();
1414 http_routes = NULL;
1416 users = NULL;
1419 providers = NULL;
1420
1421 return 0;
1422}
1423
1424/*!
1425 * \brief Load the module
1426 *
1427 * Module loading including tests for configuration or dependencies.
1428 * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
1429 * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
1430 * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
1431 * configuration file or other non-critical problem return
1432 * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
1433 */
1434static int load_module(void)
1435{
1437 phone_profile_hash_fn, NULL, phone_profile_cmp_fn);
1438 if (!profiles) {
1439 ast_log(LOG_ERROR, "Unable to allocate profiles container.\n");
1441 }
1442
1444 http_route_hash_fn, NULL, http_route_cmp_fn);
1445 if (!http_routes) {
1446 ast_log(LOG_ERROR, "Unable to allocate routes container.\n");
1447 goto error;
1448 }
1449
1450 if (load_common()) {
1451 ast_log(LOG_ERROR, "Unable to load provisioning profiles.\n");
1452 goto error;
1453 }
1454
1456 user_hash_fn, NULL, user_cmp_fn);
1457 if (!users) {
1458 ast_log(LOG_ERROR, "Unable to allocate users container.\n");
1459 goto error;
1460 }
1461
1463 MAX_PROVIDER_BUCKETS, phoneprov_provider_hash_fn, NULL, phoneprov_provider_cmp_fn);
1464 if (!providers) {
1465 ast_log(LOG_ERROR, "Unable to allocate providers container.\n");
1466 goto error;
1467 }
1468
1469 /* Register ourselves as the provider for users.conf */
1471 ast_log(LOG_WARNING, "Unable register users config provider. Others may succeed.\n");
1472 }
1473
1475
1479
1481
1482error:
1483 unload_module();
1485}
1486
1487static int reload(void)
1488{
1489 struct ao2_iterator i;
1491
1492 /* Clean everything except the providers */
1493 delete_routes();
1494 delete_users();
1496
1497 /* Reload the profiles */
1498 if (load_common()) {
1499 ast_log(LOG_ERROR, "Unable to reload provisioning profiles.\n");
1500 unload_module();
1502 }
1503
1504 /* For each provider, reload the users */
1507 for(; (provider = ao2_iterator_next(&i)); ao2_ref(provider, -1)) {
1508 if (provider->load_users()) {
1509 ast_log(LOG_ERROR, "Unable to load provider '%s' users. Reload aborted.\n", provider->provider_name);
1510 continue;
1511 }
1512 }
1515
1517}
1518
1520 .support_level = AST_MODULE_SUPPORT_EXTENDED,
1521 .load = load_module,
1522 .unload = unload_module,
1523 .reload = reload,
1524 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
1525 .requires = "http",
1527
1528/**** Public API for register/unregister, set defaults, and add extension. ****/
1529
1531{
1533 return NULL;
1534 }
1535
1536 return variable_lookup[var];
1537}
1538
1541{
1543
1545 ast_log(LOG_ERROR, "Provider name can't be empty.\n");
1546 return -1;
1547 }
1548
1549 if (!providers) {
1550 ast_log(LOG_WARNING, "Provider '%s' cannot be registered: res_phoneprov not loaded.\n", provider_name);
1551 return -1;
1552 }
1553
1555 if (provider) {
1556 ast_log(LOG_ERROR, "There is already a provider registered named '%s'.\n", provider_name);
1557 ao2_ref(provider, -1);
1558 return -1;
1559 }
1560
1562 if (!provider) {
1563 ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s'.\n", provider_name);
1564 return -1;
1565 }
1566
1568 ao2_ref(provider, -1);
1569 ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s' stringfields.\n", provider_name);
1570 return -1;
1571 }
1572
1574 provider->load_users = load_users;
1575
1577 ao2_ref(provider, -1);
1578
1579 if (provider->load_users()) {
1580 ast_log(LOG_ERROR, "Unable to load provider '%s' users. Register aborted.\n", provider_name);
1582 return -1;
1583 }
1584
1585 return 0;
1586}
1587
1588static int extensions_delete_cb(void *obj, void *arg, int flags)
1589{
1590 char *provider_name = arg;
1591 struct user *user = obj;
1592 if (strcmp(user->provider_name, provider_name)) {
1593 return 0;
1594 }
1595 return CMP_MATCH;
1596}
1597
1598static int extension_delete_cb(void *obj, void *arg, void *data, int flags)
1599{
1600 struct user *user = obj;
1601 char *provider_name = data;
1602 char *macaddress = arg;
1603
1604 if (!strcmp(user->provider_name, provider_name) && !strcasecmp(user->macaddress, macaddress)) {
1605 return CMP_MATCH;
1606 }
1607 return 0;
1608}
1609
1611{
1612 if (!users) {
1613 return;
1614 }
1615
1618}
1619
1621{
1622 if (!users) {
1623 return;
1624 }
1625
1627}
1628
1630{
1631 if (!providers) {
1632 return;
1633 }
1634
1637}
1638
1640{
1642 RAII_VAR(struct user *, user, NULL, ao2_cleanup);
1644 struct extension *exten;
1645 char *profile_name;
1646 char *mac;
1647 char *username;
1648
1649 if (ast_strlen_zero(provider_name)) {
1650 ast_log(LOG_ERROR, "Provider name can't be empty.\n");
1651 return -1;
1652 }
1653 if (!vars) {
1654 ast_log(LOG_ERROR, "Variable list can't be empty.\n");
1655 return -1;
1656 }
1657
1659 if (!username) {
1660 ast_log(LOG_ERROR, "Extension name can't be empty.\n");
1661 return -1;
1662 }
1663
1665 if (!mac) {
1666 ast_log(LOG_ERROR, "MAC Address can't be empty.\n");
1667 return -1;
1668 }
1669
1670 provider = find_provider(provider_name);
1671 if (!provider) {
1672 ast_log(LOG_ERROR, "Provider '%s' wasn't found in the registry.\n", provider_name);
1673 return -1;
1674 }
1675
1676 profile_name = ast_var_find(vars,
1678 if (!profile_name) {
1679 ast_log(LOG_ERROR, "No profile could be found for user '%s' - skipping.\n", username);
1680 return -1;
1681 }
1682 if (!(profile = find_profile(profile_name))) {
1683 ast_log(LOG_ERROR, "Could not look up profile '%s' - skipping.\n", profile_name);
1684 return -1;
1685 }
1686
1687 if (!(user = find_user(mac))) {
1688
1689 if (!(user = build_user(mac, profile, provider_name))) {
1690 ast_log(LOG_ERROR, "Could not create user for '%s' - skipping\n", mac);
1691 return -1;
1692 }
1693
1694 if (!(exten = build_extension(username, vars))) {
1695 ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
1696 return -1;
1697 }
1698
1699 if (add_user_extension(user, exten)) {
1700 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1701 exten = delete_extension(exten);
1702 return -1;
1703 }
1704
1705 if (build_user_routes(user)) {
1706 ast_log(LOG_WARNING, "Could not create http routes for '%s' - skipping\n", user->macaddress);
1707 return -1;
1708 }
1710
1711 } else {
1712 if (strcmp(provider_name, user->provider_name)) {
1713 ast_log(LOG_ERROR, "MAC address '%s' was already added by provider '%s' - skipping\n", user->macaddress, user->provider_name);
1714 return -1;
1715 }
1716
1717 if (!(exten = build_extension(username, vars))) {
1718 ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
1719 return -1;
1720 }
1721
1722 if (add_user_extension(user, exten)) {
1723 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1724 exten = delete_extension(exten);
1725 return -1;
1726 }
1727 }
1728
1729 return 0;
1730}
Access Control of various sorts.
const char * str
Definition: app_jack.c:150
#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 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:472
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:219
void ast_http_uri_unlink(struct ast_http_uri *urihandler)
Unregister a URI handler.
Definition: http.c:721
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:664
int ast_http_uri_link(struct ast_http_uri *urihandler)
Register a URI handler.
Definition: http.c:689
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:869
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:85
#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:331
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition: module.h:330
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODPRI_CHANNEL_DEPEND
Definition: module.h:340
@ AST_MODULE_SUPPORT_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:1559
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:151
struct ast_iostream * stream
Definition: tcptls.h:162
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::@213 entries
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
structure to hold extensions
struct extension::@449 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
const ast_string_field staticdir
struct phone_profile::@451 dynamic_files
const ast_string_field default_mime_type
struct phone_profile::@450 static_files
struct varshead * headp
const ast_string_field name
structure to hold file data
const ast_string_field mime_type
const ast_string_field template
struct phoneprov_file::@448 entry
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
const ast_string_field provider_name
struct user::@452 extensions
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