2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2008, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
7 * Matthew Brooks <mbrooks@digium.com>
8 * Terry Wilson <twilson@digium.com>
10 * See http://www.asterisk.org for more information about
11 * the Asterisk project. Please do not directly contact
12 * any of the maintainers of this project for assistance;
13 * the project provides a web site, mailing lists and IRC
14 * channels for your use.
16 * This program is free software, distributed under the terms of
17 * the GNU General Public License Version 2. See the LICENSE file
18 * at the top of the source tree.
23 * \brief Phone provisioning application for the asterisk internal http server
25 * \author Matthew Brooks <mbrooks@digium.com>
26 * \author Terry Wilson <twilson@digium.com>
31 #include <sys/ioctl.h>
32 #include <sys/socket.h>
35 #include <sys/sockio.h>
37 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
39 #include "asterisk/channel.h"
40 #include "asterisk/file.h"
41 #include "asterisk/paths.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/cli.h"
44 #include "asterisk/module.h"
45 #include "asterisk/http.h"
46 #include "asterisk/utils.h"
47 #include "asterisk/app.h"
48 #include "asterisk/strings.h"
49 #include "asterisk/stringfields.h"
50 #include "asterisk/options.h"
51 #include "asterisk/config.h"
52 #include "asterisk/acl.h"
53 #include "asterisk/astobj2.h"
54 #include "asterisk/ast_version.h"
57 #define MAX_PROFILE_BUCKETS 1
58 #define MAX_ROUTE_BUCKETS 1
59 #define MAX_USER_BUCKETS 1
61 #define MAX_PROFILE_BUCKETS 17
62 #define MAX_ROUTE_BUCKETS 563
63 #define MAX_USER_BUCKETS 563
64 #endif /* LOW_MEMORY */
66 #define VAR_BUF_SIZE 4096
69 <function name="PP_EACH_EXTENSION" language="en_US">
71 Execute specified template for each extension.
74 <parameter name="mac" required="true" />
75 <parameter name="template" required="true" />
78 <para>Output the specified template for each extension associated with the specified MAC address.</para>
81 <function name="PP_EACH_USER" language="en_US">
83 Generate a string for each phoneprov user.
86 <parameter name="string" required="true" />
87 <parameter name="exclude_mac" required="true" />
90 <para>Pass in a string, with phoneprov variables you want substituted in the format of
91 %{VARNAME}, and you will get the string rendered for each user in phoneprov
92 excluding ones with MAC address <replaceable>exclude_mac</replaceable>. Probably not
93 useful outside of res_phoneprov.</para>
94 <para>Example: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})</para>
99 /*! \brief for use in lookup_iface */
100 static struct in_addr __ourip = { .s_addr = 0x00000000, };
102 /* \note This enum and the pp_variable_list must be in the same order or
103 * bad things happen! */
114 PP_VAR_LIST_LENGTH, /* This entry must always be the last in the list */
117 /*! \brief Lookup table to translate between users.conf property names and
118 * variables for use in phoneprov templates */
119 static const struct pp_variable_lookup {
120 enum pp_variables id;
121 const char * const user_var;
122 const char * const template_var;
123 } pp_variable_list[] = {
124 { PP_MACADDRESS, "macaddress", "MAC" },
125 { PP_USERNAME, "username", "USERNAME" },
126 { PP_FULLNAME, "fullname", "DISPLAY_NAME" },
127 { PP_SECRET, "secret", "SECRET" },
128 { PP_LABEL, "label", "LABEL" },
129 { PP_CALLERID, "cid_number", "CALLERID" },
130 { PP_TIMEZONE, "timezone", "TIMEZONE" },
131 { PP_LINENUMBER, "linenumber", "LINE" },
132 { PP_LINEKEYS, "linekeys", "LINEKEYS" },
135 /*! \brief structure to hold file data */
136 struct phoneprov_file {
137 AST_DECLARE_STRING_FIELDS(
138 AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */
139 AST_STRING_FIELD(template); /*!< Template/physical file location */
140 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
142 AST_LIST_ENTRY(phoneprov_file) entry;
145 /*! \brief structure to hold phone profiles read from phoneprov.conf */
146 struct phone_profile {
147 AST_DECLARE_STRING_FIELDS(
148 AST_STRING_FIELD(name); /*!< Name of phone profile */
149 AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */
150 AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */
152 struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
153 AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */
154 AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */
158 AST_DECLARE_STRING_FIELDS(
159 AST_STRING_FIELD(name);
162 struct varshead *headp; /*!< List of variables to substitute into templates */
163 AST_LIST_ENTRY(extension) entry;
166 /*! \brief structure to hold users read from users.conf */
168 AST_DECLARE_STRING_FIELDS(
169 AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */
171 struct phone_profile *profile; /*!< Profile the phone belongs to */
172 AST_LIST_HEAD_NOLOCK(, extension) extensions;
175 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
177 AST_DECLARE_STRING_FIELDS(
178 AST_STRING_FIELD(uri); /*!< The URI requested */
180 struct phoneprov_file *file; /*!< The file that links to the URI */
181 struct user *user; /*!< The user that has variables to substitute into the file
182 * NULL in the case of a static route */
185 static struct ao2_container *profiles;
186 static struct ao2_container *http_routes;
187 static struct ao2_container *users;
189 static char global_server[80] = ""; /*!< Server to substitute into templates */
190 static char global_serverport[6] = ""; /*!< Server port to substitute into templates */
191 static char global_default_profile[80] = ""; /*!< Default profile to use if one isn't specified */
193 /*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
194 static struct varshead global_variables;
195 static ast_mutex_t globals_lock;
197 /* iface is the interface (e.g. eth0); address is the return value */
198 static int lookup_iface(const char *iface, struct in_addr *address)
202 struct sockaddr_in *sin;
204 memset(&ifr, 0, sizeof(ifr));
205 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
207 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
209 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
213 res = ioctl(mysock, SIOCGIFADDR, &ifr);
218 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
219 memcpy(address, &__ourip, sizeof(__ourip));
222 sin = (struct sockaddr_in *)&ifr.ifr_addr;
223 memcpy(address, &sin->sin_addr, sizeof(*address));
228 static struct phone_profile *unref_profile(struct phone_profile *prof)
235 /*! \brief Return a phone profile looked up by name */
236 static struct phone_profile *find_profile(const char *name)
238 struct phone_profile tmp = {
242 return ao2_find(profiles, &tmp, OBJ_POINTER);
245 static int profile_hash_fn(const void *obj, const int flags)
247 const struct phone_profile *profile = obj;
249 return ast_str_case_hash(profile->name);
252 static int profile_cmp_fn(void *obj, void *arg, int flags)
254 const struct phone_profile *profile1 = obj, *profile2 = arg;
256 return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH | CMP_STOP : 0;
259 static void delete_file(struct phoneprov_file *file)
261 ast_string_field_free_memory(file);
265 static void profile_destructor(void *obj)
267 struct phone_profile *profile = obj;
268 struct phoneprov_file *file;
269 struct ast_var_t *var;
271 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
274 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
277 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
280 ast_free(profile->headp);
281 ast_string_field_free_memory(profile);
284 static struct http_route *unref_route(struct http_route *route)
291 static int routes_hash_fn(const void *obj, const int flags)
293 const struct http_route *route = obj;
295 return ast_str_case_hash(route->uri);
298 static int routes_cmp_fn(void *obj, void *arg, int flags)
300 const struct http_route *route1 = obj, *route2 = arg;
302 return !strcasecmp(route1->uri, route2->uri) ? CMP_MATCH | CMP_STOP : 0;
305 static void route_destructor(void *obj)
307 struct http_route *route = obj;
309 ast_string_field_free_memory(route);
312 /*! \brief Read a TEXT file into a string and return the length */
313 static int load_file(const char *filename, char **ret)
318 if (!(f = fopen(filename, "r"))) {
323 fseek(f, 0, SEEK_END);
325 fseek(f, 0, SEEK_SET);
326 if (!(*ret = ast_malloc(len + 1)))
329 if (len != fread(*ret, sizeof(char), len, f)) {
342 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
343 \param headp pointer to list of user variables
344 \param zone A time zone. NULL sets variables based on timezone of the machine
346 static void set_timezone_variables(struct varshead *headp, const char *zone)
352 struct ast_tm tm_info;
355 struct ast_var_t *var;
359 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
360 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
361 var = ast_var_assign("TZOFFSET", buffer);
363 AST_LIST_INSERT_TAIL(headp, var, entries);
368 if ((var = ast_var_assign("DST_ENABLE", "1")))
369 AST_LIST_INSERT_TAIL(headp, var, entries);
371 when.tv_sec = dststart;
372 ast_localtime(&when, &tm_info, zone);
374 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
375 if ((var = ast_var_assign("DST_START_MONTH", buffer)))
376 AST_LIST_INSERT_TAIL(headp, var, entries);
378 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
379 if ((var = ast_var_assign("DST_START_MDAY", buffer)))
380 AST_LIST_INSERT_TAIL(headp, var, entries);
382 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
383 if ((var = ast_var_assign("DST_START_HOUR", buffer)))
384 AST_LIST_INSERT_TAIL(headp, var, entries);
386 when.tv_sec = dstend;
387 ast_localtime(&when, &tm_info, zone);
389 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
390 if ((var = ast_var_assign("DST_END_MONTH", buffer)))
391 AST_LIST_INSERT_TAIL(headp, var, entries);
393 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
394 if ((var = ast_var_assign("DST_END_MDAY", buffer)))
395 AST_LIST_INSERT_TAIL(headp, var, entries);
397 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
398 if ((var = ast_var_assign("DST_END_HOUR", buffer)))
399 AST_LIST_INSERT_TAIL(headp, var, entries);
402 /*! \brief Callback that is executed everytime an http request is received by this module */
403 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)
405 struct http_route *route;
406 struct http_route search_route = {
409 struct ast_str *result;
414 struct ast_str *http_header;
416 if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
417 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
421 if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) {
425 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
427 if (!route->user) { /* Static file */
429 fd = open(path, O_RDONLY);
434 len = lseek(fd, 0, SEEK_END);
435 lseek(fd, 0, SEEK_SET);
437 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
442 http_header = ast_str_create(80);
443 ast_str_set(&http_header, 0, "Content-type: %s",
444 route->file->mime_type);
446 ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
449 route = unref_route(route);
451 } else { /* Dynamic file */
454 len = load_file(path, &file);
456 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
468 if (!(tmp = ast_str_create(len))) {
476 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
477 * the IP address we are listening on that the phone contacted for this config file */
478 if (ast_strlen_zero(global_server)) {
481 struct sockaddr_in sa_in;
483 socklen_t namelen = sizeof(name.sa);
486 if ((res = getsockname(ser->fd, &name.sa, &namelen))) {
487 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
489 struct ast_var_t *var;
490 struct extension *exten_iter;
492 if ((var = ast_var_assign("SERVER", ast_inet_ntoa(name.sa_in.sin_addr)))) {
493 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
494 AST_LIST_INSERT_TAIL(exten_iter->headp, var, entries);
500 ast_str_substitute_variables_varshead(&tmp, 0, AST_LIST_FIRST(&route->user->extensions)->headp, file);
506 http_header = ast_str_create(80);
507 ast_str_set(&http_header, 0, "Content-type: %s",
508 route->file->mime_type);
510 if (!(result = ast_str_create(512))) {
511 ast_log(LOG_ERROR, "Could not create result string!\n");
515 ast_free(http_header);
518 ast_str_append(&result, 0, "%s", ast_str_buffer(tmp));
520 ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
525 route = unref_route(route);
531 ast_http_error(ser, 404, "Not Found", "Nothing to see here. Move along.");
535 route = unref_route(route);
536 ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
540 /*! \brief Build a route structure and add it to the list of available http routes
541 \param pp_file File to link to the route
542 \param user User to link to the route (NULL means static route)
543 \param uri URI of the route
545 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
547 struct http_route *route;
549 if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
553 if (ast_string_field_init(route, 32)) {
554 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
555 route = unref_route(route);
559 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
561 route->file = pp_file;
563 ao2_link(http_routes, route);
565 route = unref_route(route);
568 /*! \brief Build a phone profile and add it to the list of phone profiles
569 \param name the name of the profile
570 \param v ast_variable from parsing phoneprov.conf
572 static void build_profile(const char *name, struct ast_variable *v)
574 struct phone_profile *profile;
575 struct ast_var_t *var;
577 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
581 if (ast_string_field_init(profile, 32)) {
582 profile = unref_profile(profile);
586 if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
587 profile = unref_profile(profile);
591 AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
592 AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
594 ast_string_field_set(profile, name, name);
595 for (; v; v = v->next) {
596 if (!strcasecmp(v->name, "mime_type")) {
597 ast_string_field_set(profile, default_mime_type, v->value);
598 } else if (!strcasecmp(v->name, "setvar")) {
599 struct ast_var_t *variable;
600 char *value_copy = ast_strdupa(v->value);
602 AST_DECLARE_APP_ARGS(args,
603 AST_APP_ARG(varname);
607 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
609 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
611 args.varname = ast_strip(args.varname);
612 args.varval = ast_strip(args.varval);
613 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
615 if ((variable = ast_var_assign(args.varname, args.varval)))
616 AST_LIST_INSERT_TAIL(profile->headp, variable, entries);
618 } else if (!strcasecmp(v->name, "staticdir")) {
619 ast_string_field_set(profile, staticdir, v->value);
621 struct phoneprov_file *pp_file;
622 char *file_extension;
623 char *value_copy = ast_strdupa(v->value);
625 AST_DECLARE_APP_ARGS(args,
626 AST_APP_ARG(filename);
627 AST_APP_ARG(mimetype);
630 if (!(pp_file = ast_calloc_with_stringfields(1, struct phoneprov_file, 32))) {
631 profile = unref_profile(profile);
635 if ((file_extension = strrchr(pp_file->format, '.')))
638 AST_STANDARD_APP_ARGS(args, value_copy);
640 /* Mime type order of preference
641 * 1) Specific mime-type defined for file in profile
642 * 2) Mime determined by extension
643 * 3) Default mime type specified in profile
646 ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype,
647 (S_OR(S_OR(ast_http_ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
649 if (!strcasecmp(v->name, "static_file")) {
650 ast_string_field_set(pp_file, format, args.filename);
651 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
652 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
653 /* Add a route for the static files, as their filenames won't change per-user */
654 build_route(pp_file, NULL, NULL);
656 ast_string_field_set(pp_file, format, v->name);
657 ast_string_field_set(pp_file, template, args.filename);
658 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
663 /* Append the global variables to the variables list for this profile.
664 * This is for convenience later, when we need to provide a single
665 * variable list for use in substitution. */
666 ast_mutex_lock(&globals_lock);
667 AST_LIST_TRAVERSE(&global_variables, var, entries) {
668 struct ast_var_t *new_var;
669 if ((new_var = ast_var_assign(var->name, var->value))) {
670 AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
673 ast_mutex_unlock(&globals_lock);
675 ao2_link(profiles, profile);
677 profile = unref_profile(profile);
680 static struct extension *delete_extension(struct extension *exten)
682 struct ast_var_t *var;
683 while ((var = AST_LIST_REMOVE_HEAD(exten->headp, entries))) {
686 ast_free(exten->headp);
687 ast_string_field_free_memory(exten);
694 static struct extension *build_extension(struct ast_config *cfg, const char *name)
696 struct extension *exten;
697 struct ast_var_t *var;
701 if (!(exten = ast_calloc_with_stringfields(1, struct extension, 32))) {
705 ast_string_field_set(exten, name, name);
707 if (!(exten->headp = ast_calloc(1, sizeof(*exten->headp)))) {
713 for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
714 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
716 /* If we didn't get a USERNAME variable, set it to the user->name */
717 if (i == PP_USERNAME && !tmp) {
718 if ((var = ast_var_assign(pp_variable_list[PP_USERNAME].template_var, exten->name))) {
719 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
722 } else if (i == PP_TIMEZONE) {
723 /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
724 set_timezone_variables(exten->headp, tmp);
725 } else if (i == PP_LINENUMBER) {
729 exten->index = atoi(tmp);
730 } else if (i == PP_LINEKEYS) {
736 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) {
737 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
741 if (!ast_strlen_zero(global_server)) {
742 if ((var = ast_var_assign("SERVER", global_server)))
743 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
746 if (!ast_strlen_zero(global_serverport)) {
747 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
748 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
754 static struct user *unref_user(struct user *user)
761 /*! \brief Return a user looked up by name */
762 static struct user *find_user(const char *macaddress)
765 .macaddress = macaddress,
768 return ao2_find(users, &tmp, OBJ_POINTER);
771 static int users_hash_fn(const void *obj, const int flags)
773 const struct user *user = obj;
775 return ast_str_case_hash(user->macaddress);
778 static int users_cmp_fn(void *obj, void *arg, int flags)
780 const struct user *user1 = obj, *user2 = arg;
782 return !strcasecmp(user1->macaddress, user2->macaddress) ? CMP_MATCH | CMP_STOP : 0;
785 /*! \brief Free all memory associated with a user */
786 static void user_destructor(void *obj)
788 struct user *user = obj;
789 struct extension *exten;
791 while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
792 exten = delete_extension(exten);
796 user->profile = unref_profile(user->profile);
799 ast_string_field_free_memory(user);
802 /*! \brief Delete all users */
803 static void delete_users(void)
805 struct ao2_iterator i;
808 i = ao2_iterator_init(users, 0);
809 while ((user = ao2_iterator_next(&i))) {
810 ao2_unlink(users, user);
811 user = unref_user(user);
813 ao2_iterator_destroy(&i);
816 /*! \brief Build and return a user structure based on gathered config data */
817 static struct user *build_user(const char *mac, struct phone_profile *profile)
821 if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
822 profile = unref_profile(profile);
826 if (ast_string_field_init(user, 32)) {
827 profile = unref_profile(profile);
828 user = unref_user(user);
832 ast_string_field_set(user, macaddress, mac);
833 user->profile = profile; /* already ref counted by find_profile */
838 /*! \brief Add an extension to a user ordered by index/linenumber */
839 static int add_user_extension(struct user *user, struct extension *exten)
841 struct ast_var_t *var;
842 struct ast_str *str = ast_str_create(16);
848 /* Append profile variables here, and substitute variables on profile
849 * setvars, so that we can use user specific variables in them */
850 AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
851 struct ast_var_t *var2;
853 ast_str_substitute_variables_varshead(&str, 0, exten->headp, var->value);
854 if ((var2 = ast_var_assign(var->name, ast_str_buffer(str)))) {
855 AST_LIST_INSERT_TAIL(exten->headp, var2, entries);
861 if (AST_LIST_EMPTY(&user->extensions)) {
862 AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
864 struct extension *exten_iter;
866 AST_LIST_TRAVERSE_SAFE_BEGIN(&user->extensions, exten_iter, entry) {
867 if (exten->index < exten_iter->index) {
868 AST_LIST_INSERT_BEFORE_CURRENT(exten, entry);
869 } else if (exten->index == exten_iter->index) {
870 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
872 } else if (!AST_LIST_NEXT(exten_iter, entry)) {
873 AST_LIST_INSERT_TAIL(&user->extensions, exten, entry);
876 AST_LIST_TRAVERSE_SAFE_END;
882 /*! \brief Add an http route for dynamic files attached to the profile of the user */
883 static int build_user_routes(struct user *user)
885 struct phoneprov_file *pp_file;
888 if (!(str = ast_str_create(16))) {
892 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
893 ast_str_substitute_variables_varshead(&str, 0, AST_LIST_FIRST(&user->extensions)->headp, pp_file->format);
894 build_route(pp_file, user, ast_str_buffer(str));
901 /* \brief Parse config files and create appropriate structures */
902 static int set_config(void)
904 struct ast_config *cfg, *phoneprov_cfg;
906 struct ast_variable *v;
907 struct ast_flags config_flags = { 0 };
908 struct ast_var_t *var;
910 /* Try to grab the port from sip.conf. If we don't get it here, we'll set it
911 * to whatever is set in phoneprov.conf or default to 5060 */
912 if ((cfg = ast_config_load("sip.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
913 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
914 ast_config_destroy(cfg);
917 if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
918 ast_log(LOG_WARNING, "Unable to load users.conf\n");
922 /* Go ahead and load global variables from users.conf so we can append to profiles */
923 for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
924 if (!strcasecmp(v->name, "vmexten")) {
925 if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value))) {
926 ast_mutex_lock(&globals_lock);
927 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
928 ast_mutex_unlock(&globals_lock);
931 if (!strcasecmp(v->name, "localextenlength")) {
932 if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
933 ast_mutex_lock(&globals_lock);
934 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
935 ast_mutex_unlock(&globals_lock);
939 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags)) || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
940 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
941 ast_config_destroy(cfg);
946 while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
947 if (!strcasecmp(cat, "general")) {
948 for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
949 if (!strcasecmp(v->name, "serveraddr"))
950 ast_copy_string(global_server, v->value, sizeof(global_server));
951 else if (!strcasecmp(v->name, "serveriface")) {
953 lookup_iface(v->value, &addr);
954 ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
955 } else if (!strcasecmp(v->name, "serverport"))
956 ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
957 else if (!strcasecmp(v->name, "default_profile"))
958 ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
961 build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
964 ast_config_destroy(phoneprov_cfg);
967 while ((cat = ast_category_browse(cfg, cat))) {
968 const char *tmp, *mac;
970 struct phone_profile *profile;
971 struct extension *exten;
973 if (!strcasecmp(cat, "general")) {
977 if (!strcasecmp(cat, "authentication"))
980 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
983 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
984 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
988 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
989 if (ast_strlen_zero(tmp)) {
990 ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
994 if (!(user = find_user(mac))) {
995 if (!(profile = find_profile(tmp))) {
996 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
1000 if (!(user = build_user(mac, profile))) {
1001 ast_log(LOG_WARNING, "Could not create user for '%s' - skipping\n", user->macaddress);
1005 if (!(exten = build_extension(cfg, cat))) {
1006 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
1007 user = unref_user(user);
1011 if (add_user_extension(user, exten)) {
1012 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1013 user = unref_user(user);
1014 exten = delete_extension(exten);
1018 if (build_user_routes(user)) {
1019 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->macaddress);
1020 user = unref_user(user);
1024 ao2_link(users, user);
1025 user = unref_user(user);
1027 if (!(exten = build_extension(cfg, cat))) {
1028 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
1029 user = unref_user(user);
1033 if (add_user_extension(user, exten)) {
1034 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1035 user = unref_user(user);
1036 exten = delete_extension(exten);
1040 user = unref_user(user);
1044 ast_config_destroy(cfg);
1049 /*! \brief Delete all http routes, freeing their memory */
1050 static void delete_routes(void)
1052 struct ao2_iterator i;
1053 struct http_route *route;
1055 i = ao2_iterator_init(http_routes, 0);
1056 while ((route = ao2_iterator_next(&i))) {
1057 ao2_unlink(http_routes, route);
1058 route = unref_route(route);
1060 ao2_iterator_destroy(&i);
1063 /*! \brief Delete all phone profiles, freeing their memory */
1064 static void delete_profiles(void)
1066 struct ao2_iterator i;
1067 struct phone_profile *profile;
1069 i = ao2_iterator_init(profiles, 0);
1070 while ((profile = ao2_iterator_next(&i))) {
1071 ao2_unlink(profiles, profile);
1072 profile = unref_profile(profile);
1074 ao2_iterator_destroy(&i);
1077 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1078 static int pp_each_user_helper(struct ast_channel *chan, char *data, char *buf, struct ast_str **bufstr, int len)
1081 struct ao2_iterator i;
1083 struct ast_str *str;
1084 AST_DECLARE_APP_ARGS(args,
1085 AST_APP_ARG(string);
1086 AST_APP_ARG(exclude_mac);
1088 AST_STANDARD_APP_ARGS(args, data);
1090 if (!(str = ast_str_create(16))) {
1094 /* Fix data by turning %{ into ${ */
1095 while ((tmp = strstr(args.string, "%{")))
1098 i = ao2_iterator_init(users, 0);
1099 while ((user = ao2_iterator_next(&i))) {
1100 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1103 ast_str_substitute_variables_varshead(&str, len, AST_LIST_FIRST(&user->extensions)->headp, args.string);
1106 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1108 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1110 user = unref_user(user);
1112 ao2_iterator_destroy(&i);
1118 static int pp_each_user_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1120 return pp_each_user_helper(chan, data, buf, NULL, len);
1123 static int pp_each_user_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1125 return pp_each_user_helper(chan, data, NULL, buf, len);
1128 static struct ast_custom_function pp_each_user_function = {
1129 .name = "PP_EACH_USER",
1130 .read = pp_each_user_read,
1131 .read2 = pp_each_user_read2,
1134 /*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1135 static int pp_each_extension_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, int len)
1138 struct extension *exten;
1139 char path[PATH_MAX];
1142 struct ast_str *str;
1143 AST_DECLARE_APP_ARGS(args,
1145 AST_APP_ARG(template);
1148 AST_STANDARD_APP_ARGS(args, data);
1150 if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1151 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requries both a macaddress and template filename.\n");
1155 if (!(user = find_user(args.mac))) {
1156 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1160 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1161 filelen = load_file(path, &file);
1163 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1174 if (!(str = ast_str_create(filelen))) {
1178 AST_LIST_TRAVERSE(&user->extensions, exten, entry) {
1179 ast_str_substitute_variables_varshead(&str, 0, exten->headp, file);
1182 ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1184 ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1191 user = unref_user(user);
1196 static int pp_each_extension_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1198 return pp_each_extension_helper(chan, cmd, data, buf, NULL, len);
1201 static int pp_each_extension_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1203 return pp_each_extension_helper(chan, cmd, data, NULL, buf, len);
1206 static struct ast_custom_function pp_each_extension_function = {
1207 .name = "PP_EACH_EXTENSION",
1208 .read = pp_each_extension_read,
1209 .read2 = pp_each_extension_read2,
1212 /*! \brief CLI command to list static and dynamic routes */
1213 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1215 #define FORMAT "%-40.40s %-30.30s\n"
1216 struct ao2_iterator i;
1217 struct http_route *route;
1221 e->command = "phoneprov show routes";
1223 "Usage: phoneprov show routes\n"
1224 " Lists all registered phoneprov http routes.\n";
1230 /* This currently iterates over routes twice, but it is the only place I've needed
1231 * to really separate static an dynamic routes, so I've just left it this way. */
1232 ast_cli(a->fd, "Static routes\n\n");
1233 ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
1234 i = ao2_iterator_init(http_routes, 0);
1235 while ((route = ao2_iterator_next(&i))) {
1237 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1238 route = unref_route(route);
1240 ao2_iterator_destroy(&i);
1242 ast_cli(a->fd, "\nDynamic routes\n\n");
1243 ast_cli(a->fd, FORMAT, "Relative URI", "Template");
1245 i = ao2_iterator_init(http_routes, 0);
1246 while ((route = ao2_iterator_next(&i))) {
1248 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1249 route = unref_route(route);
1251 ao2_iterator_destroy(&i);
1256 static struct ast_cli_entry pp_cli[] = {
1257 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1260 static struct ast_http_uri phoneprovuri = {
1261 .callback = phoneprov_callback,
1262 .description = "Asterisk HTTP Phone Provisioning Tool",
1269 static int load_module(void)
1271 profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
1273 http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
1275 users = ao2_container_alloc(MAX_USER_BUCKETS, users_hash_fn, users_cmp_fn);
1277 AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
1278 ast_mutex_init(&globals_lock);
1280 ast_custom_function_register(&pp_each_user_function);
1281 ast_custom_function_register(&pp_each_extension_function);
1282 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
1285 ast_http_uri_link(&phoneprovuri);
1290 static int unload_module(void)
1292 struct ast_var_t *var;
1294 ast_http_uri_unlink(&phoneprovuri);
1295 ast_custom_function_unregister(&pp_each_user_function);
1296 ast_custom_function_unregister(&pp_each_extension_function);
1297 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1302 ao2_ref(profiles, -1);
1303 ao2_ref(http_routes, -1);
1306 ast_mutex_lock(&globals_lock);
1307 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1308 ast_var_delete(var);
1310 ast_mutex_unlock(&globals_lock);
1312 ast_mutex_destroy(&globals_lock);
1317 static int reload(void)
1319 struct ast_var_t *var;
1325 ast_mutex_lock(&globals_lock);
1326 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1327 ast_var_delete(var);
1329 ast_mutex_unlock(&globals_lock);
1336 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP Phone Provisioning",
1337 .load = load_module,
1338 .unload = unload_module,