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/file.h"
40 #include "asterisk/paths.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/cli.h"
43 #include "asterisk/module.h"
44 #include "asterisk/http.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/app.h"
47 #include "asterisk/strings.h"
48 #include "asterisk/stringfields.h"
49 #include "asterisk/options.h"
50 #include "asterisk/config.h"
51 #include "asterisk/acl.h"
52 #include "asterisk/astobj2.h"
53 #include "asterisk/ast_version.h"
56 #define MAX_PROFILE_BUCKETS 1
57 #define MAX_ROUTE_BUCKETS 1
59 #define MAX_PROFILE_BUCKETS 17
60 #define MAX_ROUTE_BUCKETS 563
61 #endif /* LOW_MEMORY */
63 #define VAR_BUF_SIZE 4096
65 /*! \brief for use in lookup_iface */
66 static struct in_addr __ourip = { .s_addr = 0x00000000, };
68 /* \note This enum and the pp_variable_list must be in the same order or
69 * bad things happen! */
78 PP_VAR_LIST_LENGTH, /* This entry must always be the last in the list */
81 /*! \brief Lookup table to translate between users.conf property names and
82 * variables for use in phoneprov templates */
83 static const struct pp_variable_lookup {
85 const char * const user_var;
86 const char * const template_var;
87 } pp_variable_list[] = {
88 { PP_MACADDRESS, "macaddress", "MAC" },
89 { PP_USERNAME, "username", "USERNAME" },
90 { PP_FULLNAME, "fullname", "DISPLAY_NAME" },
91 { PP_SECRET, "secret", "SECRET" },
92 { PP_LABEL, "label", "LABEL" },
93 { PP_CALLERID, "cid_number", "CALLERID" },
94 { PP_TIMEZONE, "timezone", "TIMEZONE" },
97 /*! \brief structure to hold file data */
98 struct phoneprov_file {
99 AST_DECLARE_STRING_FIELDS(
100 AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */
101 AST_STRING_FIELD(template); /*!< Template/physical file location */
102 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
104 AST_LIST_ENTRY(phoneprov_file) entry;
107 /*! \brief structure to hold phone profiles read from phoneprov.conf */
108 struct phone_profile {
109 AST_DECLARE_STRING_FIELDS(
110 AST_STRING_FIELD(name); /*!< Name of phone profile */
111 AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */
112 AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */
114 struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
115 AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */
116 AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */
119 /*! \brief structure to hold users read from users.conf */
121 AST_DECLARE_STRING_FIELDS(
122 AST_STRING_FIELD(name); /*!< Name of user */
123 AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */
125 struct phone_profile *profile; /*!< Profile the phone belongs to */
126 struct varshead *headp; /*!< List of variables to substitute into templates */
127 AST_LIST_ENTRY(user) entry;
130 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
132 AST_DECLARE_STRING_FIELDS(
133 AST_STRING_FIELD(uri); /*!< The URI requested */
135 struct phoneprov_file *file; /*!< The file that links to the URI */
136 struct user *user; /*!< The user that has variables to substitute into the file
137 * NULL in the case of a static route */
140 static struct ao2_container *profiles;
141 static struct ao2_container *http_routes;
142 static AST_RWLIST_HEAD_STATIC(users, user);
144 /*! \brief Extensions whose mime types we think we know */
149 { "png", "image/png" },
150 { "xml", "text/xml" },
151 { "jpg", "image/jpeg" },
152 { "js", "application/x-javascript" },
153 { "wav", "audio/x-wav" },
154 { "mp3", "audio/mpeg" },
157 char global_server[80] = ""; /*!< Server to substitute into templates */
158 char global_serverport[6] = ""; /*!< Server port to substitute into templates */
159 char global_default_profile[80] = ""; /*!< Default profile to use if one isn't specified */
161 /*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
162 struct varshead global_variables;
164 /*! \brief Return mime type based on extension */
165 static char *ftype2mtype(const char *ftype)
169 if (ast_strlen_zero(ftype))
172 for (x = 0;x < ARRAY_LEN(mimetypes);x++) {
173 if (!strcasecmp(ftype, mimetypes[x].ext))
174 return mimetypes[x].mtype;
180 /* iface is the interface (e.g. eth0); address is the return value */
181 static int lookup_iface(const char *iface, struct in_addr *address)
185 struct sockaddr_in *sin;
187 memset(&ifr, 0, sizeof(ifr));
188 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
190 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
192 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
196 res = ioctl(mysock, SIOCGIFADDR, &ifr);
201 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
202 memcpy(address, &__ourip, sizeof(__ourip));
205 sin = (struct sockaddr_in *)&ifr.ifr_addr;
206 memcpy(address, &sin->sin_addr, sizeof(*address));
211 static struct phone_profile *unref_profile(struct phone_profile *prof)
218 /*! \brief Return a phone profile looked up by name */
219 static struct phone_profile *find_profile(const char *name)
221 struct phone_profile tmp = {
225 return ao2_find(profiles, &tmp, OBJ_POINTER);
228 static int profile_hash_fn(const void *obj, const int flags)
230 const struct phone_profile *profile = obj;
232 return ast_str_hash(profile->name);
235 static int profile_cmp_fn(void *obj, void *arg, int flags)
237 const struct phone_profile *profile1 = obj, *profile2 = arg;
239 return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH : 0;
242 static void delete_file(struct phoneprov_file *file)
244 ast_string_field_free_memory(file);
248 static void profile_destructor(void *obj)
250 struct phone_profile *profile = obj;
251 struct phoneprov_file *file;
252 struct ast_var_t *var;
254 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
257 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
260 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
263 free(profile->headp);
264 ast_string_field_free_memory(profile);
267 static struct http_route *unref_route(struct http_route *route)
274 static int routes_hash_fn(const void *obj, const int flags)
276 const struct http_route *route = obj;
278 return ast_str_hash(route->uri);
281 static int routes_cmp_fn(void *obj, void *arg, int flags)
283 const struct http_route *route1 = obj, *route2 = arg;
285 return !strcmp(route1->uri, route2->uri) ? CMP_MATCH : 0;
288 static void route_destructor(void *obj)
290 struct http_route *route = obj;
292 ast_string_field_free_memory(route);
295 /*! \brief Read a TEXT file into a string and return the length */
296 static int load_file(const char *filename, char **ret)
301 if (!(f = fopen(filename, "r"))) {
306 fseek(f, 0, SEEK_END);
308 fseek(f, 0, SEEK_SET);
309 if (!(*ret = ast_malloc(len + 1)))
312 if (len != fread(*ret, sizeof(char), len, f)) {
325 /*! \brief Callback that is executed everytime an http request is received by this module */
326 static struct ast_str *phoneprov_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
328 struct http_route *route;
329 struct http_route search_route = {
332 struct ast_str *result = ast_str_create(512);
338 struct timeval tv = ast_tvnow();
341 if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER)))
344 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
346 if (!route->user) { /* Static file */
348 fd = open(path, O_RDONLY);
352 len = lseek(fd, 0, SEEK_END);
353 lseek(fd, 0, SEEK_SET);
355 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
360 ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
361 fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
362 "Server: Asterisk/%s\r\n"
364 "Connection: close\r\n"
365 "Cache-Control: no-cache, no-store\r\n"
366 "Content-Length: %d\r\n"
367 "Content-Type: %s\r\n\r\n",
368 ast_get_version(), buf, len, route->file->mime_type);
370 while ((len = read(fd, buf, sizeof(buf))) > 0)
371 fwrite(buf, 1, len, ser->f);
374 route = unref_route(route);
376 } else { /* Dynamic file */
380 len = load_file(path, &file);
382 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
391 /* XXX This is a hack -- maybe sum length of all variables in route->user->headp and add that? */
392 bufsize = len + VAR_BUF_SIZE;
394 /* malloc() instead of alloca() here, just in case the file is bigger than
395 * we have enough stack space for. */
396 if (!(tmp = ast_calloc(1, bufsize))) {
402 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
403 * the IP address we are listening on that the phone contacted for this config file */
404 if (ast_strlen_zero(global_server)) {
405 struct sockaddr name;
406 socklen_t namelen = sizeof(name);
409 if ((res = getsockname(ser->fd, &name, &namelen)))
410 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
412 struct ast_var_t *var;
414 if ((var = ast_var_assign("SERVER", ast_inet_ntoa(((struct sockaddr_in *)&name)->sin_addr))))
415 AST_LIST_INSERT_TAIL(route->user->headp, var, entries);
419 pbx_substitute_variables_varshead(route->user->headp, file, tmp, bufsize);
424 ast_str_append(&result, 0,
425 "Content-Type: %s\r\n"
426 "Content-length: %d\r\n"
428 "%s", route->file->mime_type, (int) strlen(tmp), tmp);
433 route = unref_route(route);
440 *title = strdup("Not Found");
442 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
445 route = unref_route(route);
447 *title = strdup("Internal Server Error");
449 return ast_http_error(500, "Internal Error", NULL, "An internal error has occured.");
452 /*! \brief Build a route structure and add it to the list of available http routes
453 \param pp_file File to link to the route
454 \param user User to link to the route (NULL means static route)
455 \param uri URI of the route
457 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
459 struct http_route *route;
461 if (!(route = ao2_alloc(sizeof(*route), route_destructor)))
464 if (ast_string_field_init(route, 32)) {
465 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
466 route = unref_route(route);
470 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
472 route->file = pp_file;
474 ao2_link(http_routes, route);
476 route = unref_route(route);
479 /*! \brief Build a phone profile and add it to the list of phone profiles
480 \param name the name of the profile
481 \param v ast_variable from parsing phoneprov.conf
483 static void build_profile(const char *name, struct ast_variable *v)
485 struct phone_profile *profile;
486 struct ast_var_t *var;
488 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor)))
491 if (ast_string_field_init(profile, 32)) {
492 profile = unref_profile(profile);
496 if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
497 profile = unref_profile(profile);
501 AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
502 AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
504 ast_string_field_set(profile, name, name);
505 for (; v; v = v->next) {
506 if (!strcasecmp(v->name, "mime_type")) {
507 ast_string_field_set(profile, default_mime_type, v->value);
508 } else if (!strcasecmp(v->name, "setvar")) {
509 struct ast_var_t *var;
510 char *value_copy = ast_strdupa(v->value);
512 AST_DECLARE_APP_ARGS(args,
513 AST_APP_ARG(varname);
517 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
519 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
521 args.varname = ast_strip(args.varname);
522 args.varval = ast_strip(args.varval);
523 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
525 if ((var = ast_var_assign(args.varname, args.varval)))
526 AST_LIST_INSERT_TAIL(profile->headp, var, entries);
528 } else if (!strcasecmp(v->name, "staticdir")) {
529 ast_string_field_set(profile, staticdir, v->value);
531 struct phoneprov_file *pp_file;
532 char *file_extension;
533 char *value_copy = ast_strdupa(v->value);
535 AST_DECLARE_APP_ARGS(args,
536 AST_APP_ARG(filename);
537 AST_APP_ARG(mimetype);
540 if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) {
541 profile = unref_profile(profile);
544 if (ast_string_field_init(pp_file, 32)) {
546 profile = unref_profile(profile);
550 if ((file_extension = strrchr(pp_file->format, '.')))
553 AST_STANDARD_APP_ARGS(args, value_copy);
555 /* Mime type order of preference
556 * 1) Specific mime-type defined for file in profile
557 * 2) Mime determined by extension
558 * 3) Default mime type specified in profile
561 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"))));
563 if (!strcasecmp(v->name, "static_file")) {
564 ast_string_field_set(pp_file, format, args.filename);
565 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
566 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
567 /* Add a route for the static files, as their filenames won't change per-user */
568 build_route(pp_file, NULL, NULL);
570 ast_string_field_set(pp_file, format, v->name);
571 ast_string_field_set(pp_file, template, args.filename);
572 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
577 /* Append the global variables to the variables list for this profile.
578 * This is for convenience later, when we need to provide a single
579 * variable list for use in substitution. */
580 AST_LIST_TRAVERSE(&global_variables, var, entries) {
581 struct ast_var_t *new_var;
582 if ((new_var = ast_var_assign(var->name, var->value)))
583 AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
586 ao2_link(profiles, profile);
588 profile = unref_profile(profile);
591 /*! \brief Free all memory associated with a user */
592 static void delete_user(struct user *user)
594 struct ast_var_t *var;
596 while ((var = AST_LIST_REMOVE_HEAD(user->headp, entries)))
599 ast_free(user->headp);
600 ast_string_field_free_memory(user);
601 user->profile = unref_profile(user->profile);
605 /*! \brief Destroy entire user list */
606 static void delete_users(void)
610 AST_RWLIST_WRLOCK(&users);
611 while ((user = AST_LIST_REMOVE_HEAD(&users, entry)))
613 AST_RWLIST_UNLOCK(&users);
616 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
617 \param headp pointer to list of user variables
618 \param zone A time zone. NULL sets variables based on timezone of the machine
620 static void set_timezone_variables(struct varshead *headp, const char *zone)
626 struct ast_tm tm_info;
629 struct ast_var_t *var;
633 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
634 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
635 var = ast_var_assign("TZOFFSET", buffer);
637 AST_LIST_INSERT_TAIL(headp, var, entries);
642 if ((var = ast_var_assign("DST_ENABLE", "1")))
643 AST_LIST_INSERT_TAIL(headp, var, entries);
645 tv.tv_sec = dststart;
646 ast_localtime(&tv, &tm_info, zone);
648 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
649 if ((var = ast_var_assign("DST_START_MONTH", buffer)))
650 AST_LIST_INSERT_TAIL(headp, var, entries);
652 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
653 if ((var = ast_var_assign("DST_START_MDAY", buffer)))
654 AST_LIST_INSERT_TAIL(headp, var, entries);
656 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
657 if ((var = ast_var_assign("DST_START_HOUR", buffer)))
658 AST_LIST_INSERT_TAIL(headp, var, entries);
661 ast_localtime(&tv, &tm_info, zone);
663 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
664 if ((var = ast_var_assign("DST_END_MONTH", buffer)))
665 AST_LIST_INSERT_TAIL(headp, var, entries);
667 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
668 if ((var = ast_var_assign("DST_END_MDAY", buffer)))
669 AST_LIST_INSERT_TAIL(headp, var, entries);
671 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
672 if ((var = ast_var_assign("DST_END_HOUR", buffer)))
673 AST_LIST_INSERT_TAIL(headp, var, entries);
676 /*! \brief Build and return a user structure based on gathered config data */
677 static struct user *build_user(struct ast_config *cfg, const char *name, const char *mac, struct phone_profile *profile)
680 struct ast_var_t *var;
684 if (!(user = ast_calloc(1, sizeof(*user)))) {
685 profile = unref_profile(profile);
689 if (!(user->headp = ast_calloc(1, sizeof(*user->headp)))) {
690 profile = unref_profile(profile);
695 if (ast_string_field_init(user, 32)) {
696 profile = unref_profile(profile);
701 ast_string_field_set(user, name, name);
702 ast_string_field_set(user, macaddress, mac);
703 user->profile = profile; /* already ref counted by find_profile */
705 for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
706 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
708 /* If we didn't get a USERNAME variable, set it to the user->name */
709 if (i == PP_USERNAME && !tmp) {
710 if ((var = ast_var_assign(pp_variable_list[PP_USERNAME].template_var, user->name))) {
711 AST_LIST_INSERT_TAIL(user->headp, var, entries);
714 } else if (i == PP_TIMEZONE) {
715 /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
716 set_timezone_variables(user->headp, tmp);
719 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp)))
720 AST_LIST_INSERT_TAIL(user->headp, var, entries);
723 if (!ast_strlen_zero(global_server)) {
724 if ((var = ast_var_assign("SERVER", global_server)))
725 AST_LIST_INSERT_TAIL(user->headp, var, entries);
728 if (!ast_strlen_zero(global_serverport)) {
729 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
730 AST_LIST_INSERT_TAIL(user->headp, var, entries);
733 /* Append profile variables here, and substitute variables on profile
734 * setvars, so that we can use user specific variables in them */
735 AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
736 char expand_buf[VAR_BUF_SIZE] = {0,};
737 struct ast_var_t *var2;
739 pbx_substitute_variables_varshead(user->headp, var->value, expand_buf, sizeof(expand_buf));
740 if ((var2 = ast_var_assign(var->name, expand_buf)))
741 AST_LIST_INSERT_TAIL(user->headp, var2, entries);
747 /*! \brief Add an http route for dynamic files attached to the profile of the user */
748 static int build_user_routes(struct user *user)
750 struct phoneprov_file *pp_file;
752 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
753 char expand_buf[VAR_BUF_SIZE] = { 0, };
755 pbx_substitute_variables_varshead(user->headp, pp_file->format, expand_buf, sizeof(expand_buf));
756 build_route(pp_file, user, expand_buf);
762 /* \brief Parse config files and create appropriate structures */
763 static int set_config(void)
765 struct ast_config *cfg;
767 struct ast_variable *v;
768 struct ast_flags config_flags = { 0 };
770 /* Try to grab the port from sip.conf. If we don't get it here, we'll set it
771 * to whatever is set in phoneprov.conf or default to 5060 */
772 if ((cfg = ast_config_load("sip.conf", config_flags))) {
773 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
774 ast_config_destroy(cfg);
777 if (!(cfg = ast_config_load("phoneprov.conf", config_flags))) {
778 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
783 while ((cat = ast_category_browse(cfg, cat))) {
784 if (!strcasecmp(cat, "general")) {
785 for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
786 if (!strcasecmp(v->name, "serveraddr"))
787 ast_copy_string(global_server, v->value, sizeof(global_server));
788 else if (!strcasecmp(v->name, "serveriface")) {
790 lookup_iface(v->value, &addr);
791 ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
792 } else if (!strcasecmp(v->name, "serverport"))
793 ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
794 else if (!strcasecmp(v->name, "default_profile"))
795 ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
798 build_profile(cat, ast_variable_browse(cfg, cat));
801 ast_config_destroy(cfg);
803 if (!(cfg = ast_config_load("users.conf", config_flags))) {
804 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
809 while ((cat = ast_category_browse(cfg, cat))) {
810 const char *tmp, *mac;
812 struct phone_profile *profile;
813 struct ast_var_t *var;
815 if (!strcasecmp(cat, "general")) {
816 for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
817 if (!strcasecmp(v->name, "vmexten")) {
818 if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value)))
819 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
821 if (!strcasecmp(v->name, "localextenlength")) {
822 if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
823 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
828 if (!strcasecmp(cat, "authentication"))
831 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
834 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
835 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
839 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
840 if (ast_strlen_zero(tmp)) {
841 ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
845 if (!(profile = find_profile(tmp))) {
846 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
850 if (!(user = build_user(cfg, cat, mac, profile))) {
851 ast_log(LOG_WARNING, "Could not create user %s - skipping.\n", cat);
855 if (build_user_routes(user)) {
856 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->name);
861 AST_RWLIST_WRLOCK(&users);
862 AST_RWLIST_INSERT_TAIL(&users, user, entry);
863 AST_RWLIST_UNLOCK(&users);
866 ast_config_destroy(cfg);
871 /*! \brief Delete all http routes, freeing their memory */
872 static void delete_routes(void)
874 struct ao2_iterator i;
875 struct http_route *route;
877 i = ao2_iterator_init(http_routes, 0);
878 while ((route = ao2_iterator_next(&i))) {
879 ao2_unlink(http_routes, route);
880 route = unref_route(route);
884 /*! \brief Delete all phone profiles, freeing their memory */
885 static void delete_profiles(void)
887 struct ao2_iterator i;
888 struct phone_profile *profile;
890 i = ao2_iterator_init(profiles, 0);
891 while ((profile = ao2_iterator_next(&i))) {
892 ao2_unlink(profiles, profile);
893 profile = unref_profile(profile);
897 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
898 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
900 char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
902 AST_DECLARE_APP_ARGS(args,
904 AST_APP_ARG(exclude_mac);
906 AST_STANDARD_APP_ARGS(args, data);
908 /* Fix data by turning %{ into ${ */
909 while ((tmp = strstr(args.string, "%{")))
912 AST_RWLIST_RDLOCK(&users);
913 AST_RWLIST_TRAVERSE(&users, user, entry) {
914 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac))
916 pbx_substitute_variables_varshead(user->headp, args.string, expand_buf, sizeof(expand_buf));
917 ast_build_string(&buf, &len, expand_buf);
919 AST_RWLIST_UNLOCK(&users);
924 static struct ast_custom_function pp_each_user_function = {
925 .name = "PP_EACH_USER",
926 .synopsis = "Generate a string for each phoneprov user",
927 .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
929 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
930 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
931 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
933 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
934 .read = pp_each_user_exec,
937 /*! \brief CLI command to list static and dynamic routes */
938 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
940 #define FORMAT "%-40.40s %-30.30s\n"
941 struct ao2_iterator i;
942 struct http_route *route;
946 e->command = "phoneprov show routes";
948 "Usage: phoneprov show routes\n"
949 " Lists all registered phoneprov http routes.\n";
955 /* This currently iterates over routes twice, but it is the only place I've needed
956 * to really separate static an dynamic routes, so I've just left it this way. */
957 ast_cli(a->fd, "Static routes\n\n");
958 ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
959 i = ao2_iterator_init(http_routes, 0);
960 while ((route = ao2_iterator_next(&i))) {
962 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
963 route = unref_route(route);
966 ast_cli(a->fd, "\nDynamic routes\n\n");
967 ast_cli(a->fd, FORMAT, "Relative URI", "Template");
969 i = ao2_iterator_init(http_routes, 0);
970 while ((route = ao2_iterator_next(&i))) {
972 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
973 route = unref_route(route);
979 static struct ast_cli_entry pp_cli[] = {
980 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
983 static struct ast_http_uri phoneprovuri = {
984 .callback = phoneprov_callback,
985 .description = "Asterisk HTTP Phone Provisioning Tool",
990 static int load_module(void)
992 profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
994 http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
996 AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
998 ast_custom_function_register(&pp_each_user_function);
999 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
1002 ast_http_uri_link(&phoneprovuri);
1007 static int unload_module(void)
1009 struct ast_var_t *var;
1011 ast_http_uri_unlink(&phoneprovuri);
1012 ast_custom_function_unregister(&pp_each_user_function);
1013 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1018 ao2_ref(profiles, -1);
1019 ao2_ref(http_routes, -1);
1021 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries)))
1022 ast_var_delete(var);
1027 static int reload(void)
1037 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1038 .load = load_module,
1039 .unload = unload_module,