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: 96773 $")
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
68 /*! \brief for use in lookup_iface */
69 static struct in_addr __ourip = { .s_addr = 0x00000000, };
71 /* \note This enum and the pp_variable_list must be in the same order or
72 * bad things happen! */
82 PP_VAR_LIST_LENGTH, /* This entry must always be the last in the list */
85 /*! \brief Lookup table to translate between users.conf property names and
86 * variables for use in phoneprov templates */
87 static const struct pp_variable_lookup {
89 const char * const user_var;
90 const char * const template_var;
91 } pp_variable_list[] = {
92 { PP_MACADDRESS, "macaddress", "MAC" },
93 { PP_USERNAME, "username", "USERNAME" },
94 { PP_FULLNAME, "fullname", "DISPLAY_NAME" },
95 { PP_SECRET, "secret", "SECRET" },
96 { PP_LABEL, "label", "LABEL" },
97 { PP_CALLERID, "cid_number", "CALLERID" },
98 { PP_TIMEZONE, "timezone", "TIMEZONE" },
99 { PP_LINENUMBER, "linenumber", "LINE" },
102 /*! \brief structure to hold file data */
103 struct phoneprov_file {
104 AST_DECLARE_STRING_FIELDS(
105 AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */
106 AST_STRING_FIELD(template); /*!< Template/physical file location */
107 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
109 AST_LIST_ENTRY(phoneprov_file) entry;
112 /*! \brief structure to hold phone profiles read from phoneprov.conf */
113 struct phone_profile {
114 AST_DECLARE_STRING_FIELDS(
115 AST_STRING_FIELD(name); /*!< Name of phone profile */
116 AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */
117 AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */
119 struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
120 AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */
121 AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */
125 AST_DECLARE_STRING_FIELDS(
126 AST_STRING_FIELD(name);
129 struct varshead *headp; /*!< List of variables to substitute into templates */
130 AST_LIST_ENTRY(extension) entry;
133 /*! \brief structure to hold users read from users.conf */
135 AST_DECLARE_STRING_FIELDS(
136 AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */
138 struct phone_profile *profile; /*!< Profile the phone belongs to */
139 AST_LIST_HEAD_NOLOCK(, extension) extensions;
142 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
144 AST_DECLARE_STRING_FIELDS(
145 AST_STRING_FIELD(uri); /*!< The URI requested */
147 struct phoneprov_file *file; /*!< The file that links to the URI */
148 struct user *user; /*!< The user that has variables to substitute into the file
149 * NULL in the case of a static route */
152 static struct ao2_container *profiles;
153 static struct ao2_container *http_routes;
154 static struct ao2_container *users;
156 /*! \brief Extensions whose mime types we think we know */
161 { "png", "image/png" },
162 { "xml", "text/xml" },
163 { "jpg", "image/jpeg" },
164 { "js", "application/x-javascript" },
165 { "wav", "audio/x-wav" },
166 { "mp3", "audio/mpeg" },
169 static char global_server[80] = ""; /*!< Server to substitute into templates */
170 static char global_serverport[6] = ""; /*!< Server port to substitute into templates */
171 static char global_default_profile[80] = ""; /*!< Default profile to use if one isn't specified */
173 /*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
174 static struct varshead global_variables;
175 static ast_mutex_t globals_lock;
177 /*! \brief Return mime type based on extension */
178 static char *ftype2mtype(const char *ftype)
182 if (ast_strlen_zero(ftype))
185 for (x = 0;x < ARRAY_LEN(mimetypes);x++) {
186 if (!strcasecmp(ftype, mimetypes[x].ext))
187 return mimetypes[x].mtype;
193 /* iface is the interface (e.g. eth0); address is the return value */
194 static int lookup_iface(const char *iface, struct in_addr *address)
198 struct sockaddr_in *sin;
200 memset(&ifr, 0, sizeof(ifr));
201 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
203 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
205 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
209 res = ioctl(mysock, SIOCGIFADDR, &ifr);
214 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
215 memcpy(address, &__ourip, sizeof(__ourip));
218 sin = (struct sockaddr_in *)&ifr.ifr_addr;
219 memcpy(address, &sin->sin_addr, sizeof(*address));
224 static struct phone_profile *unref_profile(struct phone_profile *prof)
231 /*! \brief Return a phone profile looked up by name */
232 static struct phone_profile *find_profile(const char *name)
234 struct phone_profile tmp = {
238 return ao2_find(profiles, &tmp, OBJ_POINTER);
241 static int profile_hash_fn(const void *obj, const int flags)
243 const struct phone_profile *profile = obj;
245 return ast_str_hash(profile->name);
248 static int profile_cmp_fn(void *obj, void *arg, int flags)
250 const struct phone_profile *profile1 = obj, *profile2 = arg;
252 return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH : 0;
255 static void delete_file(struct phoneprov_file *file)
257 ast_string_field_free_memory(file);
261 static void profile_destructor(void *obj)
263 struct phone_profile *profile = obj;
264 struct phoneprov_file *file;
265 struct ast_var_t *var;
267 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
270 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
273 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
276 ast_free(profile->headp);
277 ast_string_field_free_memory(profile);
280 static struct http_route *unref_route(struct http_route *route)
287 static int routes_hash_fn(const void *obj, const int flags)
289 const struct http_route *route = obj;
291 return ast_str_hash(route->uri);
294 static int routes_cmp_fn(void *obj, void *arg, int flags)
296 const struct http_route *route1 = obj, *route2 = arg;
298 return !strcmp(route1->uri, route2->uri) ? CMP_MATCH : 0;
301 static void route_destructor(void *obj)
303 struct http_route *route = obj;
305 ast_string_field_free_memory(route);
308 /*! \brief Read a TEXT file into a string and return the length */
309 static int load_file(const char *filename, char **ret)
314 if (!(f = fopen(filename, "r"))) {
319 fseek(f, 0, SEEK_END);
321 fseek(f, 0, SEEK_SET);
322 if (!(*ret = ast_malloc(len + 1)))
325 if (len != fread(*ret, sizeof(char), len, f)) {
338 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
339 \param headp pointer to list of user variables
340 \param zone A time zone. NULL sets variables based on timezone of the machine
342 static void set_timezone_variables(struct varshead *headp, const char *zone)
348 struct ast_tm tm_info;
351 struct ast_var_t *var;
355 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
356 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
357 var = ast_var_assign("TZOFFSET", buffer);
359 AST_LIST_INSERT_TAIL(headp, var, entries);
364 if ((var = ast_var_assign("DST_ENABLE", "1")))
365 AST_LIST_INSERT_TAIL(headp, var, entries);
367 tv.tv_sec = dststart;
368 ast_localtime(&tv, &tm_info, zone);
370 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
371 if ((var = ast_var_assign("DST_START_MONTH", buffer)))
372 AST_LIST_INSERT_TAIL(headp, var, entries);
374 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
375 if ((var = ast_var_assign("DST_START_MDAY", buffer)))
376 AST_LIST_INSERT_TAIL(headp, var, entries);
378 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
379 if ((var = ast_var_assign("DST_START_HOUR", buffer)))
380 AST_LIST_INSERT_TAIL(headp, var, entries);
383 ast_localtime(&tv, &tm_info, zone);
385 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
386 if ((var = ast_var_assign("DST_END_MONTH", buffer)))
387 AST_LIST_INSERT_TAIL(headp, var, entries);
389 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
390 if ((var = ast_var_assign("DST_END_MDAY", buffer)))
391 AST_LIST_INSERT_TAIL(headp, var, entries);
393 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
394 if ((var = ast_var_assign("DST_END_HOUR", buffer)))
395 AST_LIST_INSERT_TAIL(headp, var, entries);
398 /*! \brief Callback that is executed everytime an http request is received by this module */
399 static struct ast_str *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 *vars, struct ast_variable *headers, int *status, char **title, int *contentlength)
401 struct http_route *route;
402 struct http_route search_route = {
405 struct ast_str *result = ast_str_create(512);
411 struct timeval tv = ast_tvnow();
414 if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) {
418 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
420 if (!route->user) { /* Static file */
422 fd = open(path, O_RDONLY);
427 len = lseek(fd, 0, SEEK_END);
428 lseek(fd, 0, SEEK_SET);
430 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
435 ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
436 fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
437 "Server: Asterisk/%s\r\n"
439 "Connection: close\r\n"
440 "Cache-Control: no-cache, no-store\r\n"
441 "Content-Length: %d\r\n"
442 "Content-Type: %s\r\n\r\n",
443 ast_get_version(), buf, len, route->file->mime_type);
445 while ((len = read(fd, buf, sizeof(buf))) > 0) {
446 fwrite(buf, 1, len, ser->f);
450 route = unref_route(route);
452 } else { /* Dynamic file */
456 len = load_file(path, &file);
458 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
470 /* XXX This is a hack -- maybe sum length of all variables in route->user->headp and add that? */
471 bufsize = len + VAR_BUF_SIZE;
473 /* malloc() instead of alloca() here, just in case the file is bigger than
474 * we have enough stack space for. */
475 if (!(tmp = ast_calloc(1, bufsize))) {
483 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
484 * the IP address we are listening on that the phone contacted for this config file */
485 if (ast_strlen_zero(global_server)) {
486 struct sockaddr name;
487 socklen_t namelen = sizeof(name);
490 if ((res = getsockname(ser->fd, &name, &namelen))) {
491 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
493 struct ast_var_t *var;
494 struct extension *exten_iter;
496 if ((var = ast_var_assign("SERVER", ast_inet_ntoa(((struct sockaddr_in *)&name)->sin_addr)))) {
497 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
498 AST_LIST_INSERT_TAIL(exten_iter->headp, var, entries);
504 pbx_substitute_variables_varshead(AST_LIST_FIRST(&route->user->extensions)->headp, file, tmp, bufsize);
510 ast_str_append(&result, 0,
511 "Content-Type: %s\r\n"
512 "Content-length: %d\r\n"
514 "%s", route->file->mime_type, (int) strlen(tmp), tmp);
520 route = unref_route(route);
527 *title = strdup("Not Found");
529 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
532 route = unref_route(route);
534 *title = strdup("Internal Server Error");
536 return ast_http_error(500, "Internal Error", NULL, "An internal error has occured.");
539 /*! \brief Build a route structure and add it to the list of available http routes
540 \param pp_file File to link to the route
541 \param user User to link to the route (NULL means static route)
542 \param uri URI of the route
544 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
546 struct http_route *route;
548 if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
552 if (ast_string_field_init(route, 32)) {
553 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
554 route = unref_route(route);
558 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
560 route->file = pp_file;
562 ao2_link(http_routes, route);
564 route = unref_route(route);
567 /*! \brief Build a phone profile and add it to the list of phone profiles
568 \param name the name of the profile
569 \param v ast_variable from parsing phoneprov.conf
571 static void build_profile(const char *name, struct ast_variable *v)
573 struct phone_profile *profile;
574 struct ast_var_t *var;
576 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
580 if (ast_string_field_init(profile, 32)) {
581 profile = unref_profile(profile);
585 if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
586 profile = unref_profile(profile);
590 AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
591 AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
593 ast_string_field_set(profile, name, name);
594 for (; v; v = v->next) {
595 if (!strcasecmp(v->name, "mime_type")) {
596 ast_string_field_set(profile, default_mime_type, v->value);
597 } else if (!strcasecmp(v->name, "setvar")) {
598 struct ast_var_t *var;
599 char *value_copy = ast_strdupa(v->value);
601 AST_DECLARE_APP_ARGS(args,
602 AST_APP_ARG(varname);
606 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
608 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
610 args.varname = ast_strip(args.varname);
611 args.varval = ast_strip(args.varval);
612 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
614 if ((var = ast_var_assign(args.varname, args.varval)))
615 AST_LIST_INSERT_TAIL(profile->headp, var, entries);
617 } else if (!strcasecmp(v->name, "staticdir")) {
618 ast_string_field_set(profile, staticdir, v->value);
620 struct phoneprov_file *pp_file;
621 char *file_extension;
622 char *value_copy = ast_strdupa(v->value);
624 AST_DECLARE_APP_ARGS(args,
625 AST_APP_ARG(filename);
626 AST_APP_ARG(mimetype);
629 if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) {
630 profile = unref_profile(profile);
633 if (ast_string_field_init(pp_file, 32)) {
635 profile = unref_profile(profile);
639 if ((file_extension = strrchr(pp_file->format, '.')))
642 AST_STANDARD_APP_ARGS(args, value_copy);
644 /* Mime type order of preference
645 * 1) Specific mime-type defined for file in profile
646 * 2) Mime determined by extension
647 * 3) Default mime type specified in profile
650 ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype, (S_OR(S_OR(ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
652 if (!strcasecmp(v->name, "static_file")) {
653 ast_string_field_set(pp_file, format, args.filename);
654 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
655 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
656 /* Add a route for the static files, as their filenames won't change per-user */
657 build_route(pp_file, NULL, NULL);
659 ast_string_field_set(pp_file, format, v->name);
660 ast_string_field_set(pp_file, template, args.filename);
661 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
666 /* Append the global variables to the variables list for this profile.
667 * This is for convenience later, when we need to provide a single
668 * variable list for use in substitution. */
669 ast_mutex_lock(&globals_lock);
670 AST_LIST_TRAVERSE(&global_variables, var, entries) {
671 struct ast_var_t *new_var;
672 if ((new_var = ast_var_assign(var->name, var->value))) {
673 AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
676 ast_mutex_unlock(&globals_lock);
678 ao2_link(profiles, profile);
680 profile = unref_profile(profile);
683 static struct extension *delete_extension(struct extension *exten)
685 struct ast_var_t *var;
686 while ((var = AST_LIST_REMOVE_HEAD(exten->headp, entries))) {
689 ast_free(exten->headp);
690 ast_string_field_free_memory(exten);
697 static struct extension *build_extension(struct ast_config *cfg, const char *name)
699 struct extension *exten;
700 struct ast_var_t *var;
704 if (!(exten = ast_calloc(1, sizeof(*exten)))) {
708 if (ast_string_field_init(exten, 32)) {
714 ast_string_field_set(exten, name, name);
716 if (!(exten->headp = ast_calloc(1, sizeof(*exten->headp)))) {
722 for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
723 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
725 /* If we didn't get a USERNAME variable, set it to the user->name */
726 if (i == PP_USERNAME && !tmp) {
727 if ((var = ast_var_assign(pp_variable_list[PP_USERNAME].template_var, exten->name))) {
728 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
731 } else if (i == PP_TIMEZONE) {
732 /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
733 set_timezone_variables(exten->headp, tmp);
734 } else if (i == PP_LINENUMBER && tmp) {
735 exten->index = atoi(tmp);
738 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) {
739 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
743 if (!ast_strlen_zero(global_server)) {
744 if ((var = ast_var_assign("SERVER", global_server)))
745 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
748 if (!ast_strlen_zero(global_serverport)) {
749 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
750 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
756 static struct user *unref_user(struct user *user)
763 /*! \brief Return a user looked up by name */
764 static struct user *find_user(const char *macaddress)
767 .macaddress = macaddress,
770 return ao2_find(users, &tmp, OBJ_POINTER);
773 static int users_hash_fn(const void *obj, const int flags)
775 const struct user *user = obj;
777 return ast_str_hash(user->macaddress);
780 static int users_cmp_fn(void *obj, void *arg, int flags)
782 const struct user *user1 = obj, *user2 = arg;
784 return !strcasecmp(user1->macaddress, user2->macaddress) ? CMP_MATCH : 0;
787 /*! \brief Free all memory associated with a user */
788 static void user_destructor(void *obj)
790 struct user *user = obj;
791 struct extension *exten;
793 while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
794 exten = delete_extension(exten);
798 user->profile = unref_profile(user->profile);
801 ast_string_field_free_memory(user);
804 /*! \brief Delete all users */
805 static void delete_users(void)
807 struct ao2_iterator i;
810 i = ao2_iterator_init(users, 0);
811 while ((user = ao2_iterator_next(&i))) {
812 ao2_unlink(users, user);
813 user = unref_user(user);
817 /*! \brief Build and return a user structure based on gathered config data */
818 static struct user *build_user(const char *mac, struct phone_profile *profile)
823 if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
824 profile = unref_profile(profile);
828 if (ast_string_field_init(user, 32)) {
829 profile = unref_profile(profile);
830 user = unref_user(user);
834 ast_string_field_set(user, macaddress, mac);
835 user->profile = profile; /* already ref counted by find_profile */
840 /*! \brief Add an extension to a user ordered by index/linenumber */
841 static int add_user_extension(struct user *user, struct extension *exten)
843 struct ast_var_t *var;
845 /* Append profile variables here, and substitute variables on profile
846 * setvars, so that we can use user specific variables in them */
847 AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
848 char expand_buf[VAR_BUF_SIZE] = {0,};
849 struct ast_var_t *var2;
851 pbx_substitute_variables_varshead(exten->headp, var->value, expand_buf, sizeof(expand_buf));
852 if ((var2 = ast_var_assign(var->name, expand_buf)))
853 AST_LIST_INSERT_TAIL(exten->headp, var2, entries);
856 if (AST_LIST_EMPTY(&user->extensions)) {
857 AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
859 struct extension *exten_iter;
861 AST_LIST_TRAVERSE_SAFE_BEGIN(&user->extensions, exten_iter, entry) {
862 if (exten->index < exten_iter->index) {
863 AST_LIST_INSERT_BEFORE_CURRENT(exten, entry);
864 } else if (exten->index == exten_iter->index) {
865 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
866 user = unref_user(user); /* Profile should be unreffed now that it is attached to the user */
868 } else if (!AST_LIST_NEXT(exten, entry)) {
869 AST_LIST_INSERT_TAIL(&user->extensions, exten, entry);
872 AST_LIST_TRAVERSE_SAFE_END;
878 /*! \brief Add an http route for dynamic files attached to the profile of the user */
879 static int build_user_routes(struct user *user)
881 struct phoneprov_file *pp_file;
883 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
884 char expand_buf[VAR_BUF_SIZE] = { 0, };
886 pbx_substitute_variables_varshead(AST_LIST_FIRST(&user->extensions)->headp, pp_file->format, expand_buf, sizeof(expand_buf));
887 build_route(pp_file, user, expand_buf);
893 /* \brief Parse config files and create appropriate structures */
894 static int set_config(void)
896 struct ast_config *cfg, *phoneprov_cfg;
898 struct ast_variable *v;
899 struct ast_flags config_flags = { 0 };
900 struct ast_var_t *var;
902 /* Try to grab the port from sip.conf. If we don't get it here, we'll set it
903 * to whatever is set in phoneprov.conf or default to 5060 */
904 if ((cfg = ast_config_load("sip.conf", config_flags))) {
905 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
906 ast_config_destroy(cfg);
909 if (!(cfg = ast_config_load("users.conf", config_flags))) {
910 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
914 /* Go ahead and load global variables from users.conf so we can append to profiles */
915 for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
916 if (!strcasecmp(v->name, "vmexten")) {
917 if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value))) {
918 ast_mutex_lock(&globals_lock);
919 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
920 ast_mutex_unlock(&globals_lock);
923 if (!strcasecmp(v->name, "localextenlength")) {
924 if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
925 ast_mutex_lock(&globals_lock);
926 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
927 ast_mutex_unlock(&globals_lock);
931 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))) {
932 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
937 while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
938 if (!strcasecmp(cat, "general")) {
939 for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
940 if (!strcasecmp(v->name, "serveraddr"))
941 ast_copy_string(global_server, v->value, sizeof(global_server));
942 else if (!strcasecmp(v->name, "serveriface")) {
944 lookup_iface(v->value, &addr);
945 ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
946 } else if (!strcasecmp(v->name, "serverport"))
947 ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
948 else if (!strcasecmp(v->name, "default_profile"))
949 ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
952 build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
955 ast_config_destroy(phoneprov_cfg);
958 while ((cat = ast_category_browse(cfg, cat))) {
959 const char *tmp, *mac;
961 struct phone_profile *profile;
962 struct extension *exten;
964 if (!strcasecmp(cat, "general")) {
968 if (!strcasecmp(cat, "authentication"))
971 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
974 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
975 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
979 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
980 if (ast_strlen_zero(tmp)) {
981 ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
985 if (!(user = find_user(mac))) {
986 if (!(profile = find_profile(tmp))) {
987 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
991 if (!(user = build_user(mac, profile))) {
992 ast_log(LOG_WARNING, "Could not create user for '%s' - skipping\n", user->macaddress);
996 if (!(exten = build_extension(cfg, cat))) {
997 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
998 user = unref_user(user);
1002 if (add_user_extension(user, exten)) {
1003 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1004 user = unref_user(user);
1005 exten = delete_extension(exten);
1009 if (build_user_routes(user)) {
1010 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->macaddress);
1011 user = unref_user(user);
1015 ao2_link(users, user);
1016 user = unref_user(user);
1018 if (!(exten = build_extension(cfg, cat))) {
1019 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
1020 user = unref_user(user);
1024 if (add_user_extension(user, exten)) {
1025 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1026 user = unref_user(user);
1027 exten = delete_extension(exten);
1031 user = unref_user(user);
1035 ast_config_destroy(cfg);
1040 /*! \brief Delete all http routes, freeing their memory */
1041 static void delete_routes(void)
1043 struct ao2_iterator i;
1044 struct http_route *route;
1046 i = ao2_iterator_init(http_routes, 0);
1047 while ((route = ao2_iterator_next(&i))) {
1048 ao2_unlink(http_routes, route);
1049 route = unref_route(route);
1053 /*! \brief Delete all phone profiles, freeing their memory */
1054 static void delete_profiles(void)
1056 struct ao2_iterator i;
1057 struct phone_profile *profile;
1059 i = ao2_iterator_init(profiles, 0);
1060 while ((profile = ao2_iterator_next(&i))) {
1061 ao2_unlink(profiles, profile);
1062 profile = unref_profile(profile);
1066 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1067 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1069 char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
1070 struct ao2_iterator i;
1072 AST_DECLARE_APP_ARGS(args,
1073 AST_APP_ARG(string);
1074 AST_APP_ARG(exclude_mac);
1076 AST_STANDARD_APP_ARGS(args, data);
1078 /* Fix data by turning %{ into ${ */
1079 while ((tmp = strstr(args.string, "%{")))
1082 i = ao2_iterator_init(users, 0);
1083 while ((user = ao2_iterator_next(&i))) {
1084 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1087 pbx_substitute_variables_varshead(AST_LIST_FIRST(&user->extensions)->headp, args.string, expand_buf, sizeof(expand_buf));
1088 ast_build_string(&buf, &len, "%s", expand_buf);
1089 user = unref_user(user);
1095 static struct ast_custom_function pp_each_user_function = {
1096 .name = "PP_EACH_USER",
1097 .synopsis = "Generate a string for each phoneprov user",
1098 .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
1100 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
1101 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
1102 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
1104 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
1105 .read = pp_each_user_exec,
1108 /*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1109 static int pp_each_extension_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1111 char expand_buf[VAR_BUF_SIZE] = {0,};
1113 struct extension *exten;
1114 char path[PATH_MAX];
1117 AST_DECLARE_APP_ARGS(args,
1119 AST_APP_ARG(template);
1122 AST_STANDARD_APP_ARGS(args, data);
1124 if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1125 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requries both a macaddress and template filename.\n");
1129 if (!(user = find_user(args.mac))) {
1130 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1134 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1135 filelen = load_file(path, &file);
1137 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1148 AST_LIST_TRAVERSE(&user->extensions, exten, entry) {
1149 pbx_substitute_variables_varshead(exten->headp, file, expand_buf, sizeof(expand_buf));
1150 ast_build_string(&buf, &len, "%s", expand_buf);
1155 user = unref_user(user);
1160 static struct ast_custom_function pp_each_extension_function = {
1161 .name = "PP_EACH_EXTENSION",
1162 .synopsis = "Execute specified template for each extension",
1163 .syntax = "PP_EACH_EXTENSION(<mac>|<template>)",
1165 "Output the specified template for each extension associated with the specified\n"
1167 .read = pp_each_extension_exec,
1170 /*! \brief CLI command to list static and dynamic routes */
1171 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1173 #define FORMAT "%-40.40s %-30.30s\n"
1174 struct ao2_iterator i;
1175 struct http_route *route;
1179 e->command = "phoneprov show routes";
1181 "Usage: phoneprov show routes\n"
1182 " Lists all registered phoneprov http routes.\n";
1188 /* This currently iterates over routes twice, but it is the only place I've needed
1189 * to really separate static an dynamic routes, so I've just left it this way. */
1190 ast_cli(a->fd, "Static routes\n\n");
1191 ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
1192 i = ao2_iterator_init(http_routes, 0);
1193 while ((route = ao2_iterator_next(&i))) {
1195 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1196 route = unref_route(route);
1199 ast_cli(a->fd, "\nDynamic routes\n\n");
1200 ast_cli(a->fd, FORMAT, "Relative URI", "Template");
1202 i = ao2_iterator_init(http_routes, 0);
1203 while ((route = ao2_iterator_next(&i))) {
1205 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1206 route = unref_route(route);
1212 static struct ast_cli_entry pp_cli[] = {
1213 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1216 static struct ast_http_uri phoneprovuri = {
1217 .callback = phoneprov_callback,
1218 .description = "Asterisk HTTP Phone Provisioning Tool",
1226 static int load_module(void)
1228 profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
1230 http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
1232 users = ao2_container_alloc(MAX_USER_BUCKETS, users_hash_fn, users_cmp_fn);
1234 AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
1235 ast_mutex_init(&globals_lock);
1237 ast_custom_function_register(&pp_each_user_function);
1238 ast_custom_function_register(&pp_each_extension_function);
1239 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
1242 ast_http_uri_link(&phoneprovuri);
1247 static int unload_module(void)
1249 struct ast_var_t *var;
1251 ast_http_uri_unlink(&phoneprovuri);
1252 ast_custom_function_unregister(&pp_each_user_function);
1253 ast_custom_function_unregister(&pp_each_extension_function);
1254 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1259 ao2_ref(profiles, -1);
1260 ao2_ref(http_routes, -1);
1263 ast_mutex_lock(&globals_lock);
1264 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1265 ast_var_delete(var);
1267 ast_mutex_unlock(&globals_lock);
1269 ast_mutex_destroy(&globals_lock);
1274 static int reload(void)
1276 struct ast_var_t *var;
1282 ast_mutex_lock(&globals_lock);
1283 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1284 ast_var_delete(var);
1286 ast_mutex_unlock(&globals_lock);
1293 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1294 .load = load_module,
1295 .unload = unload_module,