2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2008, Digium, Inc.
5 * Copyright (C) 2014, Fairview 5 Engineering, LLC
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>
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.
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.
25 * \brief Phone provisioning application for the asterisk internal http server
27 * \author Matthew Brooks <mbrooks@digium.com>
28 * \author Terry Wilson <twilson@digium.com>
29 * \author George Joseph <george.joseph@fairview5.com>
32 /*! \li \ref res_phoneprov.c uses the configuration file \ref phoneprov.conf and \ref users.conf and \ref sip.conf
33 * \addtogroup configuration_file Configuration Files
37 * \page phoneprov.conf phoneprov.conf
38 * \verbinclude phoneprov.conf.sample
42 <support_level>extended</support_level>
45 #define AST_API_MODULE
49 #include <sys/ioctl.h>
50 #include <sys/socket.h>
53 #include <sys/sockio.h>
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"
66 #include "asterisk/stringfields.h"
67 #include "asterisk/options.h"
68 #include "asterisk/config.h"
69 #include "asterisk/acl.h"
70 #include "asterisk/astobj2.h"
71 #include "asterisk/ast_version.h"
72 #include "asterisk/phoneprov.h"
75 #define MAX_PROVIDER_BUCKETS 1
76 #define MAX_PROFILE_BUCKETS 1
77 #define MAX_ROUTE_BUCKETS 1
78 #define MAX_USER_BUCKETS 1
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 */
86 #define VAR_BUF_SIZE 4096
89 <function name="PP_EACH_EXTENSION" language="en_US">
91 Execute specified template for each extension.
94 <parameter name="mac" required="true" />
95 <parameter name="template_file" required="true" />
98 <para>Output the specified template for each extension associated with the specified MAC address.</para>
101 <function name="PP_EACH_USER" language="en_US">
103 Generate a string for each phoneprov user.
106 <parameter name="string" required="true" />
107 <parameter name="exclude_mac" required="true" />
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(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})</para>
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
125 * SIMPLE_HASH_FN(mystruct, myfield) will produce a function
126 * named mystruct_hash_fn which hashes mystruct->myfield.
128 #define SIMPLE_HASH_FN(fname, stype, field) \
129 static int fname(const void *obj, const int flags) \
131 const struct stype *provider = obj; \
133 switch (flags & OBJ_SEARCH_MASK) { \
134 case OBJ_SEARCH_KEY: \
137 case OBJ_SEARCH_OBJECT: \
139 key = provider->field; \
145 return ast_str_hash(key); \
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
154 * SIMPLE_CMP_FN(mystruct, myfield) will produce a function
155 * named mystruct_cmp_fn which compares mystruct->myfield.
157 #define SIMPLE_CMP_FN(fname, stype, field) \
158 static int fname(void *obj, void *arg, int flags) \
160 const struct stype *object_left = obj, *object_right = arg; \
161 const char *right_key = arg; \
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); \
169 case OBJ_SEARCH_PARTIAL_KEY: \
170 cmp = strncmp(object_left->field, right_key, strlen(right_key)); \
182 static 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",
191 [AST_PHONEPROV_STD_LINENUMBER] = "LINE",
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",
208 /* Translate the standard variables to their users.conf equivalents. */
209 static 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",
220 [AST_PHONEPROV_STD_SERVER] = NULL,
221 [AST_PHONEPROV_STD_SERVER_PORT] = NULL,
222 [AST_PHONEPROV_STD_SERVER_IFACE] = NULL,
223 [AST_PHONEPROV_STD_VOICEMAIL_EXTEN] = "vmexten",
224 [AST_PHONEPROV_STD_EXTENSION_LENGTH] = "localextenlength",
225 [AST_PHONEPROV_STD_TZOFFSET] = NULL,
226 [AST_PHONEPROV_STD_DST_ENABLE] = NULL,
227 [AST_PHONEPROV_STD_DST_START_MONTH] = NULL,
228 [AST_PHONEPROV_STD_DST_START_MDAY] = NULL,
229 [AST_PHONEPROV_STD_DST_START_HOUR] = NULL,
230 [AST_PHONEPROV_STD_DST_END_MONTH] = NULL,
231 [AST_PHONEPROV_STD_DST_END_MDAY] = NULL,
232 [AST_PHONEPROV_STD_DST_END_HOUR] = NULL,
235 /* Translate the standard variables to their phoneprov.conf [general] equivalents. */
236 static const char *pp_general_lookup[] = {
237 [AST_PHONEPROV_STD_MAC] = NULL,
238 [AST_PHONEPROV_STD_PROFILE] = "default_profile",
239 [AST_PHONEPROV_STD_USERNAME] = NULL,
240 [AST_PHONEPROV_STD_DISPLAY_NAME] = NULL,
241 [AST_PHONEPROV_STD_SECRET] = NULL,
242 [AST_PHONEPROV_STD_LABEL] = NULL,
243 [AST_PHONEPROV_STD_CALLERID] = NULL,
244 [AST_PHONEPROV_STD_TIMEZONE] = NULL,
245 [AST_PHONEPROV_STD_LINENUMBER] = NULL,
246 [AST_PHONEPROV_STD_LINEKEYS] = NULL,
247 [AST_PHONEPROV_STD_SERVER] = "serveraddr",
248 [AST_PHONEPROV_STD_SERVER_PORT] = "serverport",
249 [AST_PHONEPROV_STD_SERVER_IFACE] = "serveriface",
250 [AST_PHONEPROV_STD_VOICEMAIL_EXTEN] = NULL,
251 [AST_PHONEPROV_STD_EXTENSION_LENGTH] = NULL,
252 [AST_PHONEPROV_STD_TZOFFSET] = NULL,
253 [AST_PHONEPROV_STD_DST_ENABLE] = NULL,
254 [AST_PHONEPROV_STD_DST_START_MONTH] = NULL,
255 [AST_PHONEPROV_STD_DST_START_MDAY] = NULL,
256 [AST_PHONEPROV_STD_DST_START_HOUR] = NULL,
257 [AST_PHONEPROV_STD_DST_END_MONTH] = NULL,
258 [AST_PHONEPROV_STD_DST_END_MDAY] = NULL,
259 [AST_PHONEPROV_STD_DST_END_HOUR] = NULL,
262 /*! \brief for use in lookup_iface */
263 static struct in_addr __ourip = { .s_addr = 0x00000000, };
265 /*! \brief structure to hold config providers */
266 struct phoneprov_provider {
267 AST_DECLARE_STRING_FIELDS(
268 AST_STRING_FIELD(provider_name);
270 ast_phoneprov_load_users_cb load_users;
272 struct ao2_container *providers;
273 SIMPLE_HASH_FN(phoneprov_provider_hash_fn, phoneprov_provider, provider_name)
274 SIMPLE_CMP_FN(phoneprov_provider_cmp_fn, phoneprov_provider, provider_name)
276 /*! \brief structure to hold file data */
277 struct phoneprov_file {
278 AST_DECLARE_STRING_FIELDS(
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 */
283 AST_LIST_ENTRY(phoneprov_file) entry;
286 /*! \brief structure to hold extensions */
288 AST_DECLARE_STRING_FIELDS(
289 AST_STRING_FIELD(name);
292 struct varshead *headp; /*!< List of variables to substitute into templates */
293 AST_LIST_ENTRY(extension) entry;
296 /*! \brief structure to hold phone profiles read from phoneprov.conf */
297 struct phone_profile {
298 AST_DECLARE_STRING_FIELDS(
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 */
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 */
307 struct ao2_container *profiles;
308 SIMPLE_HASH_FN(phone_profile_hash_fn, phone_profile, name)
309 SIMPLE_CMP_FN(phone_profile_cmp_fn, phone_profile, name)
311 /*! \brief structure to hold users read from users.conf */
313 AST_DECLARE_STRING_FIELDS(
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 */
317 struct phone_profile *profile; /*!< Profile the phone belongs to */
318 AST_LIST_HEAD_NOLOCK(, extension) extensions;
320 struct ao2_container *users;
321 SIMPLE_HASH_FN(user_hash_fn, user, macaddress)
322 SIMPLE_CMP_FN(user_cmp_fn, user, macaddress)
324 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
326 AST_DECLARE_STRING_FIELDS(
327 AST_STRING_FIELD(uri); /*!< The URI requested */
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 */
332 struct phone_profile *profile;
334 struct ao2_container *http_routes;
335 SIMPLE_HASH_FN(http_route_hash_fn, http_route, uri)
336 SIMPLE_CMP_FN(http_route_cmp_fn, http_route, uri)
338 #define SIPUSERS_PROVIDER_NAME "sipusers"
340 /* iface is the interface (e.g. eth0); address is the return value */
341 static int lookup_iface(const char *iface, struct in_addr *address)
345 struct sockaddr_in *sin;
347 memset(&ifr, 0, sizeof(ifr));
348 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
350 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
352 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
356 res = ioctl(mysock, SIOCGIFADDR, &ifr);
361 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
362 memcpy(address, &__ourip, sizeof(__ourip));
365 sin = (struct sockaddr_in *)&ifr.ifr_addr;
366 memcpy(address, &sin->sin_addr, sizeof(*address));
371 static struct phoneprov_provider *find_provider(char *name)
373 return ao2_find(providers, name, OBJ_SEARCH_KEY);
376 /*! \brief Delete all providers */
377 static void delete_providers(void)
383 ao2_callback(providers, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
386 static void provider_destructor(void *obj)
388 struct phoneprov_provider *provider = obj;
389 ast_string_field_free_memory(provider);
392 static void delete_file(struct phoneprov_file *file)
394 ast_string_field_free_memory(file);
398 /*! \brief Read a TEXT file into a string and return the length */
399 static int load_file(const char *filename, char **ret)
404 if (!(f = fopen(filename, "r"))) {
409 fseek(f, 0, SEEK_END);
411 fseek(f, 0, SEEK_SET);
412 if (!(*ret = ast_malloc(len + 1))) {
417 if (len != fread(*ret, sizeof(char), len, f)) {
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
435 static void set_timezone_variables(struct varshead *headp, const char *zone)
441 struct ast_tm tm_info;
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));
455 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_ENABLE", "1"));
457 when.tv_sec = dststart;
458 ast_localtime(&when, &tm_info, zone);
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));
463 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
464 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_MDAY", buffer));
466 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
467 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_START_HOUR", buffer));
469 when.tv_sec = dstend;
470 ast_localtime(&when, &tm_info, zone);
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));
475 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
476 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_MDAY", buffer));
478 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
479 AST_VAR_LIST_INSERT_TAIL(headp, ast_var_assign("DST_END_HOUR", buffer));
482 static struct http_route *unref_route(struct http_route *route)
489 static void route_destructor(void *obj)
491 struct http_route *route = obj;
493 ast_string_field_free_memory(route);
496 /*! \brief Delete all http routes, freeing their memory */
497 static void delete_routes(void)
503 ao2_callback(http_routes, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
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 user User to link to the route (NULL means static route)
509 \param uri URI of the route
511 static void build_route(struct phoneprov_file *pp_file, struct phone_profile *profile, struct user *user, char *uri)
513 struct http_route *route;
515 if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
519 if (ast_string_field_init(route, 32)) {
520 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
521 route = unref_route(route);
525 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
527 route->file = pp_file;
528 route->profile = profile;
530 ao2_link(http_routes, route);
532 route = unref_route(route);
535 static struct phone_profile *unref_profile(struct phone_profile *prof)
542 /*! \brief Return a phone profile looked up by name */
543 static struct phone_profile *find_profile(const char *name)
545 return ao2_find(profiles, name, OBJ_SEARCH_KEY);
548 static void profile_destructor(void *obj)
550 struct phone_profile *profile = obj;
551 struct phoneprov_file *file;
552 struct ast_var_t *var;
554 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry))) {
558 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry))) {
562 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries))) {
566 ast_free(profile->headp);
567 ast_string_field_free_memory(profile);
570 /*! \brief Delete all phone profiles, freeing their memory */
571 static void delete_profiles(void)
577 ao2_callback(profiles, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
580 /*! \brief Build a phone profile and add it to the list of phone profiles
581 \param name the name of the profile
582 \param v ast_variable from parsing phoneprov.conf
584 static void build_profile(const char *name, struct ast_variable *v)
586 struct phone_profile *profile;
588 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
592 if (ast_string_field_init(profile, 32)) {
593 profile = unref_profile(profile);
597 if (!(profile->headp = ast_var_list_create())) {
598 profile = unref_profile(profile);
602 AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
603 AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
605 ast_string_field_set(profile, name, name);
606 for (; v; v = v->next) {
607 if (!strcasecmp(v->name, "mime_type")) {
608 ast_string_field_set(profile, default_mime_type, v->value);
609 } else if (!strcasecmp(v->name, "setvar")) {
610 char value_copy[strlen(v->value) + 1];
612 AST_DECLARE_APP_ARGS(args,
613 AST_APP_ARG(varname);
617 strcpy(value_copy, v->value); /* safe */
618 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
620 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
622 args.varname = ast_strip(args.varname);
623 args.varval = ast_strip(args.varval);
624 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
626 AST_VAR_LIST_INSERT_TAIL(profile->headp, ast_var_assign(args.varname, args.varval));
628 } else if (!strcasecmp(v->name, "staticdir")) {
629 ast_string_field_set(profile, staticdir, v->value);
631 struct phoneprov_file *pp_file;
632 char *file_extension;
633 char value_copy[strlen(v->value) + 1];
635 AST_DECLARE_APP_ARGS(args,
636 AST_APP_ARG(filename);
637 AST_APP_ARG(mimetype);
640 if (!(pp_file = ast_calloc_with_stringfields(1, struct phoneprov_file, 32))) {
641 profile = unref_profile(profile);
645 if ((file_extension = strrchr(pp_file->format, '.')))
648 strcpy(value_copy, v->value); /* safe */
649 AST_STANDARD_APP_ARGS(args, value_copy);
651 /* Mime type order of preference
652 * 1) Specific mime-type defined for file in profile
653 * 2) Mime determined by extension
654 * 3) Default mime type specified in profile
657 ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype,
658 (S_OR(S_OR(ast_http_ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
660 if (!strcasecmp(v->name, "static_file")) {
661 ast_string_field_set(pp_file, format, args.filename);
662 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
663 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
664 /* Add a route for the static files, as their filenames won't change per-user */
665 build_route(pp_file, profile, NULL, NULL);
667 ast_string_field_set(pp_file, format, v->name);
668 ast_string_field_set(pp_file, template, args.filename);
669 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
674 ao2_link(profiles, profile);
676 profile = unref_profile(profile);
679 static struct extension *delete_extension(struct extension *exten)
681 ast_var_list_destroy(exten->headp);
682 ast_string_field_free_memory(exten);
688 static struct extension *build_extension(const char *name, struct varshead *vars)
690 struct extension *exten;
693 if (!(exten = ast_calloc_with_stringfields(1, struct extension, 32))) {
697 ast_string_field_set(exten, name, name);
699 exten->headp = ast_var_list_clone(vars);
701 ast_log(LOG_ERROR, "Unable to clone variables for extension '%s'\n", name);
702 delete_extension(exten);
706 tmp = ast_var_find(exten->headp, variable_lookup[AST_PHONEPROV_STD_LINENUMBER]);
708 AST_VAR_LIST_INSERT_TAIL(exten->headp,
709 ast_var_assign(variable_lookup[AST_PHONEPROV_STD_LINENUMBER], "1"));
712 sscanf(tmp, "%d", &exten->index);
715 if (!ast_var_find(exten->headp, variable_lookup[AST_PHONEPROV_STD_LINEKEYS])) {
716 AST_VAR_LIST_INSERT_TAIL(exten->headp,
717 ast_var_assign(variable_lookup[AST_PHONEPROV_STD_LINEKEYS], "1"));
720 set_timezone_variables(exten->headp,
721 ast_var_find(vars, variable_lookup[AST_PHONEPROV_STD_TIMEZONE]));
726 static struct user *unref_user(struct user *user)
733 /*! \brief Return a user looked up by name */
734 static struct user *find_user(const char *macaddress)
736 return ao2_find(users, macaddress, OBJ_SEARCH_KEY);
739 static int routes_delete_cb(void *obj, void *arg, int flags)
741 struct http_route *route = obj;
742 struct user *user = route->user;
743 char *macaddress = arg;
745 if (user && !strcmp(user->macaddress, macaddress)) {
751 /*! \brief Free all memory associated with a user */
752 static void user_destructor(void *obj)
754 struct user *user = obj;
755 struct extension *exten;
757 while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
758 exten = delete_extension(exten);
762 user->profile = unref_profile(user->profile);
766 ao2_callback(http_routes, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, routes_delete_cb, (void *)user->macaddress);
769 ast_string_field_free_memory(user);
772 /*! \brief Delete all users */
773 static void delete_users(void)
779 ao2_callback(users, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, NULL);
782 /*! \brief Build and return a user structure based on gathered config data */
783 static struct user *build_user(const char *mac, struct phone_profile *profile, char *provider_name)
787 if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
791 if (ast_string_field_init(user, 64)) {
792 user = unref_user(user);
796 ast_string_field_set(user, macaddress, mac);
797 ast_string_field_set(user, provider_name, provider_name);
798 user->profile = profile;
804 /*! \brief Add an extension to a user ordered by index/linenumber */
805 static int add_user_extension(struct user *user, struct extension *exten)
807 struct ast_var_t *pvar, *var2;
808 struct ast_str *str = ast_str_create(16);
814 /* Append profile variables here, and substitute variables on profile
815 * setvars, so that we can use user specific variables in them */
816 AST_VAR_LIST_TRAVERSE(user->profile->headp, pvar) {
817 if (ast_var_find(exten->headp, pvar->name)) {
821 ast_str_substitute_variables_varshead(&str, 0, exten->headp, pvar->value);
822 if ((var2 = ast_var_assign(pvar->name, ast_str_buffer(str)))) {
823 AST_VAR_LIST_INSERT_TAIL(exten->headp, var2);
828 if (AST_LIST_EMPTY(&user->extensions)) {
829 AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
831 struct extension *exten_iter;
833 AST_LIST_TRAVERSE_SAFE_BEGIN(&user->extensions, exten_iter, entry) {
834 if (exten->index < exten_iter->index) {
835 AST_LIST_INSERT_BEFORE_CURRENT(exten, entry);
836 } else if (exten->index == exten_iter->index) {
837 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
839 } else if (!AST_LIST_NEXT(exten_iter, entry)) {
840 AST_LIST_INSERT_TAIL(&user->extensions, exten, entry);
843 AST_LIST_TRAVERSE_SAFE_END;
849 /*! \brief Add an http route for dynamic files attached to the profile of the user */
850 static int build_user_routes(struct user *user)
852 struct phoneprov_file *pp_file;
855 if (!(str = ast_str_create(16))) {
859 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
860 ast_str_substitute_variables_varshead(&str, 0, AST_LIST_FIRST(&user->extensions)->headp, pp_file->format);
861 build_route(pp_file, user->profile, user, ast_str_buffer(str));
868 /*! \brief Callback that is executed everytime an http request is received by this module */
869 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)
871 struct http_route *route;
872 struct ast_str *result;
878 struct ast_str *http_header;
880 if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
881 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
885 if (!(route = ao2_find(http_routes, uri, OBJ_SEARCH_KEY))) {
889 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
891 if (!route->user) { /* Static file */
893 fd = open(path, O_RDONLY);
898 len = lseek(fd, 0, SEEK_END);
899 lseek(fd, 0, SEEK_SET);
901 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
906 http_header = ast_str_create(80);
907 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
908 route->file->mime_type);
910 ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
913 route = unref_route(route);
915 } else { /* Dynamic file */
918 len = load_file(path, &file);
920 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
932 if (!(tmp = ast_str_create(len))) {
940 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
941 * the IP address we are listening on that the phone contacted for this config file */
943 server = ast_var_find(AST_LIST_FIRST(&route->user->extensions)->headp,
944 variable_lookup[AST_PHONEPROV_STD_SERVER]);
949 struct sockaddr_in sa_in;
951 socklen_t namelen = sizeof(name.sa);
954 if ((res = getsockname(ast_iostream_get_fd(ser->stream), &name.sa, &namelen))) {
955 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
957 struct extension *exten_iter;
958 const char *newserver = ast_inet_ntoa(name.sa_in.sin_addr);
960 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
961 AST_VAR_LIST_INSERT_TAIL(exten_iter->headp,
962 ast_var_assign(variable_lookup[AST_PHONEPROV_STD_SERVER], newserver));
967 ast_str_substitute_variables_varshead(&tmp, 0, AST_LIST_FIRST(&route->user->extensions)->headp, file);
971 http_header = ast_str_create(80);
972 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
973 route->file->mime_type);
975 if (!(result = ast_str_create(512))) {
976 ast_log(LOG_ERROR, "Could not create result string!\n");
980 ast_free(http_header);
983 ast_str_append(&result, 0, "%s", ast_str_buffer(tmp));
985 ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
988 route = unref_route(route);
994 ast_http_error(ser, 404, "Not Found", uri);
998 route = unref_route(route);
999 ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
1003 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1004 static int pp_each_user_helper(struct ast_channel *chan, char *data, char *buf, struct ast_str **bufstr, int len)
1007 struct ao2_iterator i;
1009 struct ast_str *str;
1010 AST_DECLARE_APP_ARGS(args,
1011 AST_APP_ARG(string);
1012 AST_APP_ARG(exclude_mac);
1014 AST_STANDARD_APP_ARGS(args, data);
1016 if (!(str = ast_str_create(16))) {
1020 /* Fix data by turning %{ into ${ */
1021 while ((tmp = strstr(args.string, "%{")))
1024 i = ao2_iterator_init(users, 0);
1025 while ((user = ao2_iterator_next(&i))) {
1026 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1029 ast_str_substitute_variables_varshead(&str, len, AST_LIST_FIRST(&user->extensions)->headp, args.string);
1032 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1034 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1036 user = unref_user(user);
1038 ao2_iterator_destroy(&i);
1044 static int pp_each_user_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1046 return pp_each_user_helper(chan, data, buf, NULL, len);
1049 static int pp_each_user_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1051 return pp_each_user_helper(chan, data, NULL, buf, len);
1054 static struct ast_custom_function pp_each_user_function = {
1055 .name = "PP_EACH_USER",
1056 .read = pp_each_user_read,
1057 .read2 = pp_each_user_read2,
1060 /*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1061 static int pp_each_extension_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, int len)
1064 struct extension *exten;
1065 char path[PATH_MAX];
1068 struct ast_str *str;
1069 AST_DECLARE_APP_ARGS(args,
1071 AST_APP_ARG(template);
1074 AST_STANDARD_APP_ARGS(args, data);
1076 if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1077 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requries both a macaddress and template filename.\n");
1081 if (!(user = find_user(args.mac))) {
1082 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1086 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1087 filelen = load_file(path, &file);
1089 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1100 if (!(str = ast_str_create(filelen))) {
1104 AST_LIST_TRAVERSE(&user->extensions, exten, entry) {
1105 ast_str_substitute_variables_varshead(&str, 0, exten->headp, file);
1108 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1110 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1117 user = unref_user(user);
1122 static int pp_each_extension_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1124 return pp_each_extension_helper(chan, cmd, data, buf, NULL, len);
1127 static int pp_each_extension_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1129 return pp_each_extension_helper(chan, cmd, data, NULL, buf, len);
1132 static struct ast_custom_function pp_each_extension_function = {
1133 .name = "PP_EACH_EXTENSION",
1134 .read = pp_each_extension_read,
1135 .read2 = pp_each_extension_read2,
1138 #define FORMATS "%-20.20s %-40.40s %-30.30s\n"
1139 #define FORMATD "%-20.20s %-20.20s %-40.40s %-30.30s\n"
1140 static int route_list_cb(void *obj, void *arg, void *data, int flags)
1142 int fd = *(int *)arg;
1143 struct http_route *route = obj;
1145 if (data && route->user) {
1146 ast_cli(fd, FORMATD, route->user->provider_name, route->profile->name, route->uri, route->file->template);
1148 if (!data && !route->user) {
1149 ast_cli(fd, FORMATS, route->profile->name, route->uri, route->file->template);
1155 /*! \brief CLI command to list static and dynamic routes */
1156 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1161 e->command = "phoneprov show routes";
1163 "Usage: phoneprov show routes\n"
1164 " Lists all registered phoneprov http routes.\n";
1170 /* This currently iterates over routes twice, but it is the only place I've needed
1171 * to really separate static an dynamic routes, so I've just left it this way. */
1172 ast_cli(a->fd, "Static routes\n\n");
1173 ast_cli(a->fd, FORMATS, "Profile", "Relative URI", "Physical location");
1175 ao2_callback_data(http_routes, OBJ_NODATA | OBJ_MULTIPLE, route_list_cb, &fd, NULL);
1177 ast_cli(a->fd, "\nDynamic routes\n\n");
1178 ast_cli(a->fd, FORMATD, "Provider", "Profile", "Relative URI", "Template");
1180 ao2_callback_data(http_routes, OBJ_NODATA | OBJ_MULTIPLE, route_list_cb, &fd, (void *)1);
1185 static struct ast_cli_entry pp_cli[] = {
1186 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1189 static struct ast_http_uri phoneprovuri = {
1190 .callback = phoneprov_callback,
1191 .description = "Asterisk HTTP Phone Provisioning Tool",
1198 static struct varshead *get_defaults(void)
1200 struct ast_config *phoneprov_cfg, *cfg = CONFIG_STATUS_FILEINVALID;
1202 struct ast_variable *v;
1203 struct ast_var_t *var;
1204 struct ast_flags config_flags = { 0 };
1205 struct varshead *defaults = ast_var_list_create();
1208 ast_log(LOG_ERROR, "Unable to create default var list.\n");
1212 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
1213 || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
1214 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
1215 ast_var_list_destroy(defaults);
1219 value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_SERVER]);
1221 struct in_addr addr;
1222 value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_SERVER_IFACE]);
1224 lookup_iface(value, &addr);
1225 value = ast_inet_ntoa(addr);
1229 var = ast_var_assign(variable_lookup[AST_PHONEPROV_STD_SERVER], value);
1230 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1232 ast_log(LOG_WARNING, "Unable to find a valid server address or name.\n");
1235 value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_SERVER_PORT]);
1237 if ((cfg = ast_config_load("sip.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1238 value = ast_variable_retrieve(cfg, "general", "bindport");
1241 var = ast_var_assign(variable_lookup[AST_PHONEPROV_STD_SERVER_PORT], S_OR(value, "5060"));
1242 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
1243 ast_config_destroy(cfg);
1245 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1247 value = ast_variable_retrieve(phoneprov_cfg, "general", pp_general_lookup[AST_PHONEPROV_STD_PROFILE]);
1249 ast_log(LOG_ERROR, "Unable to load default profile.\n");
1250 ast_config_destroy(phoneprov_cfg);
1251 ast_var_list_destroy(defaults);
1254 var = ast_var_assign(variable_lookup[AST_PHONEPROV_STD_PROFILE], value);
1255 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1256 ast_config_destroy(phoneprov_cfg);
1258 if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
1259 ast_log(LOG_ERROR, "Unable to load users.conf\n");
1260 ast_var_list_destroy(defaults);
1264 /* Go ahead and load global variables from users.conf so we can append to profiles */
1265 for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
1266 if (!strcasecmp(v->name, pp_user_lookup[AST_PHONEPROV_STD_VOICEMAIL_EXTEN])) {
1267 var = ast_var_assign(variable_lookup[AST_PHONEPROV_STD_VOICEMAIL_EXTEN], v->value);
1268 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1270 if (!strcasecmp(v->name, pp_user_lookup[AST_PHONEPROV_STD_EXTENSION_LENGTH])) {
1271 var = ast_var_assign(variable_lookup[AST_PHONEPROV_STD_EXTENSION_LENGTH], v->value);
1272 AST_VAR_LIST_INSERT_TAIL(defaults, var);
1275 ast_config_destroy(cfg);
1280 static int load_users(void)
1282 struct ast_config *cfg;
1285 struct ast_flags config_flags = { 0 };
1286 struct varshead *defaults = get_defaults();
1289 ast_log(LOG_WARNING, "Unable to load default variables.\n");
1293 if (!(cfg = ast_config_load("users.conf", config_flags))
1294 || cfg == CONFIG_STATUS_FILEINVALID) {
1295 ast_log(LOG_WARNING, "Unable to load users.conf\n");
1296 ast_var_list_destroy(defaults);
1301 while ((cat = ast_category_browse(cfg, cat))) {
1304 struct ast_var_t *varx;
1305 struct ast_var_t *vard;
1307 if (strcasecmp(cat, "general") && strcasecmp(cat, "authentication")) {
1308 struct varshead *variables = ast_var_list_create();
1310 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp))) {
1311 ast_var_list_destroy(variables);
1315 /* Transfer the standard variables */
1316 for (i = 0; i < AST_PHONEPROV_STD_VAR_LIST_LENGTH; i++) {
1317 if (pp_user_lookup[i]) {
1318 value = ast_variable_retrieve(cfg, cat, pp_user_lookup[i]);
1320 varx = ast_var_assign(variable_lookup[i],
1322 AST_VAR_LIST_INSERT_TAIL(variables, varx);
1327 if (!ast_var_find(variables, variable_lookup[AST_PHONEPROV_STD_MAC])) {
1328 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
1329 ast_var_list_destroy(variables);
1333 /* Apply defaults */
1334 AST_VAR_LIST_TRAVERSE(defaults, vard) {
1335 if (ast_var_find(variables, vard->name)) {
1338 varx = ast_var_assign(vard->name, vard->value);
1339 AST_VAR_LIST_INSERT_TAIL(variables, varx);
1342 ast_phoneprov_add_extension(SIPUSERS_PROVIDER_NAME, variables);
1345 ast_config_destroy(cfg);
1346 ast_var_list_destroy(defaults);
1350 static int load_common(void)
1352 struct ast_config *phoneprov_cfg;
1353 struct ast_flags config_flags = { 0 };
1356 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))
1357 || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
1358 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
1363 while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
1364 if (!strcasecmp(cat, "general")) {
1367 build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
1369 ast_config_destroy(phoneprov_cfg);
1371 if (!ao2_container_count(profiles)) {
1372 ast_log(LOG_ERROR, "There are no provisioning profiles in phoneprov.conf.\n");
1379 static int unload_module(void)
1381 ast_http_uri_unlink(&phoneprovuri);
1382 ast_custom_function_unregister(&pp_each_user_function);
1383 ast_custom_function_unregister(&pp_each_extension_function);
1384 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1386 /* This cleans up the sip.conf/users.conf provider (called specifically for clarity) */
1387 ast_phoneprov_provider_unregister(SIPUSERS_PROVIDER_NAME);
1389 /* This cleans up the framework which also cleans up the providers. */
1391 ao2_cleanup(profiles);
1395 ao2_cleanup(http_routes);
1400 ao2_cleanup(providers);
1407 * \brief Load the module
1409 * Module loading including tests for configuration or dependencies.
1410 * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
1411 * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
1412 * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the
1413 * configuration file or other non-critical problem return
1414 * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
1416 static int load_module(void)
1418 profiles = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MAX_PROFILE_BUCKETS,
1419 phone_profile_hash_fn, NULL, phone_profile_cmp_fn);
1421 ast_log(LOG_ERROR, "Unable to allocate profiles container.\n");
1422 return AST_MODULE_LOAD_DECLINE;
1425 http_routes = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MAX_ROUTE_BUCKETS,
1426 http_route_hash_fn, NULL, http_route_cmp_fn);
1428 ast_log(LOG_ERROR, "Unable to allocate routes container.\n");
1432 if (load_common()) {
1433 ast_log(LOG_ERROR, "Unable to load provisioning profiles.\n");
1437 users = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MAX_USER_BUCKETS,
1438 user_hash_fn, NULL, user_cmp_fn);
1440 ast_log(LOG_ERROR, "Unable to allocate users container.\n");
1444 providers = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0,
1445 MAX_PROVIDER_BUCKETS, phoneprov_provider_hash_fn, NULL, phoneprov_provider_cmp_fn);
1447 ast_log(LOG_ERROR, "Unable to allocate providers container.\n");
1451 /* Register ourselves as the provider for sip.conf/users.conf */
1452 if (ast_phoneprov_provider_register(SIPUSERS_PROVIDER_NAME, load_users)) {
1453 ast_log(LOG_WARNING, "Unable register sip/users config provider. Others may succeed.\n");
1456 ast_http_uri_link(&phoneprovuri);
1458 ast_custom_function_register(&pp_each_user_function);
1459 ast_custom_function_register(&pp_each_extension_function);
1460 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
1462 return AST_MODULE_LOAD_SUCCESS;
1466 return AST_MODULE_LOAD_DECLINE;
1469 static int reload(void)
1471 struct ao2_iterator i;
1472 struct phoneprov_provider *provider;
1474 /* Clean everything except the providers */
1479 /* Reload the profiles */
1480 if (load_common()) {
1481 ast_log(LOG_ERROR, "Unable to reload provisioning profiles.\n");
1483 return AST_MODULE_LOAD_DECLINE;
1486 /* For each provider, reload the users */
1487 ao2_lock(providers);
1488 i = ao2_iterator_init(providers, 0);
1489 for(; (provider = ao2_iterator_next(&i)); ao2_ref(provider, -1)) {
1490 if (provider->load_users()) {
1491 ast_log(LOG_ERROR, "Unable to load provider '%s' users. Reload aborted.\n", provider->provider_name);
1495 ao2_iterator_destroy(&i);
1496 ao2_unlock(providers);
1498 return AST_MODULE_LOAD_SUCCESS;
1501 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "HTTP Phone Provisioning",
1502 .support_level = AST_MODULE_SUPPORT_EXTENDED,
1503 .load = load_module,
1504 .unload = unload_module,
1506 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
1510 /**** Public API for register/unregister, set defaults, and add extension. ****/
1512 const char *ast_phoneprov_std_variable_lookup(enum ast_phoneprov_std_variables var)
1514 if (var >= AST_PHONEPROV_STD_VAR_LIST_LENGTH) {
1518 return variable_lookup[var];
1521 int ast_phoneprov_provider_register(char *provider_name,
1522 ast_phoneprov_load_users_cb load_users)
1524 struct phoneprov_provider *provider;
1526 if (ast_strlen_zero(provider_name)) {
1527 ast_log(LOG_ERROR, "Provider name can't be empty.\n");
1532 ast_log(LOG_WARNING, "Provider '%s' cannot be registered: res_phoneprov not loaded.\n", provider_name);
1536 provider = find_provider(provider_name);
1538 ast_log(LOG_ERROR, "There is already a provider registered named '%s'.\n", provider_name);
1539 ao2_ref(provider, -1);
1543 provider = ao2_alloc(sizeof(struct phoneprov_provider), provider_destructor);
1545 ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s'.\n", provider_name);
1549 if (ast_string_field_init(provider, 32)) {
1550 ao2_ref(provider, -1);
1551 ast_log(LOG_ERROR, "Unable to allocate sufficient memory for provider '%s' stringfields.\n", provider_name);
1555 ast_string_field_set(provider, provider_name, provider_name);
1556 provider->load_users = load_users;
1558 ao2_link(providers, provider);
1559 ao2_ref(provider, -1);
1561 if (provider->load_users()) {
1562 ast_log(LOG_ERROR, "Unable to load provider '%s' users. Register aborted.\n", provider_name);
1563 ast_phoneprov_provider_unregister(provider_name);
1570 static int extensions_delete_cb(void *obj, void *arg, int flags)
1572 char *provider_name = arg;
1573 struct user *user = obj;
1574 if (strcmp(user->provider_name, provider_name)) {
1580 static int extension_delete_cb(void *obj, void *arg, void *data, int flags)
1582 struct user *user = obj;
1583 char *provider_name = data;
1584 char *macaddress = arg;
1586 if (!strcmp(user->provider_name, provider_name) && !strcasecmp(user->macaddress, macaddress)) {
1592 void ast_phoneprov_delete_extension(char *provider_name, char *macaddress)
1598 ao2_callback_data(users, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE | OBJ_SEARCH_KEY,
1599 extension_delete_cb, macaddress, provider_name);
1602 void ast_phoneprov_delete_extensions(char *provider_name)
1608 ao2_callback(users, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, extensions_delete_cb, provider_name);
1611 void ast_phoneprov_provider_unregister(char *provider_name)
1617 ast_phoneprov_delete_extensions(provider_name);
1618 ao2_find(providers, provider_name, OBJ_SEARCH_KEY | OBJ_NODATA | OBJ_UNLINK);
1621 int ast_phoneprov_add_extension(char *provider_name, struct varshead *vars)
1623 RAII_VAR(struct phoneprov_provider *, provider, NULL, ao2_cleanup);
1624 RAII_VAR(struct user *, user, NULL, ao2_cleanup);
1625 RAII_VAR(struct phone_profile *, profile, NULL, ao2_cleanup);
1626 struct extension *exten;
1631 if (ast_strlen_zero(provider_name)) {
1632 ast_log(LOG_ERROR, "Provider name can't be empty.\n");
1636 ast_log(LOG_ERROR, "Variable list can't be empty.\n");
1640 username = ast_var_find(vars, variable_lookup[AST_PHONEPROV_STD_USERNAME]);
1642 ast_log(LOG_ERROR, "Extension name can't be empty.\n");
1646 mac = ast_var_find(vars, variable_lookup[AST_PHONEPROV_STD_MAC]);
1648 ast_log(LOG_ERROR, "MAC Address can't be empty.\n");
1652 provider = find_provider(provider_name);
1654 ast_log(LOG_ERROR, "Provider '%s' wasn't found in the registry.\n", provider_name);
1658 profile_name = ast_var_find(vars,
1659 variable_lookup[AST_PHONEPROV_STD_PROFILE]);
1660 if (!profile_name) {
1661 ast_log(LOG_ERROR, "No profile could be found for user '%s' - skipping.\n", username);
1664 if (!(profile = find_profile(profile_name))) {
1665 ast_log(LOG_ERROR, "Could not look up profile '%s' - skipping.\n", profile_name);
1669 if (!(user = find_user(mac))) {
1671 if (!(user = build_user(mac, profile, provider_name))) {
1672 ast_log(LOG_ERROR, "Could not create user for '%s' - skipping\n", mac);
1676 if (!(exten = build_extension(username, vars))) {
1677 ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
1681 if (add_user_extension(user, exten)) {
1682 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1683 exten = delete_extension(exten);
1687 if (build_user_routes(user)) {
1688 ast_log(LOG_WARNING, "Could not create http routes for '%s' - skipping\n", user->macaddress);
1691 ao2_link(users, user);
1694 if (strcmp(provider_name, user->provider_name)) {
1695 ast_log(LOG_ERROR, "MAC address '%s' was already added by provider '%s' - skipping\n", user->macaddress, user->provider_name);
1699 if (!(exten = build_extension(username, vars))) {
1700 ast_log(LOG_ERROR, "Could not create extension for '%s' - skipping\n", user->macaddress);
1704 if (add_user_extension(user, exten)) {
1705 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1706 exten = delete_extension(exten);