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 * \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 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 96773 $")
37 #include "asterisk/file.h"
38 #include "asterisk/paths.h"
39 #include "asterisk/pbx.h"
40 #include "asterisk/cli.h"
41 #include "asterisk/module.h"
42 #include "asterisk/http.h"
43 #include "asterisk/utils.h"
44 #include "asterisk/app.h"
45 #include "asterisk/strings.h"
46 #include "asterisk/stringfields.h"
47 #include "asterisk/options.h"
48 #include "asterisk/config.h"
49 #include "asterisk/acl.h"
50 #include "asterisk/astobj2.h"
51 #include "asterisk/version.h"
54 #define MAX_PROFILE_BUCKETS 1
55 #define MAX_ROUTE_BUCKETS 1
57 #define MAX_PROFILE_BUCKETS 17
58 #define MAX_ROUTE_BUCKETS 563
59 #endif /* LOW_MEMORY */
61 #define VAR_BUF_SIZE 4096
63 /*! \brief for use in lookup_iface */
64 static struct in_addr __ourip = { .s_addr = 0x00000000, };
66 /* \note This enum and the pp_variable_list must be in the same order or
67 * bad things happen! */
76 PP_VAR_LIST_LENGTH, /* This entry must always be the last in the list */
79 /*! \brief Lookup table to translate between users.conf property names and
80 * variables for use in phoneprov templates */
81 static const struct pp_variable_lookup {
83 const char * const user_var;
84 const char * const template_var;
85 } pp_variable_list[] = {
86 { PP_MACADDRESS, "macaddress", "MAC" },
87 { PP_USERNAME, "username", "USERNAME" },
88 { PP_FULLNAME, "fullname", "DISPLAY_NAME" },
89 { PP_SECRET, "secret", "SECRET" },
90 { PP_LABEL, "label", "LABEL" },
91 { PP_CALLERID, "cid_number", "CALLERID" },
92 { PP_TIMEZONE, "timezone", "TIMEZONE" },
95 /*! \brief structure to hold file data */
96 struct phoneprov_file {
97 AST_DECLARE_STRING_FIELDS(
98 AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */
99 AST_STRING_FIELD(template); /*!< Template/physical file location */
100 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
102 AST_LIST_ENTRY(phoneprov_file) entry;
105 /*! \brief structure to hold phone profiles read from phoneprov.conf */
106 struct phone_profile {
107 AST_DECLARE_STRING_FIELDS(
108 AST_STRING_FIELD(name); /*!< Name of phone profile */
109 AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */
110 AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */
112 struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
113 AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */
114 AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */
117 /*! \brief structure to hold users read from users.conf */
119 AST_DECLARE_STRING_FIELDS(
120 AST_STRING_FIELD(name); /*!< Name of user */
121 AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */
123 struct phone_profile *profile; /*!< Profile the phone belongs to */
124 struct varshead *headp; /*!< List of variables to substitute into templates */
125 AST_LIST_ENTRY(user) entry;
128 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
130 AST_DECLARE_STRING_FIELDS(
131 AST_STRING_FIELD(uri); /*!< The URI requested */
133 struct phoneprov_file *file; /*!< The file that links to the URI */
134 struct user *user; /*!< The user that has variables to substitute into the file
135 * NULL in the case of a static route */
138 static struct ao2_container *profiles;
139 static struct ao2_container *http_routes;
140 static AST_RWLIST_HEAD_STATIC(users, user);
142 /*! \brief Extensions whose mime types we think we know */
147 { "png", "image/png" },
148 { "xml", "text/xml" },
149 { "jpg", "image/jpeg" },
150 { "js", "application/x-javascript" },
151 { "wav", "audio/x-wav" },
152 { "mp3", "audio/mpeg" },
155 char global_server[80] = ""; /*!< Server to substitute into templates */
156 char global_serverport[6] = ""; /*!< Server port to substitute into templates */
157 char global_default_profile[80] = ""; /*!< Default profile to use if one isn't specified */
159 /*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
160 struct varshead global_variables;
162 /*! \brief Return mime type based on extension */
163 static char *ftype2mtype(const char *ftype)
167 if (ast_strlen_zero(ftype))
170 for (x = 0;x < ARRAY_LEN(mimetypes);x++) {
171 if (!strcasecmp(ftype, mimetypes[x].ext))
172 return mimetypes[x].mtype;
178 /* iface is the interface (e.g. eth0); address is the return value */
179 static int lookup_iface(const char *iface, struct in_addr *address)
183 struct sockaddr_in *sin;
185 memset(&ifr, 0, sizeof(ifr));
186 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
188 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
190 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
194 res = ioctl(mysock, SIOCGIFADDR, &ifr);
199 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
200 memcpy(address, &__ourip, sizeof(__ourip));
203 sin = (struct sockaddr_in *)&ifr.ifr_addr;
204 memcpy(address, &sin->sin_addr, sizeof(*address));
209 static struct phone_profile *unref_profile(struct phone_profile *prof)
216 /*! \brief Return a phone profile looked up by name */
217 static struct phone_profile *find_profile(const char *name)
219 struct phone_profile tmp = {
223 return ao2_find(profiles, &tmp, OBJ_POINTER);
226 static int profile_hash_fn(const void *obj, const int flags)
228 const struct phone_profile *profile = obj;
230 return ast_str_hash(profile->name);
233 static int profile_cmp_fn(void *obj, void *arg, int flags)
235 const struct phone_profile *profile1 = obj, *profile2 = arg;
237 return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH : 0;
240 static void delete_file(struct phoneprov_file *file)
242 ast_string_field_free_memory(file);
246 static void profile_destructor(void *obj)
248 struct phone_profile *profile = obj;
249 struct phoneprov_file *file;
250 struct ast_var_t *var;
252 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
255 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
258 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
261 free(profile->headp);
262 ast_string_field_free_memory(profile);
265 static struct http_route *unref_route(struct http_route *route)
272 static int routes_hash_fn(const void *obj, const int flags)
274 const struct http_route *route = obj;
276 return ast_str_hash(route->uri);
279 static int routes_cmp_fn(void *obj, void *arg, int flags)
281 const struct http_route *route1 = obj, *route2 = arg;
283 return !strcmp(route1->uri, route2->uri) ? CMP_MATCH : 0;
286 static void route_destructor(void *obj)
288 struct http_route *route = obj;
290 ast_string_field_free_memory(route);
293 /*! \brief Read a TEXT file into a string and return the length */
294 static int load_file(const char *filename, char **ret)
299 if (!(f = fopen(filename, "r"))) {
304 fseek(f, 0, SEEK_END);
306 fseek(f, 0, SEEK_SET);
307 if (!(*ret = ast_malloc(len + 1)))
310 if (len != fread(*ret, sizeof(char), len, f)) {
323 /*! \brief Callback that is executed everytime an http request is received by this module */
324 static struct ast_str *phoneprov_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
326 struct http_route *route;
327 struct http_route search_route = {
330 struct ast_str *result = ast_str_create(512);
336 struct timeval tv = ast_tvnow();
339 if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER)))
342 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
344 if (!route->user) { /* Static file */
346 fd = open(path, O_RDONLY);
350 len = lseek(fd, 0, SEEK_END);
351 lseek(fd, 0, SEEK_SET);
353 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
358 ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
359 fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
360 "Server: Asterisk/%s\r\n"
362 "Connection: close\r\n"
363 "Cache-Control: no-cache, no-store\r\n"
364 "Content-Length: %d\r\n"
365 "Content-Type: %s\r\n\r\n",
366 ast_get_version(), buf, len, route->file->mime_type);
368 while ((len = read(fd, buf, sizeof(buf))) > 0)
369 fwrite(buf, 1, len, ser->f);
372 route = unref_route(route);
374 } else { /* Dynamic file */
378 len = load_file(path, &file);
380 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
389 /* XXX This is a hack -- maybe sum length of all variables in route->user->headp and add that? */
390 bufsize = len + VAR_BUF_SIZE;
392 /* malloc() instead of alloca() here, just in case the file is bigger than
393 * we have enough stack space for. */
394 if (!(tmp = ast_calloc(1, bufsize))) {
400 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
401 * the IP address we are listening on that the phone contacted for this config file */
402 if (ast_strlen_zero(global_server)) {
403 struct sockaddr name;
404 socklen_t namelen = sizeof(name);
407 if ((res = getsockname(ser->fd, &name, &namelen)))
408 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
410 struct ast_var_t *var;
412 if ((var = ast_var_assign("SERVER", ast_inet_ntoa(((struct sockaddr_in *)&name)->sin_addr))))
413 AST_LIST_INSERT_TAIL(route->user->headp, var, entries);
417 pbx_substitute_variables_varshead(route->user->headp, file, tmp, bufsize);
422 ast_str_append(&result, 0,
423 "Content-Type: %s\r\n"
424 "Content-length: %d\r\n"
426 "%s", route->file->mime_type, (int) strlen(tmp), tmp);
431 route = unref_route(route);
438 *title = strdup("Not Found");
440 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
443 route = unref_route(route);
445 *title = strdup("Internal Server Error");
447 return ast_http_error(500, "Internal Error", NULL, "An internal error has occured.");
450 /*! \brief Build a route structure and add it to the list of available http routes
451 \param pp_file File to link to the route
452 \param user User to link to the route (NULL means static route)
453 \param uri URI of the route
455 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
457 struct http_route *route;
459 if (!(route = ao2_alloc(sizeof(*route), route_destructor)))
462 if (ast_string_field_init(route, 32)) {
463 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
464 route = unref_route(route);
468 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
470 route->file = pp_file;
472 ao2_link(http_routes, route);
474 route = unref_route(route);
477 /*! \brief Build a phone profile and add it to the list of phone profiles
478 \param name the name of the profile
479 \param v ast_variable from parsing phoneprov.conf
481 static void build_profile(const char *name, struct ast_variable *v)
483 struct phone_profile *profile;
484 struct ast_var_t *var;
486 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor)))
489 if (ast_string_field_init(profile, 32)) {
490 profile = unref_profile(profile);
494 if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
495 profile = unref_profile(profile);
499 AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
500 AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
502 ast_string_field_set(profile, name, name);
503 for (; v; v = v->next) {
504 if (!strcasecmp(v->name, "mime_type")) {
505 ast_string_field_set(profile, default_mime_type, v->value);
506 } else if (!strcasecmp(v->name, "setvar")) {
507 struct ast_var_t *var;
508 char *value_copy = ast_strdupa(v->value);
510 AST_DECLARE_APP_ARGS(args,
511 AST_APP_ARG(varname);
515 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
517 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
519 args.varname = ast_strip(args.varname);
520 args.varval = ast_strip(args.varval);
521 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
523 if ((var = ast_var_assign(args.varname, args.varval)))
524 AST_LIST_INSERT_TAIL(profile->headp, var, entries);
526 } else if (!strcasecmp(v->name, "staticdir")) {
527 ast_string_field_set(profile, staticdir, v->value);
529 struct phoneprov_file *pp_file;
530 char *file_extension;
531 char *value_copy = ast_strdupa(v->value);
533 AST_DECLARE_APP_ARGS(args,
534 AST_APP_ARG(filename);
535 AST_APP_ARG(mimetype);
538 if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) {
539 profile = unref_profile(profile);
542 if (ast_string_field_init(pp_file, 32)) {
544 profile = unref_profile(profile);
548 if ((file_extension = strrchr(pp_file->format, '.')))
551 AST_STANDARD_APP_ARGS(args, value_copy);
553 /* Mime type order of preference
554 * 1) Specific mime-type defined for file in profile
555 * 2) Mime determined by extension
556 * 3) Default mime type specified in profile
559 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"))));
561 if (!strcasecmp(v->name, "static_file")) {
562 ast_string_field_set(pp_file, format, args.filename);
563 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
564 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
565 /* Add a route for the static files, as their filenames won't change per-user */
566 build_route(pp_file, NULL, NULL);
568 ast_string_field_set(pp_file, format, v->name);
569 ast_string_field_set(pp_file, template, args.filename);
570 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
575 /* Append the global variables to the variables list for this profile.
576 * This is for convenience later, when we need to provide a single
577 * variable list for use in substitution. */
578 AST_LIST_TRAVERSE(&global_variables, var, entries) {
579 struct ast_var_t *new_var;
580 if ((new_var = ast_var_assign(var->name, var->value)))
581 AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
584 ao2_link(profiles, profile);
586 profile = unref_profile(profile);
589 /*! \brief Free all memory associated with a user */
590 static void delete_user(struct user *user)
592 struct ast_var_t *var;
594 while ((var = AST_LIST_REMOVE_HEAD(user->headp, entries)))
597 ast_free(user->headp);
598 ast_string_field_free_memory(user);
599 user->profile = unref_profile(user->profile);
603 /*! \brief Destroy entire user list */
604 static void delete_users(void)
608 AST_RWLIST_WRLOCK(&users);
609 while ((user = AST_LIST_REMOVE_HEAD(&users, entry)))
611 AST_RWLIST_UNLOCK(&users);
614 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
615 \param headp pointer to list of user variables
616 \param zone A time zone. NULL sets variables based on timezone of the machine
618 static void set_timezone_variables(struct varshead *headp, const char *zone)
624 struct ast_tm tm_info;
627 struct ast_var_t *var;
631 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
632 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
633 var = ast_var_assign("TZOFFSET", buffer);
635 AST_LIST_INSERT_TAIL(headp, var, entries);
640 if ((var = ast_var_assign("DST_ENABLE", "1")))
641 AST_LIST_INSERT_TAIL(headp, var, entries);
643 tv.tv_sec = dststart;
644 ast_localtime(&tv, &tm_info, zone);
646 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
647 if ((var = ast_var_assign("DST_START_MONTH", buffer)))
648 AST_LIST_INSERT_TAIL(headp, var, entries);
650 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
651 if ((var = ast_var_assign("DST_START_MDAY", buffer)))
652 AST_LIST_INSERT_TAIL(headp, var, entries);
654 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
655 if ((var = ast_var_assign("DST_START_HOUR", buffer)))
656 AST_LIST_INSERT_TAIL(headp, var, entries);
659 ast_localtime(&tv, &tm_info, zone);
661 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
662 if ((var = ast_var_assign("DST_END_MONTH", buffer)))
663 AST_LIST_INSERT_TAIL(headp, var, entries);
665 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
666 if ((var = ast_var_assign("DST_END_MDAY", buffer)))
667 AST_LIST_INSERT_TAIL(headp, var, entries);
669 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
670 if ((var = ast_var_assign("DST_END_HOUR", buffer)))
671 AST_LIST_INSERT_TAIL(headp, var, entries);
674 /*! \brief Build and return a user structure based on gathered config data */
675 static struct user *build_user(struct ast_config *cfg, const char *name, const char *mac, struct phone_profile *profile)
678 struct ast_var_t *var;
682 if (!(user = ast_calloc(1, sizeof(*user)))) {
683 profile = unref_profile(profile);
687 if (!(user->headp = ast_calloc(1, sizeof(*user->headp)))) {
688 profile = unref_profile(profile);
693 if (ast_string_field_init(user, 32)) {
694 profile = unref_profile(profile);
699 ast_string_field_set(user, name, name);
700 ast_string_field_set(user, macaddress, mac);
701 user->profile = profile; /* already ref counted by find_profile */
703 for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
704 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
706 if (i == PP_TIMEZONE) {
707 /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
708 set_timezone_variables(user->headp, tmp);
711 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp)))
712 AST_LIST_INSERT_TAIL(user->headp, var, entries);
715 if (!ast_strlen_zero(global_server)) {
716 if ((var = ast_var_assign("SERVER", global_server)))
717 AST_LIST_INSERT_TAIL(user->headp, var, entries);
720 if (!ast_strlen_zero(global_serverport)) {
721 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
722 AST_LIST_INSERT_TAIL(user->headp, var, entries);
725 /* Append profile variables here, and substitute variables on profile
726 * setvars, so that we can use user specific variables in them */
727 AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
728 char expand_buf[VAR_BUF_SIZE] = {0,};
729 struct ast_var_t *var2;
731 pbx_substitute_variables_varshead(user->headp, var->value, expand_buf, sizeof(expand_buf));
732 if ((var2 = ast_var_assign(var->name, expand_buf)))
733 AST_LIST_INSERT_TAIL(user->headp, var2, entries);
739 /*! \brief Add an http route for dynamic files attached to the profile of the user */
740 static int build_user_routes(struct user *user)
742 struct phoneprov_file *pp_file;
744 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
745 char expand_buf[VAR_BUF_SIZE] = { 0, };
747 pbx_substitute_variables_varshead(user->headp, pp_file->format, expand_buf, sizeof(expand_buf));
748 build_route(pp_file, user, expand_buf);
754 /* \brief Parse config files and create appropriate structures */
755 static int set_config(void)
757 struct ast_config *cfg;
759 struct ast_variable *v;
760 struct ast_flags config_flags = { 0 };
762 /* Try to grab the port from sip.conf. If we don't get it here, we'll set it
763 * to whatever is set in phoneprov.conf or default to 5060 */
764 if ((cfg = ast_config_load("sip.conf", config_flags))) {
765 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
766 ast_config_destroy(cfg);
769 if (!(cfg = ast_config_load("phoneprov.conf", config_flags))) {
770 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
775 while ((cat = ast_category_browse(cfg, cat))) {
776 if (!strcasecmp(cat, "general")) {
777 for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
778 if (!strcasecmp(v->name, "serveraddr"))
779 ast_copy_string(global_server, v->value, sizeof(global_server));
780 else if (!strcasecmp(v->name, "serveriface")) {
782 lookup_iface(v->value, &addr);
783 ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
784 } else if (!strcasecmp(v->name, "serverport"))
785 ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
786 else if (!strcasecmp(v->name, "default_profile"))
787 ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
790 build_profile(cat, ast_variable_browse(cfg, cat));
793 ast_config_destroy(cfg);
795 if (!(cfg = ast_config_load("users.conf", config_flags))) {
796 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
801 while ((cat = ast_category_browse(cfg, cat))) {
802 const char *tmp, *mac;
804 struct phone_profile *profile;
805 struct ast_var_t *var;
807 if (!strcasecmp(cat, "general")) {
808 for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
809 if (!strcasecmp(v->name, "vmexten")) {
810 if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value)))
811 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
813 if (!strcasecmp(v->name, "localextenlength")) {
814 if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
815 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
820 if (!strcasecmp(cat, "authentication"))
823 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
826 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
827 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
831 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
832 if (ast_strlen_zero(tmp)) {
833 ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
837 if (!(profile = find_profile(tmp))) {
838 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
842 if (!(user = build_user(cfg, cat, mac, profile))) {
843 ast_log(LOG_WARNING, "Could not create user %s - skipping.\n", cat);
847 if (build_user_routes(user)) {
848 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->name);
853 AST_RWLIST_WRLOCK(&users);
854 AST_RWLIST_INSERT_TAIL(&users, user, entry);
855 AST_RWLIST_UNLOCK(&users);
858 ast_config_destroy(cfg);
863 /*! \brief Delete all http routes, freeing their memory */
864 static void delete_routes(void)
866 struct ao2_iterator i;
867 struct http_route *route;
869 i = ao2_iterator_init(http_routes, 0);
870 while ((route = ao2_iterator_next(&i))) {
871 ao2_unlink(http_routes, route);
872 route = unref_route(route);
876 /*! \brief Delete all phone profiles, freeing their memory */
877 static void delete_profiles(void)
879 struct ao2_iterator i;
880 struct phone_profile *profile;
882 i = ao2_iterator_init(profiles, 0);
883 while ((profile = ao2_iterator_next(&i))) {
884 ao2_unlink(profiles, profile);
885 profile = unref_profile(profile);
889 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
890 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
892 char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
894 AST_DECLARE_APP_ARGS(args,
896 AST_APP_ARG(exclude_mac);
898 AST_STANDARD_APP_ARGS(args, data);
900 /* Fix data by turning %{ into ${ */
901 while ((tmp = strstr(args.string, "%{")))
904 AST_RWLIST_RDLOCK(&users);
905 AST_RWLIST_TRAVERSE(&users, user, entry) {
906 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac))
908 pbx_substitute_variables_varshead(user->headp, args.string, expand_buf, sizeof(expand_buf));
909 ast_build_string(&buf, &len, expand_buf);
911 AST_RWLIST_UNLOCK(&users);
916 static struct ast_custom_function pp_each_user_function = {
917 .name = "PP_EACH_USER",
918 .synopsis = "Generate a string for each phoneprov user",
919 .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
921 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
922 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
923 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
925 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
926 .read = pp_each_user_exec,
929 /*! \brief CLI command to list static and dynamic routes */
930 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
932 #define FORMAT "%-40.40s %-30.30s\n"
933 struct ao2_iterator i;
934 struct http_route *route;
938 e->command = "phoneprov show routes";
940 "Usage: phoneprov show routes\n"
941 " Lists all registered phoneprov http routes.\n";
947 /* This currently iterates over routes twice, but it is the only place I've needed
948 * to really separate static an dynamic routes, so I've just left it this way. */
949 ast_cli(a->fd, "Static routes\n\n");
950 ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
951 i = ao2_iterator_init(http_routes, 0);
952 while ((route = ao2_iterator_next(&i))) {
954 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
955 route = unref_route(route);
958 ast_cli(a->fd, "\nDynamic routes\n\n");
959 ast_cli(a->fd, FORMAT, "Relative URI", "Template");
961 i = ao2_iterator_init(http_routes, 0);
962 while ((route = ao2_iterator_next(&i))) {
964 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
965 route = unref_route(route);
971 static struct ast_cli_entry pp_cli[] = {
972 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
975 static struct ast_http_uri phoneprovuri = {
976 .callback = phoneprov_callback,
977 .description = "Asterisk HTTP Phone Provisioning Tool",
982 static int load_module(void)
984 profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
986 http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
988 AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
990 ast_custom_function_register(&pp_each_user_function);
991 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
994 ast_http_uri_link(&phoneprovuri);
999 static int unload_module(void)
1001 struct ast_var_t *var;
1003 ast_http_uri_unlink(&phoneprovuri);
1004 ast_custom_function_unregister(&pp_each_user_function);
1005 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1010 ao2_ref(profiles, -1);
1011 ao2_ref(http_routes, -1);
1013 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries)))
1014 ast_var_delete(var);
1019 static int reload(void)
1029 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1030 .load = load_module,
1031 .unload = unload_module,