Newer
Older
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 1999 - 2008, Digium, Inc.
*
* Mark Spencer <markster@digium.com>
* Matthew Brooks <mbrooks@digium.com>
* Terry Wilson <twilson@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*! \file
*
* \brief Phone provisioning application for the asterisk internal http server
*
* \author Matthew Brooks <mbrooks@digium.com>
* \author Terry Wilson <twilson@digium.com>
*/
/*! \li \ref res_phoneprov.c uses the configuration file \ref phoneprov.conf and \ref users.conf and \ref sip.conf
* \addtogroup configuration_file Configuration Files
*/
/*!
* \page phoneprov.conf phoneprov.conf
* \verbinclude phoneprov.conf.sample
*/
/*** MODULEINFO
<support_level>extended</support_level>
***/
#include "asterisk.h"
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#ifdef SOLARIS
#include <sys/sockio.h>
#endif
#include "asterisk/file.h"
#include "asterisk/paths.h"
#include "asterisk/pbx.h"
#include "asterisk/cli.h"
#include "asterisk/module.h"
#include "asterisk/http.h"
#include "asterisk/utils.h"
#include "asterisk/app.h"
#include "asterisk/strings.h"
#include "asterisk/stringfields.h"
#include "asterisk/options.h"
#include "asterisk/config.h"
#include "asterisk/acl.h"
#include "asterisk/astobj2.h"
#ifdef LOW_MEMORY
#define MAX_PROFILE_BUCKETS 1
#define MAX_ROUTE_BUCKETS 1
#else
#define MAX_PROFILE_BUCKETS 17
#define MAX_ROUTE_BUCKETS 563
#endif /* LOW_MEMORY */
#define VAR_BUF_SIZE 4096
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/*** DOCUMENTATION
<function name="PP_EACH_EXTENSION" language="en_US">
<synopsis>
Execute specified template for each extension.
</synopsis>
<syntax>
<parameter name="mac" required="true" />
<parameter name="template" required="true" />
</syntax>
<description>
<para>Output the specified template for each extension associated with the specified MAC address.</para>
</description>
</function>
<function name="PP_EACH_USER" language="en_US">
<synopsis>
Generate a string for each phoneprov user.
</synopsis>
<syntax>
<parameter name="string" required="true" />
<parameter name="exclude_mac" required="true" />
</syntax>
<description>
<para>Pass in a string, with phoneprov variables you want substituted in the format of
%{VARNAME}, and you will get the string rendered for each user in phoneprov
excluding ones with MAC address <replaceable>exclude_mac</replaceable>. Probably not
useful outside of res_phoneprov.</para>
<para>Example: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})</para>
</description>
</function>
***/
/*! \brief for use in lookup_iface */
static struct in_addr __ourip = { .s_addr = 0x00000000, };
/* \note This enum and the pp_variable_list must be in the same order or
* bad things happen! */
enum pp_variables {
PP_MACADDRESS,
PP_USERNAME,
PP_FULLNAME,
PP_SECRET,
PP_LABEL,
PP_CALLERID,
PP_TIMEZONE,
Doug Bailey
committed
PP_LINEKEYS,
PP_VAR_LIST_LENGTH, /* This entry must always be the last in the list */
};
/*! \brief Lookup table to translate between users.conf property names and
* variables for use in phoneprov templates */
static const struct pp_variable_lookup {
enum pp_variables id;
const char * const user_var;
const char * const template_var;
} pp_variable_list[] = {
{ PP_MACADDRESS, "macaddress", "MAC" },
{ PP_USERNAME, "username", "USERNAME" },
{ PP_FULLNAME, "fullname", "DISPLAY_NAME" },
{ PP_SECRET, "secret", "SECRET" },
{ PP_LABEL, "label", "LABEL" },
{ PP_CALLERID, "cid_number", "CALLERID" },
{ PP_TIMEZONE, "timezone", "TIMEZONE" },
{ PP_LINENUMBER, "linenumber", "LINE" },
Doug Bailey
committed
{ PP_LINEKEYS, "linekeys", "LINEKEYS" },
};
/*! \brief structure to hold file data */
struct phoneprov_file {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(format); /*!< After variable substitution, becomes route->uri */
AST_STRING_FIELD(template); /*!< Template/physical file location */
AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
);
AST_LIST_ENTRY(phoneprov_file) entry;
};
/*! \brief structure to hold phone profiles read from phoneprov.conf */
struct phone_profile {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name); /*!< Name of phone profile */
AST_STRING_FIELD(default_mime_type); /*!< Default mime type if it isn't provided */
AST_STRING_FIELD(staticdir); /*!< Subdirectory that static files are stored in */
);
struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files; /*!< List of static files */
AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files; /*!< List of dynamic files */
};
struct extension {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name);
);
int index;
struct varshead *headp; /*!< List of variables to substitute into templates */
AST_LIST_ENTRY(extension) entry;
};
/*! \brief structure to hold users read from users.conf */
struct user {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(macaddress); /*!< Mac address of user's phone */
);
struct phone_profile *profile; /*!< Profile the phone belongs to */
AST_LIST_HEAD_NOLOCK(, extension) extensions;
};
/*! \brief structure to hold http routes (valid URIs, and the files they link to) */
struct http_route {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(uri); /*!< The URI requested */
);
struct phoneprov_file *file; /*!< The file that links to the URI */
struct user *user; /*!< The user that has variables to substitute into the file
* NULL in the case of a static route */
};
static struct ao2_container *profiles;
static struct ao2_container *http_routes;
static char global_server[80] = ""; /*!< Server to substitute into templates */
static char global_serverport[6] = ""; /*!< Server port to substitute into templates */
static char global_default_profile[80] = ""; /*!< Default profile to use if one isn't specified */
/*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
Terry Wilson
committed
static ast_mutex_t globals_lock;
/* iface is the interface (e.g. eth0); address is the return value */
static int lookup_iface(const char *iface, struct in_addr *address)
{
int mysock, res = 0;
struct ifreq ifr;
struct sockaddr_in *sin;
memset(&ifr, 0, sizeof(ifr));
ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if (mysock < 0) {
ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
return -1;
}
res = ioctl(mysock, SIOCGIFADDR, &ifr);
close(mysock);
if (res < 0) {
ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
memcpy(address, &__ourip, sizeof(__ourip));
return -1;
} else {
sin = (struct sockaddr_in *)&ifr.ifr_addr;
memcpy(address, &sin->sin_addr, sizeof(*address));
return 0;
}
}
static struct phone_profile *unref_profile(struct phone_profile *prof)
{
ao2_ref(prof, -1);
return NULL;
}
/*! \brief Return a phone profile looked up by name */
static struct phone_profile *find_profile(const char *name)
{
struct phone_profile tmp = {
.name = name,
};
return ao2_find(profiles, &tmp, OBJ_POINTER);
}
static int profile_hash_fn(const void *obj, const int flags)
{
const struct phone_profile *profile = obj;
Terry Wilson
committed
return ast_str_case_hash(profile->name);
}
static int profile_cmp_fn(void *obj, void *arg, int flags)
{
const struct phone_profile *profile1 = obj, *profile2 = arg;
return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH | CMP_STOP : 0;
}
static void delete_file(struct phoneprov_file *file)
{
ast_string_field_free_memory(file);
free(file);
}
static void profile_destructor(void *obj)
{
struct phone_profile *profile = obj;
struct phoneprov_file *file;
struct ast_var_t *var;
while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
delete_file(file);
while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
delete_file(file);
while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
ast_var_delete(var);
ast_string_field_free_memory(profile);
}
static struct http_route *unref_route(struct http_route *route)
{
ao2_ref(route, -1);
return NULL;
}
static int routes_hash_fn(const void *obj, const int flags)
{
const struct http_route *route = obj;
Terry Wilson
committed
Terry Wilson
committed
return ast_str_case_hash(route->uri);
}
static int routes_cmp_fn(void *obj, void *arg, int flags)
{
const struct http_route *route1 = obj, *route2 = arg;
Terry Wilson
committed
return !strcasecmp(route1->uri, route2->uri) ? CMP_MATCH | CMP_STOP : 0;
}
static void route_destructor(void *obj)
{
struct http_route *route = obj;
ast_string_field_free_memory(route);
}
/*! \brief Read a TEXT file into a string and return the length */
static int load_file(const char *filename, char **ret)
{
int len = 0;
FILE *f;
if (!(f = fopen(filename, "r"))) {
*ret = NULL;
return -1;
}
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (!(*ret = ast_malloc(len + 1)))
return -2;
if (len != fread(*ret, sizeof(char), len, f)) {
free(*ret);
*ret = NULL;
return -3;
}
fclose(f);
(*ret)[len] = '\0';
return len;
}
/*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
\param headp pointer to list of user variables
\param zone A time zone. NULL sets variables based on timezone of the machine
*/
static void set_timezone_variables(struct varshead *headp, const char *zone)
{
time_t utc_time;
int dstenable;
time_t dststart;
time_t dstend;
struct ast_tm tm_info;
int tzoffset;
char buffer[21];
struct ast_var_t *var;
struct timeval when;
time(&utc_time);
ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
snprintf(buffer, sizeof(buffer), "%d", tzoffset);
var = ast_var_assign("TZOFFSET", buffer);
if (var)
if (!dstenable)
return;
if ((var = ast_var_assign("DST_ENABLE", "1")))
AST_LIST_INSERT_TAIL(headp, var, entries);
ast_localtime(&when, &tm_info, zone);
snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
if ((var = ast_var_assign("DST_START_MONTH", buffer)))
AST_LIST_INSERT_TAIL(headp, var, entries);
snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
if ((var = ast_var_assign("DST_START_MDAY", buffer)))
AST_LIST_INSERT_TAIL(headp, var, entries);
snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
if ((var = ast_var_assign("DST_START_HOUR", buffer)))
AST_LIST_INSERT_TAIL(headp, var, entries);
when.tv_sec = dstend;
ast_localtime(&when, &tm_info, zone);
snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
if ((var = ast_var_assign("DST_END_MONTH", buffer)))
AST_LIST_INSERT_TAIL(headp, var, entries);
snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
if ((var = ast_var_assign("DST_END_MDAY", buffer)))
AST_LIST_INSERT_TAIL(headp, var, entries);
snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
if ((var = ast_var_assign("DST_END_HOUR", buffer)))
AST_LIST_INSERT_TAIL(headp, var, entries);
}
/*! \brief Callback that is executed everytime an http request is received by this module */
static int 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 *get_vars, struct ast_variable *headers)
{
struct http_route *route;
struct http_route search_route = {
.uri = uri,
};
struct ast_str *result;
char path[PATH_MAX];
char *file = NULL;
int len;
int fd;
struct ast_str *http_header;
if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) {
goto out404;
snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
if (!route->user) { /* Static file */
fd = open(path, O_RDONLY);
goto out500;
len = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
if (len < 0) {
ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
close(fd);
goto out500;
}
http_header = ast_str_create(80);
ast_str_set(&http_header, 0, "Content-type: %s\r\n",
route->file->mime_type);
ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
close(fd);
route = unref_route(route);
return 0;
} else { /* Dynamic file */
len = load_file(path, &file);
if (len < 0) {
ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
ast_free(file);
goto out500;
}
goto out500;
ast_free(file);
goto out500;
}
/* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
* the IP address we are listening on that the phone contacted for this config file */
if (ast_strlen_zero(global_server)) {
union {
struct sockaddr sa;
struct sockaddr_in sa_in;
} name;
socklen_t namelen = sizeof(name.sa);
int res;
if ((res = getsockname(ser->fd, &name.sa, &namelen))) {
ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
struct ast_var_t *var;
if ((var = ast_var_assign("SERVER", ast_inet_ntoa(name.sa_in.sin_addr)))) {
AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
AST_LIST_INSERT_TAIL(exten_iter->headp, var, entries);
}
}
}
}
ast_str_substitute_variables_varshead(&tmp, 0, AST_LIST_FIRST(&route->user->extensions)->headp, file);
ast_free(file);
http_header = ast_str_create(80);
ast_str_set(&http_header, 0, "Content-type: %s\r\n",
route->file->mime_type);
if (!(result = ast_str_create(512))) {
ast_log(LOG_ERROR, "Could not create result string!\n");
if (tmp) {
ast_free(tmp);
}
ast_free(http_header);
goto out500;
}
ast_str_append(&result, 0, "%s", ast_str_buffer(tmp));
ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
ast_free(tmp);
route = unref_route(route);
return 0;
}
out404:
ast_http_error(ser, 404, "Not Found", "Nothing to see here. Move along.");
out500:
route = unref_route(route);
ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
}
/*! \brief Build a route structure and add it to the list of available http routes
\param pp_file File to link to the route
\param user User to link to the route (NULL means static route)
\param uri URI of the route
*/
static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
{
struct http_route *route;
if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
return;
if (ast_string_field_init(route, 32)) {
ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
route = unref_route(route);
return;
}
ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
route->user = user;
route->file = pp_file;
ao2_link(http_routes, route);
route = unref_route(route);
}
/*! \brief Build a phone profile and add it to the list of phone profiles
\param name the name of the profile
\param v ast_variable from parsing phoneprov.conf
*/
static void build_profile(const char *name, struct ast_variable *v)
{
struct phone_profile *profile;
struct ast_var_t *var;
if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
return;
if (ast_string_field_init(profile, 32)) {
profile = unref_profile(profile);
return;
}
if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
profile = unref_profile(profile);
return;
}
AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
ast_string_field_set(profile, name, name);
for (; v; v = v->next) {
if (!strcasecmp(v->name, "mime_type")) {
ast_string_field_set(profile, default_mime_type, v->value);
} else if (!strcasecmp(v->name, "setvar")) {
struct ast_var_t *variable;
char *value_copy = ast_strdupa(v->value);
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(varname);
AST_APP_ARG(varval);
);
AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
do {
if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
break;
args.varname = ast_strip(args.varname);
args.varval = ast_strip(args.varval);
if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
break;
if ((variable = ast_var_assign(args.varname, args.varval)))
AST_LIST_INSERT_TAIL(profile->headp, variable, entries);
} while (0);
} else if (!strcasecmp(v->name, "staticdir")) {
ast_string_field_set(profile, staticdir, v->value);
} else {
struct phoneprov_file *pp_file;
char *file_extension;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(filename);
AST_APP_ARG(mimetype);
);
if (!(pp_file = ast_calloc_with_stringfields(1, struct phoneprov_file, 32))) {
profile = unref_profile(profile);
return;
}
if ((file_extension = strrchr(pp_file->format, '.')))
file_extension++;
AST_STANDARD_APP_ARGS(args, value_copy);
/* Mime type order of preference
* 1) Specific mime-type defined for file in profile
* 2) Mime determined by extension
* 3) Default mime type specified in profile
* 4) text/plain
*/
ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype,
(S_OR(S_OR(ast_http_ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
if (!strcasecmp(v->name, "static_file")) {
ast_string_field_set(pp_file, format, args.filename);
ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
/* Add a route for the static files, as their filenames won't change per-user */
build_route(pp_file, NULL, NULL);
} else {
ast_string_field_set(pp_file, format, v->name);
ast_string_field_set(pp_file, template, args.filename);
AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
}
}
}
/* Append the global variables to the variables list for this profile.
* This is for convenience later, when we need to provide a single
* variable list for use in substitution. */
Terry Wilson
committed
ast_mutex_lock(&globals_lock);
AST_LIST_TRAVERSE(&global_variables, var, entries) {
struct ast_var_t *new_var;
if ((new_var = ast_var_assign(var->name, var->value))) {
AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
}
Terry Wilson
committed
ast_mutex_unlock(&globals_lock);
ao2_link(profiles, profile);
profile = unref_profile(profile);
}
static struct extension *delete_extension(struct extension *exten)
{
struct ast_var_t *var;
while ((var = AST_LIST_REMOVE_HEAD(exten->headp, entries))) {
ast_var_delete(var);
}
ast_free(exten->headp);
ast_string_field_free_memory(exten);
}
static struct extension *build_extension(struct ast_config *cfg, const char *name)
{
struct extension *exten;
struct ast_var_t *var;
const char *tmp;
int i;
if (!(exten = ast_calloc_with_stringfields(1, struct extension, 32))) {
ast_string_field_set(exten, name, name);
if (!(exten->headp = ast_calloc(1, sizeof(*exten->headp)))) {
ast_free(exten);
exten = NULL;
return NULL;
}
for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
/* If we didn't get a USERNAME variable, set it to the user->name */
if (i == PP_USERNAME && !tmp) {
if ((var = ast_var_assign(pp_variable_list[PP_USERNAME].template_var, exten->name))) {
AST_LIST_INSERT_TAIL(exten->headp, var, entries);
}
continue;
} else if (i == PP_TIMEZONE) {
/* perfectly ok if tmp is NULL, will set variables based on server's time zone */
set_timezone_variables(exten->headp, tmp);
} else if (i == PP_LINENUMBER) {
if (!tmp) {
tmp = "1";
}
Doug Bailey
committed
} else if (i == PP_LINEKEYS) {
if (!tmp) {
tmp = "1";
}
}
if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) {
AST_LIST_INSERT_TAIL(exten->headp, var, entries);
}
}
if (!ast_strlen_zero(global_server)) {
if ((var = ast_var_assign("SERVER", global_server)))
AST_LIST_INSERT_TAIL(exten->headp, var, entries);
}
if (!ast_strlen_zero(global_serverport)) {
if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
AST_LIST_INSERT_TAIL(exten->headp, var, entries);
}
return exten;
}
static struct user *unref_user(struct user *user)
{
/*! \brief Return a user looked up by name */
static struct user *find_user(const char *macaddress)
{
struct user tmp = {
.macaddress = macaddress,
};
return ao2_find(users, &tmp, OBJ_POINTER);
static int users_hash_fn(const void *obj, const int flags)
{
const struct user *user = obj;
Terry Wilson
committed
return ast_str_case_hash(user->macaddress);
static int users_cmp_fn(void *obj, void *arg, int flags)
{
const struct user *user1 = obj, *user2 = arg;
return !strcasecmp(user1->macaddress, user2->macaddress) ? CMP_MATCH | CMP_STOP : 0;
/*! \brief Free all memory associated with a user */
static void user_destructor(void *obj)
{
struct user *user = obj;
struct extension *exten;
while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
exten = delete_extension(exten);
}
if (user->profile) {
user->profile = unref_profile(user->profile);
}
ast_string_field_free_memory(user);
/*! \brief Delete all users */
static void delete_users(void)
{
struct ao2_iterator i;
struct user *user;
i = ao2_iterator_init(users, 0);
while ((user = ao2_iterator_next(&i))) {
ao2_unlink(users, user);
user = unref_user(user);
}
ao2_iterator_destroy(&i);
}
/*! \brief Build and return a user structure based on gathered config data */
static struct user *build_user(const char *mac, struct phone_profile *profile)
{
struct user *user;
Terry Wilson
committed
if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
profile = unref_profile(profile);
return NULL;
}
if (ast_string_field_init(user, 32)) {
profile = unref_profile(profile);
return NULL;
}
ast_string_field_set(user, macaddress, mac);
user->profile = profile; /* already ref counted by find_profile */
/*! \brief Add an extension to a user ordered by index/linenumber */
static int add_user_extension(struct user *user, struct extension *exten)
{
struct ast_var_t *var;
struct ast_str *str = ast_str_create(16);
if (!str) {
return -1;
}
/* Append profile variables here, and substitute variables on profile
* setvars, so that we can use user specific variables in them */
AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
struct ast_var_t *var2;
ast_str_substitute_variables_varshead(&str, 0, exten->headp, var->value);
if ((var2 = ast_var_assign(var->name, ast_str_buffer(str)))) {
AST_LIST_INSERT_TAIL(exten->headp, var2, entries);
}
if (AST_LIST_EMPTY(&user->extensions)) {
AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
} else {
struct extension *exten_iter;
AST_LIST_TRAVERSE_SAFE_BEGIN(&user->extensions, exten_iter, entry) {
if (exten->index < exten_iter->index) {
AST_LIST_INSERT_BEFORE_CURRENT(exten, entry);
} else if (exten->index == exten_iter->index) {
ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
return -1;
Terry Wilson
committed
} else if (!AST_LIST_NEXT(exten_iter, entry)) {
AST_LIST_INSERT_TAIL(&user->extensions, exten, entry);
}
}
AST_LIST_TRAVERSE_SAFE_END;
}
return 0;
}
/*! \brief Add an http route for dynamic files attached to the profile of the user */
static int build_user_routes(struct user *user)
{
struct phoneprov_file *pp_file;
if (!(str = ast_str_create(16))) {
return -1;
}
AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
ast_str_substitute_variables_varshead(&str, 0, AST_LIST_FIRST(&user->extensions)->headp, pp_file->format);
build_route(pp_file, user, ast_str_buffer(str));
}
return 0;
}
/* \brief Parse config files and create appropriate structures */
static int set_config(void)
{
Terry Wilson
committed
struct ast_config *cfg, *phoneprov_cfg;
char *cat;
struct ast_variable *v;
struct ast_flags config_flags = { 0 };
Terry Wilson
committed
struct ast_var_t *var;
/* Try to grab the port from sip.conf. If we don't get it here, we'll set it
* to whatever is set in phoneprov.conf or default to 5060 */
Tilghman Lesher
committed
if ((cfg = ast_config_load("sip.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
ast_config_destroy(cfg);
}
Tilghman Lesher
committed
if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
ast_log(LOG_WARNING, "Unable to load users.conf\n");
Terry Wilson
committed
return 0;
}
/* Go ahead and load global variables from users.conf so we can append to profiles */
for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
if (!strcasecmp(v->name, "vmexten")) {
if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value))) {
ast_mutex_lock(&globals_lock);
AST_LIST_INSERT_TAIL(&global_variables, var, entries);
ast_mutex_unlock(&globals_lock);
}
}
if (!strcasecmp(v->name, "localextenlength")) {
if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
ast_mutex_lock(&globals_lock);
AST_LIST_INSERT_TAIL(&global_variables, var, entries);
ast_mutex_unlock(&globals_lock);
}
}
Tilghman Lesher
committed
if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags)) || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
ast_config_destroy(cfg);
return -1;
}
cat = NULL;
Terry Wilson
committed
while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
if (!strcasecmp(cat, "general")) {
Terry Wilson
committed
for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
if (!strcasecmp(v->name, "serveraddr"))
ast_copy_string(global_server, v->value, sizeof(global_server));
else if (!strcasecmp(v->name, "serveriface")) {
struct in_addr addr;
lookup_iface(v->value, &addr);
ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
} else if (!strcasecmp(v->name, "serverport"))
ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
else if (!strcasecmp(v->name, "default_profile"))
ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
}
Terry Wilson
committed
build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
}
Terry Wilson
committed
ast_config_destroy(phoneprov_cfg);
cat = NULL;
while ((cat = ast_category_browse(cfg, cat))) {
const char *tmp, *mac;
struct user *user;
struct phone_profile *profile;
if (!strcasecmp(cat, "general")) {
Terry Wilson
committed
continue;
}
if (!strcasecmp(cat, "authentication"))
continue;
if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
continue;
if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
continue;
}