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) {
738 exten->index = atoi(tmp);
741 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) {
742 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
746 if (!ast_strlen_zero(global_server)) {
747 if ((var = ast_var_assign("SERVER", global_server)))
748 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
751 if (!ast_strlen_zero(global_serverport)) {
752 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
753 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
759 static struct user *unref_user(struct user *user)
766 /*! \brief Return a user looked up by name */
767 static struct user *find_user(const char *macaddress)
770 .macaddress = macaddress,
773 return ao2_find(users, &tmp, OBJ_POINTER);
776 static int users_hash_fn(const void *obj, const int flags)
778 const struct user *user = obj;
780 return ast_str_hash(user->macaddress);
783 static int users_cmp_fn(void *obj, void *arg, int flags)
785 const struct user *user1 = obj, *user2 = arg;
787 return !strcasecmp(user1->macaddress, user2->macaddress) ? CMP_MATCH : 0;
790 /*! \brief Free all memory associated with a user */
791 static void user_destructor(void *obj)
793 struct user *user = obj;
794 struct extension *exten;
796 while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
797 exten = delete_extension(exten);
801 user->profile = unref_profile(user->profile);
804 ast_string_field_free_memory(user);
807 /*! \brief Delete all users */
808 static void delete_users(void)
810 struct ao2_iterator i;
813 i = ao2_iterator_init(users, 0);
814 while ((user = ao2_iterator_next(&i))) {
815 ao2_unlink(users, user);
816 user = unref_user(user);
820 /*! \brief Build and return a user structure based on gathered config data */
821 static struct user *build_user(const char *mac, struct phone_profile *profile)
826 if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
827 profile = unref_profile(profile);
831 if (ast_string_field_init(user, 32)) {
832 profile = unref_profile(profile);
833 user = unref_user(user);
837 ast_string_field_set(user, macaddress, mac);
838 user->profile = profile; /* already ref counted by find_profile */
843 /*! \brief Add an extension to a user ordered by index/linenumber */
844 static int add_user_extension(struct user *user, struct extension *exten)
846 struct ast_var_t *var;
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 char expand_buf[VAR_BUF_SIZE] = {0,};
852 struct ast_var_t *var2;
854 pbx_substitute_variables_varshead(exten->headp, var->value, expand_buf, sizeof(expand_buf));
855 if ((var2 = ast_var_assign(var->name, expand_buf)))
856 AST_LIST_INSERT_TAIL(exten->headp, var2, entries);
859 if (AST_LIST_EMPTY(&user->extensions)) {
860 AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
862 struct extension *exten_iter;
864 AST_LIST_TRAVERSE_SAFE_BEGIN(&user->extensions, exten_iter, entry) {
865 if (exten->index < exten_iter->index) {
866 AST_LIST_INSERT_BEFORE_CURRENT(exten, entry);
867 } else if (exten->index == exten_iter->index) {
868 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
869 user = unref_user(user); /* Profile should be unreffed now that it is attached to the user */
871 } else if (!AST_LIST_NEXT(exten, entry)) {
872 AST_LIST_INSERT_TAIL(&user->extensions, exten, entry);
875 AST_LIST_TRAVERSE_SAFE_END;
881 /*! \brief Add an http route for dynamic files attached to the profile of the user */
882 static int build_user_routes(struct user *user)
884 struct phoneprov_file *pp_file;
886 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
887 char expand_buf[VAR_BUF_SIZE] = { 0, };
889 pbx_substitute_variables_varshead(AST_LIST_FIRST(&user->extensions)->headp, pp_file->format, expand_buf, sizeof(expand_buf));
890 build_route(pp_file, user, expand_buf);
896 /* \brief Parse config files and create appropriate structures */
897 static int set_config(void)
899 struct ast_config *cfg, *phoneprov_cfg;
901 struct ast_variable *v;
902 struct ast_flags config_flags = { 0 };
903 struct ast_var_t *var;
905 /* Try to grab the port from sip.conf. If we don't get it here, we'll set it
906 * to whatever is set in phoneprov.conf or default to 5060 */
907 if ((cfg = ast_config_load("sip.conf", config_flags))) {
908 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
909 ast_config_destroy(cfg);
912 if (!(cfg = ast_config_load("users.conf", config_flags))) {
913 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
917 /* Go ahead and load global variables from users.conf so we can append to profiles */
918 for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
919 if (!strcasecmp(v->name, "vmexten")) {
920 if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value))) {
921 ast_mutex_lock(&globals_lock);
922 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
923 ast_mutex_unlock(&globals_lock);
926 if (!strcasecmp(v->name, "localextenlength")) {
927 if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
928 ast_mutex_lock(&globals_lock);
929 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
930 ast_mutex_unlock(&globals_lock);
934 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))) {
935 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
940 while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
941 if (!strcasecmp(cat, "general")) {
942 for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
943 if (!strcasecmp(v->name, "serveraddr"))
944 ast_copy_string(global_server, v->value, sizeof(global_server));
945 else if (!strcasecmp(v->name, "serveriface")) {
947 lookup_iface(v->value, &addr);
948 ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
949 } else if (!strcasecmp(v->name, "serverport"))
950 ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
951 else if (!strcasecmp(v->name, "default_profile"))
952 ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
955 build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
958 ast_config_destroy(phoneprov_cfg);
961 while ((cat = ast_category_browse(cfg, cat))) {
962 const char *tmp, *mac;
964 struct phone_profile *profile;
965 struct extension *exten;
967 if (!strcasecmp(cat, "general")) {
971 if (!strcasecmp(cat, "authentication"))
974 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
977 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
978 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
982 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
983 if (ast_strlen_zero(tmp)) {
984 ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
988 if (!(user = find_user(mac))) {
989 if (!(profile = find_profile(tmp))) {
990 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
994 if (!(user = build_user(mac, profile))) {
995 ast_log(LOG_WARNING, "Could not create user for '%s' - skipping\n", user->macaddress);
999 if (!(exten = build_extension(cfg, cat))) {
1000 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
1001 user = unref_user(user);
1005 if (add_user_extension(user, exten)) {
1006 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1007 user = unref_user(user);
1008 exten = delete_extension(exten);
1012 if (build_user_routes(user)) {
1013 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->macaddress);
1014 user = unref_user(user);
1018 ao2_link(users, user);
1019 user = unref_user(user);
1021 if (!(exten = build_extension(cfg, cat))) {
1022 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
1023 user = unref_user(user);
1027 if (add_user_extension(user, exten)) {
1028 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1029 user = unref_user(user);
1030 exten = delete_extension(exten);
1034 user = unref_user(user);
1038 ast_config_destroy(cfg);
1043 /*! \brief Delete all http routes, freeing their memory */
1044 static void delete_routes(void)
1046 struct ao2_iterator i;
1047 struct http_route *route;
1049 i = ao2_iterator_init(http_routes, 0);
1050 while ((route = ao2_iterator_next(&i))) {
1051 ao2_unlink(http_routes, route);
1052 route = unref_route(route);
1056 /*! \brief Delete all phone profiles, freeing their memory */
1057 static void delete_profiles(void)
1059 struct ao2_iterator i;
1060 struct phone_profile *profile;
1062 i = ao2_iterator_init(profiles, 0);
1063 while ((profile = ao2_iterator_next(&i))) {
1064 ao2_unlink(profiles, profile);
1065 profile = unref_profile(profile);
1069 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1070 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1072 char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
1073 struct ao2_iterator i;
1075 AST_DECLARE_APP_ARGS(args,
1076 AST_APP_ARG(string);
1077 AST_APP_ARG(exclude_mac);
1079 AST_STANDARD_APP_ARGS(args, data);
1081 /* Fix data by turning %{ into ${ */
1082 while ((tmp = strstr(args.string, "%{")))
1085 i = ao2_iterator_init(users, 0);
1086 while ((user = ao2_iterator_next(&i))) {
1087 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1090 pbx_substitute_variables_varshead(AST_LIST_FIRST(&user->extensions)->headp, args.string, expand_buf, sizeof(expand_buf));
1091 ast_build_string(&buf, &len, "%s", expand_buf);
1092 user = unref_user(user);
1098 static struct ast_custom_function pp_each_user_function = {
1099 .name = "PP_EACH_USER",
1100 .synopsis = "Generate a string for each phoneprov user",
1101 .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
1103 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
1104 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
1105 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
1107 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
1108 .read = pp_each_user_exec,
1111 /*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1112 static int pp_each_extension_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1114 char expand_buf[VAR_BUF_SIZE] = {0,};
1116 struct extension *exten;
1117 char path[PATH_MAX];
1120 AST_DECLARE_APP_ARGS(args,
1122 AST_APP_ARG(template);
1125 AST_STANDARD_APP_ARGS(args, data);
1127 if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1128 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requries both a macaddress and template filename.\n");
1132 if (!(user = find_user(args.mac))) {
1133 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1137 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1138 filelen = load_file(path, &file);
1140 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1151 AST_LIST_TRAVERSE(&user->extensions, exten, entry) {
1152 pbx_substitute_variables_varshead(exten->headp, file, expand_buf, sizeof(expand_buf));
1153 ast_build_string(&buf, &len, "%s", expand_buf);
1158 user = unref_user(user);
1163 static struct ast_custom_function pp_each_extension_function = {
1164 .name = "PP_EACH_EXTENSION",
1165 .synopsis = "Execute specified template for each extension",
1166 .syntax = "PP_EACH_EXTENSION(<mac>|<template>)",
1168 "Output the specified template for each extension associated with the specified\n"
1170 .read = pp_each_extension_exec,
1173 /*! \brief CLI command to list static and dynamic routes */
1174 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1176 #define FORMAT "%-40.40s %-30.30s\n"
1177 struct ao2_iterator i;
1178 struct http_route *route;
1182 e->command = "phoneprov show routes";
1184 "Usage: phoneprov show routes\n"
1185 " Lists all registered phoneprov http routes.\n";
1191 /* This currently iterates over routes twice, but it is the only place I've needed
1192 * to really separate static an dynamic routes, so I've just left it this way. */
1193 ast_cli(a->fd, "Static routes\n\n");
1194 ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
1195 i = ao2_iterator_init(http_routes, 0);
1196 while ((route = ao2_iterator_next(&i))) {
1198 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1199 route = unref_route(route);
1202 ast_cli(a->fd, "\nDynamic routes\n\n");
1203 ast_cli(a->fd, FORMAT, "Relative URI", "Template");
1205 i = ao2_iterator_init(http_routes, 0);
1206 while ((route = ao2_iterator_next(&i))) {
1208 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1209 route = unref_route(route);
1215 static struct ast_cli_entry pp_cli[] = {
1216 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1219 static struct ast_http_uri phoneprovuri = {
1220 .callback = phoneprov_callback,
1221 .description = "Asterisk HTTP Phone Provisioning Tool",
1229 static int load_module(void)
1231 profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
1233 http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
1235 users = ao2_container_alloc(MAX_USER_BUCKETS, users_hash_fn, users_cmp_fn);
1237 AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
1238 ast_mutex_init(&globals_lock);
1240 ast_custom_function_register(&pp_each_user_function);
1241 ast_custom_function_register(&pp_each_extension_function);
1242 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
1245 ast_http_uri_link(&phoneprovuri);
1250 static int unload_module(void)
1252 struct ast_var_t *var;
1254 ast_http_uri_unlink(&phoneprovuri);
1255 ast_custom_function_unregister(&pp_each_user_function);
1256 ast_custom_function_unregister(&pp_each_extension_function);
1257 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1262 ao2_ref(profiles, -1);
1263 ao2_ref(http_routes, -1);
1266 ast_mutex_lock(&globals_lock);
1267 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1268 ast_var_delete(var);
1270 ast_mutex_unlock(&globals_lock);
1272 ast_mutex_destroy(&globals_lock);
1277 static int reload(void)
1279 struct ast_var_t *var;
1285 ast_mutex_lock(&globals_lock);
1286 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1287 ast_var_delete(var);
1289 ast_mutex_unlock(&globals_lock);
1296 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1297 .load = load_module,
1298 .unload = unload_module,