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] = "5060"; /*!< 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 pbx_substitute_variables_varshead(route->user->headp, file, tmp, bufsize);
405 ast_str_append(&result, 0,
406 "Content-Type: %s\r\n"
407 "Content-length: %d\r\n"
409 "%s", route->file->mime_type, (int) strlen(tmp), tmp);
414 route = unref_route(route);
421 *title = strdup("Not Found");
423 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
426 route = unref_route(route);
428 *title = strdup("Internal Server Error");
430 return ast_http_error(500, "Internal Error", NULL, "An internal error has occured.");
433 /*! \brief Build a route structure and add it to the list of available http routes
434 \param pp_file File to link to the route
435 \param user User to link to the route (NULL means static route)
436 \param uri URI of the route
438 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
440 struct http_route *route;
442 if (!(route = ao2_alloc(sizeof(*route), route_destructor)))
445 if (ast_string_field_init(route, 32)) {
446 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
447 route = unref_route(route);
451 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
453 route->file = pp_file;
455 ao2_link(http_routes, route);
457 route = unref_route(route);
460 /*! \brief Build a phone profile and add it to the list of phone profiles
461 \param name the name of the profile
462 \param v ast_variable from parsing phoneprov.conf
464 static void build_profile(const char *name, struct ast_variable *v)
466 struct phone_profile *profile;
467 struct ast_var_t *var;
469 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor)))
472 if (ast_string_field_init(profile, 32)) {
473 profile = unref_profile(profile);
477 if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
478 profile = unref_profile(profile);
482 AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
483 AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
485 ast_string_field_set(profile, name, name);
486 for (; v; v = v->next) {
487 if (!strcasecmp(v->name, "mime_type")) {
488 ast_string_field_set(profile, default_mime_type, v->value);
489 } else if (!strcasecmp(v->name, "setvar")) {
490 struct ast_var_t *var;
491 char *value_copy = ast_strdupa(v->value);
493 AST_DECLARE_APP_ARGS(args,
494 AST_APP_ARG(varname);
498 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
500 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
502 args.varname = ast_strip(args.varname);
503 args.varval = ast_strip(args.varval);
504 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
506 if ((var = ast_var_assign(args.varname, args.varval)))
507 AST_LIST_INSERT_TAIL(profile->headp, var, entries);
509 } else if (!strcasecmp(v->name, "staticdir")) {
510 ast_string_field_set(profile, staticdir, v->value);
512 struct phoneprov_file *pp_file;
513 char *file_extension;
514 char *value_copy = ast_strdupa(v->value);
516 AST_DECLARE_APP_ARGS(args,
517 AST_APP_ARG(filename);
518 AST_APP_ARG(mimetype);
521 if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) {
522 profile = unref_profile(profile);
525 if (ast_string_field_init(pp_file, 32)) {
527 profile = unref_profile(profile);
531 if ((file_extension = strrchr(pp_file->format, '.')))
534 AST_STANDARD_APP_ARGS(args, value_copy);
536 /* Mime type order of preference
537 * 1) Specific mime-type defined for file in profile
538 * 2) Mime determined by extension
539 * 3) Default mime type specified in profile
542 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"))));
544 if (!strcasecmp(v->name, "static_file")) {
545 ast_string_field_set(pp_file, format, args.filename);
546 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
547 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
548 /* Add a route for the static files, as their filenames won't change per-user */
549 build_route(pp_file, NULL, NULL);
551 ast_string_field_set(pp_file, format, v->name);
552 ast_string_field_set(pp_file, template, args.filename);
553 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
558 /* Append the global variables to the variables list for this profile.
559 * This is for convenience later, when we need to provide a single
560 * variable list for use in substitution. */
561 AST_LIST_TRAVERSE(&global_variables, var, entries) {
562 struct ast_var_t *new_var;
563 if ((new_var = ast_var_assign(var->name, var->value)))
564 AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
567 ao2_link(profiles, profile);
569 profile = unref_profile(profile);
572 /*! \brief Free all memory associated with a user */
573 static void delete_user(struct user *user)
575 struct ast_var_t *var;
577 while ((var = AST_LIST_REMOVE_HEAD(user->headp, entries)))
580 ast_free(user->headp);
581 ast_string_field_free_memory(user);
582 user->profile = unref_profile(user->profile);
586 /*! \brief Destroy entire user list */
587 static void delete_users(void)
591 AST_RWLIST_WRLOCK(&users);
592 while ((user = AST_LIST_REMOVE_HEAD(&users, entry)))
594 AST_RWLIST_UNLOCK(&users);
597 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
598 \param headp pointer to list of user variables
599 \param zone A time zone. NULL sets variables based on timezone of the machine
601 static void set_timezone_variables(struct varshead *headp, const char *zone)
607 struct ast_tm tm_info;
610 struct ast_var_t *var;
614 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
615 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
616 var = ast_var_assign("TZOFFSET", buffer);
618 AST_LIST_INSERT_TAIL(headp, var, entries);
623 if ((var = ast_var_assign("DST_ENABLE", "1")))
624 AST_LIST_INSERT_TAIL(headp, var, entries);
626 tv.tv_sec = dststart;
627 ast_localtime(&tv, &tm_info, zone);
629 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
630 if ((var = ast_var_assign("DST_START_MONTH", buffer)))
631 AST_LIST_INSERT_TAIL(headp, var, entries);
633 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
634 if ((var = ast_var_assign("DST_START_MDAY", buffer)))
635 AST_LIST_INSERT_TAIL(headp, var, entries);
637 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
638 if ((var = ast_var_assign("DST_START_HOUR", buffer)))
639 AST_LIST_INSERT_TAIL(headp, var, entries);
642 ast_localtime(&tv, &tm_info, zone);
644 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
645 if ((var = ast_var_assign("DST_END_MONTH", buffer)))
646 AST_LIST_INSERT_TAIL(headp, var, entries);
648 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
649 if ((var = ast_var_assign("DST_END_MDAY", buffer)))
650 AST_LIST_INSERT_TAIL(headp, var, entries);
652 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
653 if ((var = ast_var_assign("DST_END_HOUR", buffer)))
654 AST_LIST_INSERT_TAIL(headp, var, entries);
657 /*! \brief Build and return a user structure based on gathered config data */
658 static struct user *build_user(struct ast_config *cfg, const char *name, const char *mac, struct phone_profile *profile)
661 struct ast_var_t *var;
665 if (!(user = ast_calloc(1, sizeof(*user)))) {
666 profile = unref_profile(profile);
670 if (!(user->headp = ast_calloc(1, sizeof(*user->headp)))) {
671 profile = unref_profile(profile);
676 if (ast_string_field_init(user, 32)) {
677 profile = unref_profile(profile);
682 ast_string_field_set(user, name, name);
683 ast_string_field_set(user, macaddress, mac);
684 user->profile = profile; /* already ref counted by find_profile */
686 for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
687 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
689 if (i == PP_TIMEZONE) {
690 /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
691 set_timezone_variables(user->headp, tmp);
694 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp)))
695 AST_LIST_INSERT_TAIL(user->headp, var, entries);
698 if (!ast_strlen_zero(global_server)) {
699 if ((var = ast_var_assign("SERVER", global_server)))
700 AST_LIST_INSERT_TAIL(user->headp, var, entries);
701 if (!ast_strlen_zero(global_serverport)) {
702 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
703 AST_LIST_INSERT_TAIL(user->headp, var, entries);
707 /* Append profile variables here, and substitute variables on profile
708 * setvars, so that we can use user specific variables in them */
709 AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
710 char expand_buf[VAR_BUF_SIZE] = {0,};
711 struct ast_var_t *var2;
713 pbx_substitute_variables_varshead(user->headp, var->value, expand_buf, sizeof(expand_buf));
714 if ((var2 = ast_var_assign(var->name, expand_buf)))
715 AST_LIST_INSERT_TAIL(user->headp, var2, entries);
721 /*! \brief Add an http route for dynamic files attached to the profile of the user */
722 static int build_user_routes(struct user *user)
724 struct phoneprov_file *pp_file;
726 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
727 char expand_buf[VAR_BUF_SIZE] = { 0, };
729 pbx_substitute_variables_varshead(user->headp, pp_file->format, expand_buf, sizeof(expand_buf));
730 build_route(pp_file, user, expand_buf);
736 /* \brief Parse config files and create appropriate structures */
737 static int set_config(void)
739 struct ast_config *phoneprov_cfg, *users_cfg;
741 struct ast_variable *v;
742 struct ast_flags config_flags = { 0 };
744 if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))) {
745 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
750 while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
751 if (!strcasecmp(cat, "general")) {
752 for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
753 if (!strcasecmp(v->name, "serveraddr"))
754 ast_copy_string(global_server, v->value, sizeof(global_server));
755 else if (!strcasecmp(v->name, "serveriface")) {
757 lookup_iface(v->value, &addr);
758 ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
759 } else if (!strcasecmp(v->name, "serverport"))
760 ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
761 else if (!strcasecmp(v->name, "default_profile"))
762 ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
764 if (ast_strlen_zero(global_server))
765 ast_log(LOG_WARNING, "No serveraddr/serveriface set in phoneprov.conf. Breakage likely.\n");
767 build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
770 ast_config_destroy(phoneprov_cfg);
772 if (!(users_cfg = ast_config_load("users.conf", config_flags))) {
773 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
778 while ((cat = ast_category_browse(users_cfg, cat))) {
779 const char *tmp, *mac;
781 struct phone_profile *profile;
782 struct ast_var_t *var;
784 if (!strcasecmp(cat, "general")) {
785 for (v = ast_variable_browse(users_cfg, cat); v; v = v->next) {
786 if (!strcasecmp(v->name, "vmexten")) {
787 if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value)))
788 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
790 if (!strcasecmp(v->name, "localextenlength")) {
791 if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
792 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
797 if (!strcasecmp(cat, "authentication"))
800 if (!((tmp = ast_variable_retrieve(users_cfg, cat, "autoprov")) && ast_true(tmp)))
803 if (!(mac = ast_variable_retrieve(users_cfg, cat, "macaddress"))) {
804 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
808 tmp = S_OR(ast_variable_retrieve(users_cfg, cat, "profile"), global_default_profile);
809 if (ast_strlen_zero(tmp)) {
810 ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
814 if (!(profile = find_profile(tmp))) {
815 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
819 if (!(user = build_user(users_cfg, cat, mac, profile))) {
820 ast_log(LOG_WARNING, "Could not create user %s - skipping.\n", cat);
824 if (build_user_routes(user)) {
825 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->name);
830 AST_RWLIST_WRLOCK(&users);
831 AST_RWLIST_INSERT_TAIL(&users, user, entry);
832 AST_RWLIST_UNLOCK(&users);
835 ast_config_destroy(users_cfg);
840 /*! \brief Delete all http routes, freeing their memory */
841 static void delete_routes(void)
843 struct ao2_iterator i;
844 struct http_route *route;
846 i = ao2_iterator_init(http_routes, 0);
847 while ((route = ao2_iterator_next(&i))) {
848 ao2_unlink(http_routes, route);
849 route = unref_route(route);
853 /*! \brief Delete all phone profiles, freeing their memory */
854 static void delete_profiles(void)
856 struct ao2_iterator i;
857 struct phone_profile *profile;
859 i = ao2_iterator_init(profiles, 0);
860 while ((profile = ao2_iterator_next(&i))) {
861 ao2_unlink(profiles, profile);
862 profile = unref_profile(profile);
866 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
867 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
869 char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
871 AST_DECLARE_APP_ARGS(args,
873 AST_APP_ARG(exclude_mac);
875 AST_STANDARD_APP_ARGS(args, data);
877 /* Fix data by turning %{ into ${ */
878 while ((tmp = strstr(args.string, "%{")))
881 AST_RWLIST_RDLOCK(&users);
882 AST_RWLIST_TRAVERSE(&users, user, entry) {
883 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac))
885 pbx_substitute_variables_varshead(user->headp, args.string, expand_buf, sizeof(expand_buf));
886 ast_build_string(&buf, &len, expand_buf);
888 AST_RWLIST_UNLOCK(&users);
893 static struct ast_custom_function pp_each_user_function = {
894 .name = "PP_EACH_USER",
895 .synopsis = "Generate a string for each phoneprov user",
896 .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
898 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
899 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
900 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
902 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
903 .read = pp_each_user_exec,
906 /*! \brief CLI command to list static and dynamic routes */
907 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
909 #define FORMAT "%-40.40s %-30.30s\n"
910 struct ao2_iterator i;
911 struct http_route *route;
915 e->command = "phoneprov show routes";
917 "Usage: phoneprov show routes\n"
918 " Lists all registered phoneprov http routes.\n";
924 /* This currently iterates over routes twice, but it is the only place I've needed
925 * to really separate static an dynamic routes, so I've just left it this way. */
926 ast_cli(a->fd, "Static routes\n\n");
927 ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
928 i = ao2_iterator_init(http_routes, 0);
929 while ((route = ao2_iterator_next(&i))) {
931 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
932 route = unref_route(route);
935 ast_cli(a->fd, "\nDynamic routes\n\n");
936 ast_cli(a->fd, FORMAT, "Relative URI", "Template");
938 i = ao2_iterator_init(http_routes, 0);
939 while ((route = ao2_iterator_next(&i))) {
941 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
942 route = unref_route(route);
948 static struct ast_cli_entry pp_cli[] = {
949 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
952 static struct ast_http_uri phoneprovuri = {
953 .callback = phoneprov_callback,
954 .description = "Asterisk HTTP Phone Provisioning Tool",
959 static int load_module(void)
961 profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
963 http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
965 AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
967 ast_custom_function_register(&pp_each_user_function);
968 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
971 ast_http_uri_link(&phoneprovuri);
976 static int unload_module(void)
978 struct ast_var_t *var;
980 ast_http_uri_unlink(&phoneprovuri);
981 ast_custom_function_unregister(&pp_each_user_function);
982 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
987 ao2_ref(profiles, -1);
988 ao2_ref(http_routes, -1);
990 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries)))
996 static int reload(void)
1006 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1007 .load = load_module,
1008 .unload = unload_module,