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 char global_server[80] = ""; /*!< Server to substitute into templates */
170 char global_serverport[6] = ""; /*!< Server port to substitute into templates */
171 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 struct varshead global_variables;
176 /*! \brief Return mime type based on extension */
177 static char *ftype2mtype(const char *ftype)
181 if (ast_strlen_zero(ftype))
184 for (x = 0;x < ARRAY_LEN(mimetypes);x++) {
185 if (!strcasecmp(ftype, mimetypes[x].ext))
186 return mimetypes[x].mtype;
192 /* iface is the interface (e.g. eth0); address is the return value */
193 static int lookup_iface(const char *iface, struct in_addr *address)
197 struct sockaddr_in *sin;
199 memset(&ifr, 0, sizeof(ifr));
200 ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
202 mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
204 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
208 res = ioctl(mysock, SIOCGIFADDR, &ifr);
213 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
214 memcpy(address, &__ourip, sizeof(__ourip));
217 sin = (struct sockaddr_in *)&ifr.ifr_addr;
218 memcpy(address, &sin->sin_addr, sizeof(*address));
223 static struct phone_profile *unref_profile(struct phone_profile *prof)
230 /*! \brief Return a phone profile looked up by name */
231 static struct phone_profile *find_profile(const char *name)
233 struct phone_profile tmp = {
237 return ao2_find(profiles, &tmp, OBJ_POINTER);
240 static int profile_hash_fn(const void *obj, const int flags)
242 const struct phone_profile *profile = obj;
244 return ast_str_hash(profile->name);
247 static int profile_cmp_fn(void *obj, void *arg, int flags)
249 const struct phone_profile *profile1 = obj, *profile2 = arg;
251 return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH : 0;
254 static void delete_file(struct phoneprov_file *file)
256 ast_string_field_free_memory(file);
260 static void profile_destructor(void *obj)
262 struct phone_profile *profile = obj;
263 struct phoneprov_file *file;
264 struct ast_var_t *var;
266 while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
269 while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
272 while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
275 free(profile->headp);
276 ast_string_field_free_memory(profile);
279 static struct http_route *unref_route(struct http_route *route)
286 static int routes_hash_fn(const void *obj, const int flags)
288 const struct http_route *route = obj;
290 return ast_str_hash(route->uri);
293 static int routes_cmp_fn(void *obj, void *arg, int flags)
295 const struct http_route *route1 = obj, *route2 = arg;
297 return !strcmp(route1->uri, route2->uri) ? CMP_MATCH : 0;
300 static void route_destructor(void *obj)
302 struct http_route *route = obj;
304 ast_string_field_free_memory(route);
307 /*! \brief Read a TEXT file into a string and return the length */
308 static int load_file(const char *filename, char **ret)
313 if (!(f = fopen(filename, "r"))) {
318 fseek(f, 0, SEEK_END);
320 fseek(f, 0, SEEK_SET);
321 if (!(*ret = ast_malloc(len + 1)))
324 if (len != fread(*ret, sizeof(char), len, f)) {
337 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
338 \param headp pointer to list of user variables
339 \param zone A time zone. NULL sets variables based on timezone of the machine
341 static void set_timezone_variables(struct varshead *headp, const char *zone)
347 struct ast_tm tm_info;
350 struct ast_var_t *var;
354 ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
355 snprintf(buffer, sizeof(buffer), "%d", tzoffset);
356 var = ast_var_assign("TZOFFSET", buffer);
358 AST_LIST_INSERT_TAIL(headp, var, entries);
363 if ((var = ast_var_assign("DST_ENABLE", "1")))
364 AST_LIST_INSERT_TAIL(headp, var, entries);
366 tv.tv_sec = dststart;
367 ast_localtime(&tv, &tm_info, zone);
369 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
370 if ((var = ast_var_assign("DST_START_MONTH", buffer)))
371 AST_LIST_INSERT_TAIL(headp, var, entries);
373 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
374 if ((var = ast_var_assign("DST_START_MDAY", buffer)))
375 AST_LIST_INSERT_TAIL(headp, var, entries);
377 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
378 if ((var = ast_var_assign("DST_START_HOUR", buffer)))
379 AST_LIST_INSERT_TAIL(headp, var, entries);
382 ast_localtime(&tv, &tm_info, zone);
384 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
385 if ((var = ast_var_assign("DST_END_MONTH", buffer)))
386 AST_LIST_INSERT_TAIL(headp, var, entries);
388 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
389 if ((var = ast_var_assign("DST_END_MDAY", buffer)))
390 AST_LIST_INSERT_TAIL(headp, var, entries);
392 snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
393 if ((var = ast_var_assign("DST_END_HOUR", buffer)))
394 AST_LIST_INSERT_TAIL(headp, var, entries);
397 /*! \brief Callback that is executed everytime an http request is received by this module */
398 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)
400 struct http_route *route;
401 struct http_route search_route = {
404 struct ast_str *result = ast_str_create(512);
410 struct timeval tv = ast_tvnow();
413 if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) {
417 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
419 if (!route->user) { /* Static file */
421 fd = open(path, O_RDONLY);
426 len = lseek(fd, 0, SEEK_END);
427 lseek(fd, 0, SEEK_SET);
429 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
434 ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
435 fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
436 "Server: Asterisk/%s\r\n"
438 "Connection: close\r\n"
439 "Cache-Control: no-cache, no-store\r\n"
440 "Content-Length: %d\r\n"
441 "Content-Type: %s\r\n\r\n",
442 ast_get_version(), buf, len, route->file->mime_type);
444 while ((len = read(fd, buf, sizeof(buf))) > 0) {
445 fwrite(buf, 1, len, ser->f);
449 route = unref_route(route);
451 } else { /* Dynamic file */
455 len = load_file(path, &file);
457 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
469 /* XXX This is a hack -- maybe sum length of all variables in route->user->headp and add that? */
470 bufsize = len + VAR_BUF_SIZE;
472 /* malloc() instead of alloca() here, just in case the file is bigger than
473 * we have enough stack space for. */
474 if (!(tmp = ast_calloc(1, bufsize))) {
482 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
483 * the IP address we are listening on that the phone contacted for this config file */
484 if (ast_strlen_zero(global_server)) {
485 struct sockaddr name;
486 socklen_t namelen = sizeof(name);
489 if ((res = getsockname(ser->fd, &name, &namelen))) {
490 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
492 struct ast_var_t *var;
493 struct extension *exten_iter;
495 if ((var = ast_var_assign("SERVER", ast_inet_ntoa(((struct sockaddr_in *)&name)->sin_addr)))) {
496 AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
497 AST_LIST_INSERT_TAIL(exten_iter->headp, var, entries);
503 pbx_substitute_variables_varshead(AST_LIST_FIRST(&route->user->extensions)->headp, file, tmp, bufsize);
509 ast_str_append(&result, 0,
510 "Content-Type: %s\r\n"
511 "Content-length: %d\r\n"
513 "%s", route->file->mime_type, (int) strlen(tmp), tmp);
519 route = unref_route(route);
526 *title = strdup("Not Found");
528 return ast_http_error(404, "Not Found", NULL, "Nothing to see here. Move along.");
531 route = unref_route(route);
533 *title = strdup("Internal Server Error");
535 return ast_http_error(500, "Internal Error", NULL, "An internal error has occured.");
538 /*! \brief Build a route structure and add it to the list of available http routes
539 \param pp_file File to link to the route
540 \param user User to link to the route (NULL means static route)
541 \param uri URI of the route
543 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
545 struct http_route *route;
547 if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
551 if (ast_string_field_init(route, 32)) {
552 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
553 route = unref_route(route);
557 ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
559 route->file = pp_file;
561 ao2_link(http_routes, route);
563 route = unref_route(route);
566 /*! \brief Build a phone profile and add it to the list of phone profiles
567 \param name the name of the profile
568 \param v ast_variable from parsing phoneprov.conf
570 static void build_profile(const char *name, struct ast_variable *v)
572 struct phone_profile *profile;
573 struct ast_var_t *var;
575 if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
579 if (ast_string_field_init(profile, 32)) {
580 profile = unref_profile(profile);
584 if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
585 profile = unref_profile(profile);
589 AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
590 AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
592 ast_string_field_set(profile, name, name);
593 for (; v; v = v->next) {
594 if (!strcasecmp(v->name, "mime_type")) {
595 ast_string_field_set(profile, default_mime_type, v->value);
596 } else if (!strcasecmp(v->name, "setvar")) {
597 struct ast_var_t *var;
598 char *value_copy = ast_strdupa(v->value);
600 AST_DECLARE_APP_ARGS(args,
601 AST_APP_ARG(varname);
605 AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
607 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
609 args.varname = ast_strip(args.varname);
610 args.varval = ast_strip(args.varval);
611 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
613 if ((var = ast_var_assign(args.varname, args.varval)))
614 AST_LIST_INSERT_TAIL(profile->headp, var, entries);
616 } else if (!strcasecmp(v->name, "staticdir")) {
617 ast_string_field_set(profile, staticdir, v->value);
619 struct phoneprov_file *pp_file;
620 char *file_extension;
621 char *value_copy = ast_strdupa(v->value);
623 AST_DECLARE_APP_ARGS(args,
624 AST_APP_ARG(filename);
625 AST_APP_ARG(mimetype);
628 if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) {
629 profile = unref_profile(profile);
632 if (ast_string_field_init(pp_file, 32)) {
634 profile = unref_profile(profile);
638 if ((file_extension = strrchr(pp_file->format, '.')))
641 AST_STANDARD_APP_ARGS(args, value_copy);
643 /* Mime type order of preference
644 * 1) Specific mime-type defined for file in profile
645 * 2) Mime determined by extension
646 * 3) Default mime type specified in profile
649 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"))));
651 if (!strcasecmp(v->name, "static_file")) {
652 ast_string_field_set(pp_file, format, args.filename);
653 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
654 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
655 /* Add a route for the static files, as their filenames won't change per-user */
656 build_route(pp_file, NULL, NULL);
658 ast_string_field_set(pp_file, format, v->name);
659 ast_string_field_set(pp_file, template, args.filename);
660 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
665 /* Append the global variables to the variables list for this profile.
666 * This is for convenience later, when we need to provide a single
667 * variable list for use in substitution. */
668 AST_LIST_TRAVERSE(&global_variables, var, entries) {
669 struct ast_var_t *new_var;
670 if ((new_var = ast_var_assign(var->name, var->value))) {
671 AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
675 ao2_link(profiles, profile);
677 profile = unref_profile(profile);
680 static struct extension *delete_extension(struct extension *exten)
682 struct ast_var_t *var;
683 while ((var = AST_LIST_REMOVE_HEAD(exten->headp, entries))) {
686 ast_free(exten->headp);
687 ast_string_field_free_memory(exten);
692 static struct extension *build_extension(struct ast_config *cfg, const char *name)
694 struct extension *exten;
695 struct ast_var_t *var;
699 if (!(exten = ast_calloc(1, sizeof(*exten)))) {
703 if (ast_string_field_init(exten, 32)) {
709 ast_string_field_set(exten, name, name);
711 if (!(exten->headp = ast_calloc(1, sizeof(*exten->headp)))) {
717 for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
718 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
720 /* If we didn't get a USERNAME variable, set it to the user->name */
721 if (i == PP_USERNAME && !tmp) {
722 if ((var = ast_var_assign(pp_variable_list[PP_USERNAME].template_var, exten->name))) {
723 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
726 } else if (i == PP_TIMEZONE) {
727 /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
728 set_timezone_variables(exten->headp, tmp);
729 } else if (i == PP_LINENUMBER) {
730 exten->index = atoi(tmp);
733 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) {
734 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
738 if (!ast_strlen_zero(global_server)) {
739 if ((var = ast_var_assign("SERVER", global_server)))
740 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
743 if (!ast_strlen_zero(global_serverport)) {
744 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
745 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
751 static struct user *unref_user(struct user *user)
758 /*! \brief Return a user looked up by name */
759 static struct user *find_user(const char *macaddress)
762 .macaddress = macaddress,
765 return ao2_find(users, &tmp, OBJ_POINTER);
768 static int users_hash_fn(const void *obj, const int flags)
770 const struct user *user = obj;
772 return ast_str_hash(user->macaddress);
775 static int users_cmp_fn(void *obj, void *arg, int flags)
777 const struct user *user1 = obj, *user2 = arg;
779 return !strcasecmp(user1->macaddress, user2->macaddress) ? CMP_MATCH : 0;
782 /*! \brief Free all memory associated with a user */
783 static void user_destructor(void *obj)
785 struct user *user = obj;
786 struct extension *exten;
788 while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
789 exten = delete_extension(exten);
792 ast_string_field_free_memory(user);
794 user->profile = unref_profile(user->profile);
798 /*! \brief Delete all users */
799 static void delete_users(void)
801 struct ao2_iterator i;
804 i = ao2_iterator_init(users, 0);
805 while ((user = ao2_iterator_next(&i))) {
806 ao2_unlink(users, user);
807 user = unref_user(user);
811 /*! \brief Build and return a user structure based on gathered config data */
812 static struct user *build_user(const char *mac, struct phone_profile *profile)
817 if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
818 profile = unref_profile(profile);
822 if (ast_string_field_init(user, 32)) {
823 profile = unref_profile(profile);
824 user = unref_user(user);
828 ast_string_field_set(user, macaddress, mac);
829 user->profile = profile; /* already ref counted by find_profile */
834 /*! \brief Add an extension to a user ordered by index/linenumber */
835 static int add_user_extension(struct user *user, struct extension *exten)
837 struct ast_var_t *var;
839 /* Append profile variables here, and substitute variables on profile
840 * setvars, so that we can use user specific variables in them */
841 AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
842 char expand_buf[VAR_BUF_SIZE] = {0,};
843 struct ast_var_t *var2;
845 pbx_substitute_variables_varshead(exten->headp, var->value, expand_buf, sizeof(expand_buf));
846 if ((var2 = ast_var_assign(var->name, expand_buf)))
847 AST_LIST_INSERT_TAIL(exten->headp, var2, entries);
850 if (AST_LIST_EMPTY(&user->extensions)) {
851 AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
853 struct extension *exten_iter;
855 AST_LIST_TRAVERSE_SAFE_BEGIN(&user->extensions, exten_iter, entry) {
856 if (exten->index < exten_iter->index) {
857 AST_LIST_INSERT_BEFORE_CURRENT(exten, entry);
858 } else if (exten->index == exten_iter->index) {
859 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
860 user = unref_user(user); /* Profile should be unreffed now that it is attached to the user */
862 } else if (!AST_LIST_NEXT(exten, entry)) {
863 AST_LIST_INSERT_TAIL(&user->extensions, exten, entry);
866 AST_LIST_TRAVERSE_SAFE_END;
872 /*! \brief Add an http route for dynamic files attached to the profile of the user */
873 static int build_user_routes(struct user *user)
875 struct phoneprov_file *pp_file;
877 AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
878 char expand_buf[VAR_BUF_SIZE] = { 0, };
880 pbx_substitute_variables_varshead(AST_LIST_FIRST(&user->extensions)->headp, pp_file->format, expand_buf, sizeof(expand_buf));
881 build_route(pp_file, user, expand_buf);
887 /* \brief Parse config files and create appropriate structures */
888 static int set_config(void)
890 struct ast_config *cfg;
892 struct ast_variable *v;
893 struct ast_flags config_flags = { 0 };
895 /* Try to grab the port from sip.conf. If we don't get it here, we'll set it
896 * to whatever is set in phoneprov.conf or default to 5060 */
897 if ((cfg = ast_config_load("sip.conf", config_flags))) {
898 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
899 ast_config_destroy(cfg);
902 if (!(cfg = ast_config_load("phoneprov.conf", config_flags))) {
903 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
908 while ((cat = ast_category_browse(cfg, cat))) {
909 if (!strcasecmp(cat, "general")) {
910 for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
911 if (!strcasecmp(v->name, "serveraddr"))
912 ast_copy_string(global_server, v->value, sizeof(global_server));
913 else if (!strcasecmp(v->name, "serveriface")) {
915 lookup_iface(v->value, &addr);
916 ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
917 } else if (!strcasecmp(v->name, "serverport"))
918 ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
919 else if (!strcasecmp(v->name, "default_profile"))
920 ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
923 build_profile(cat, ast_variable_browse(cfg, cat));
926 ast_config_destroy(cfg);
928 if (!(cfg = ast_config_load("users.conf", config_flags))) {
929 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
934 while ((cat = ast_category_browse(cfg, cat))) {
935 const char *tmp, *mac;
937 struct phone_profile *profile;
938 struct extension *exten;
939 struct ast_var_t *var;
941 if (!strcasecmp(cat, "general")) {
942 for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
943 if (!strcasecmp(v->name, "vmexten")) {
944 if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value)))
945 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
947 if (!strcasecmp(v->name, "localextenlength")) {
948 if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
949 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
954 if (!strcasecmp(cat, "authentication"))
957 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
960 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
961 ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
965 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
966 if (ast_strlen_zero(tmp)) {
967 ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
971 if (!(profile = find_profile(tmp))) {
972 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
976 if (!((user = find_user(mac)) || (user = build_user(mac, profile)))) {
977 ast_log(LOG_WARNING, "Could not create user for '%s' - skipping\n", user->macaddress);
981 if (!(exten = build_extension(cfg, cat))) {
982 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
983 user = unref_user(user);
987 if (add_user_extension(user, exten)) {
988 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
989 user = unref_user(user);
990 exten = delete_extension(exten);
994 if (!find_user(mac)) {
995 if (build_user_routes(user)) {
996 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->macaddress);
997 user = unref_user(user);
1001 ao2_link(users, user);
1005 ast_config_destroy(cfg);
1010 /*! \brief Delete all http routes, freeing their memory */
1011 static void delete_routes(void)
1013 struct ao2_iterator i;
1014 struct http_route *route;
1016 i = ao2_iterator_init(http_routes, 0);
1017 while ((route = ao2_iterator_next(&i))) {
1018 ao2_unlink(http_routes, route);
1019 route = unref_route(route);
1023 /*! \brief Delete all phone profiles, freeing their memory */
1024 static void delete_profiles(void)
1026 struct ao2_iterator i;
1027 struct phone_profile *profile;
1029 i = ao2_iterator_init(profiles, 0);
1030 while ((profile = ao2_iterator_next(&i))) {
1031 ao2_unlink(profiles, profile);
1032 profile = unref_profile(profile);
1036 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1037 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1039 char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
1040 struct ao2_iterator i;
1042 AST_DECLARE_APP_ARGS(args,
1043 AST_APP_ARG(string);
1044 AST_APP_ARG(exclude_mac);
1046 AST_STANDARD_APP_ARGS(args, data);
1048 /* Fix data by turning %{ into ${ */
1049 while ((tmp = strstr(args.string, "%{")))
1052 i = ao2_iterator_init(users, 0);
1053 while ((user = ao2_iterator_next(&i))) {
1054 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1057 pbx_substitute_variables_varshead(AST_LIST_FIRST(&user->extensions)->headp, args.string, expand_buf, sizeof(expand_buf));
1058 ast_build_string(&buf, &len, "%s", expand_buf);
1059 user = unref_user(user);
1065 static struct ast_custom_function pp_each_user_function = {
1066 .name = "PP_EACH_USER",
1067 .synopsis = "Generate a string for each phoneprov user",
1068 .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
1070 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
1071 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
1072 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
1074 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
1075 .read = pp_each_user_exec,
1078 /*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1079 static int pp_each_extension_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1081 char expand_buf[VAR_BUF_SIZE] = {0,};
1083 struct extension *exten;
1084 char path[PATH_MAX];
1087 AST_DECLARE_APP_ARGS(args,
1089 AST_APP_ARG(template);
1092 AST_STANDARD_APP_ARGS(args, data);
1094 if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1095 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requries both a macaddress and template filename.\n");
1099 if (!(user = find_user(args.mac))) {
1100 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1104 snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1105 filelen = load_file(path, &file);
1107 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1118 AST_LIST_TRAVERSE(&user->extensions, exten, entry) {
1119 pbx_substitute_variables_varshead(exten->headp, file, expand_buf, sizeof(expand_buf));
1120 ast_build_string(&buf, &len, "%s", expand_buf);
1123 user = unref_user(user);
1128 static struct ast_custom_function pp_each_extension_function = {
1129 .name = "PP_EACH_EXTENSION",
1130 .synopsis = "Execute specified template for each extension",
1131 .syntax = "PP_EACH_EXTENSION(<mac>|<template>)",
1133 "Output the specified template for each extension associated with the specified\n"
1135 .read = pp_each_extension_exec,
1138 /*! \brief CLI command to list static and dynamic routes */
1139 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1141 #define FORMAT "%-40.40s %-30.30s\n"
1142 struct ao2_iterator i;
1143 struct http_route *route;
1147 e->command = "phoneprov show routes";
1149 "Usage: phoneprov show routes\n"
1150 " Lists all registered phoneprov http routes.\n";
1156 /* This currently iterates over routes twice, but it is the only place I've needed
1157 * to really separate static an dynamic routes, so I've just left it this way. */
1158 ast_cli(a->fd, "Static routes\n\n");
1159 ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
1160 i = ao2_iterator_init(http_routes, 0);
1161 while ((route = ao2_iterator_next(&i))) {
1163 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1164 route = unref_route(route);
1167 ast_cli(a->fd, "\nDynamic routes\n\n");
1168 ast_cli(a->fd, FORMAT, "Relative URI", "Template");
1170 i = ao2_iterator_init(http_routes, 0);
1171 while ((route = ao2_iterator_next(&i))) {
1173 ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1174 route = unref_route(route);
1180 static struct ast_cli_entry pp_cli[] = {
1181 AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1184 static struct ast_http_uri phoneprovuri = {
1185 .callback = phoneprov_callback,
1186 .description = "Asterisk HTTP Phone Provisioning Tool",
1194 static int load_module(void)
1196 profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
1198 http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
1200 users = ao2_container_alloc(MAX_USER_BUCKETS, users_hash_fn, users_cmp_fn);
1202 AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
1204 ast_custom_function_register(&pp_each_user_function);
1205 ast_custom_function_register(&pp_each_extension_function);
1206 ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
1209 ast_http_uri_link(&phoneprovuri);
1214 static int unload_module(void)
1216 struct ast_var_t *var;
1218 ast_http_uri_unlink(&phoneprovuri);
1219 ast_custom_function_unregister(&pp_each_user_function);
1220 ast_custom_function_unregister(&pp_each_extension_function);
1221 ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1226 ao2_ref(profiles, -1);
1227 ao2_ref(http_routes, -1);
1229 while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries)))
1230 ast_var_delete(var);
1235 static int reload(void)
1245 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1246 .load = load_module,
1247 .unload = unload_module,