PJSIP: Add Path header support
[asterisk/asterisk.git] / res / res_phoneprov.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2008, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  * Matthew Brooks <mbrooks@digium.com>
8  * Terry Wilson <twilson@digium.com>
9  *
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.
15  *
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.
19  */
20
21 /*! \file
22  *
23  * \brief Phone provisioning application for the asterisk internal http server
24  *
25  * \author Matthew Brooks <mbrooks@digium.com>
26  * \author Terry Wilson <twilson@digium.com>
27  */
28
29 /*! \li \ref res_phoneprov.c uses the configuration file \ref phoneprov.conf and \ref users.conf and \ref sip.conf
30  * \addtogroup configuration_file Configuration Files
31  */
32
33 /*! 
34  * \page phoneprov.conf phoneprov.conf
35  * \verbinclude phoneprov.conf.sample
36  */
37
38 /*** MODULEINFO
39         <support_level>extended</support_level>
40  ***/
41
42 #include "asterisk.h"
43
44 #include <sys/ioctl.h>
45 #include <sys/socket.h>
46 #include <net/if.h>
47 #ifdef SOLARIS
48 #include <sys/sockio.h>
49 #endif
50 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
51
52 #include "asterisk/channel.h"
53 #include "asterisk/file.h"
54 #include "asterisk/paths.h"
55 #include "asterisk/pbx.h"
56 #include "asterisk/cli.h"
57 #include "asterisk/module.h"
58 #include "asterisk/http.h"
59 #include "asterisk/utils.h"
60 #include "asterisk/app.h"
61 #include "asterisk/strings.h"
62 #include "asterisk/stringfields.h"
63 #include "asterisk/options.h"
64 #include "asterisk/config.h"
65 #include "asterisk/acl.h"
66 #include "asterisk/astobj2.h"
67 #include "asterisk/ast_version.h"
68
69 #ifdef LOW_MEMORY
70 #define MAX_PROFILE_BUCKETS 1
71 #define MAX_ROUTE_BUCKETS 1
72 #define MAX_USER_BUCKETS 1
73 #else
74 #define MAX_PROFILE_BUCKETS 17
75 #define MAX_ROUTE_BUCKETS 563
76 #define MAX_USER_BUCKETS 563
77 #endif /* LOW_MEMORY */
78
79 #define VAR_BUF_SIZE 4096
80
81 /*** DOCUMENTATION
82         <function name="PP_EACH_EXTENSION" language="en_US">
83                 <synopsis>
84                         Execute specified template for each extension.
85                 </synopsis>
86                 <syntax>
87                         <parameter name="mac" required="true" />
88                         <parameter name="template" required="true" />
89                 </syntax>
90                 <description>
91                         <para>Output the specified template for each extension associated with the specified MAC address.</para>
92                 </description>
93         </function>
94         <function name="PP_EACH_USER" language="en_US">
95                 <synopsis>
96                         Generate a string for each phoneprov user.
97                 </synopsis>
98                 <syntax>
99                         <parameter name="string" required="true" />
100                         <parameter name="exclude_mac" required="true" />
101                 </syntax>
102                 <description>
103                         <para>Pass in a string, with phoneprov variables you want substituted in the format of
104                         %{VARNAME}, and you will get the string rendered for each user in phoneprov
105                         excluding ones with MAC address <replaceable>exclude_mac</replaceable>. Probably not
106                         useful outside of res_phoneprov.</para>
107                         <para>Example: ${PP_EACH_USER(&lt;item&gt;&lt;fn&gt;%{DISPLAY_NAME}&lt;/fn&gt;&lt;/item&gt;|${MAC})</para>
108                 </description>
109         </function>
110  ***/
111
112 /*! \brief for use in lookup_iface */
113 static struct in_addr __ourip = { .s_addr = 0x00000000, };
114
115 /* \note This enum and the pp_variable_list must be in the same order or
116  * bad things happen! */
117 enum pp_variables {
118         PP_MACADDRESS,
119         PP_USERNAME,
120         PP_FULLNAME,
121         PP_SECRET,
122         PP_LABEL,
123         PP_CALLERID,
124         PP_TIMEZONE,
125         PP_LINENUMBER,
126         PP_LINEKEYS,
127         PP_VAR_LIST_LENGTH,     /* This entry must always be the last in the list */
128 };
129
130 /*! \brief Lookup table to translate between users.conf property names and
131  * variables for use in phoneprov templates */
132 static const struct pp_variable_lookup {
133         enum pp_variables id;
134         const char * const user_var;
135         const char * const template_var;
136 } pp_variable_list[] = {
137         { PP_MACADDRESS, "macaddress", "MAC" },
138         { PP_USERNAME, "username", "USERNAME" },
139         { PP_FULLNAME, "fullname", "DISPLAY_NAME" },
140         { PP_SECRET, "secret", "SECRET" },
141         { PP_LABEL, "label", "LABEL" },
142         { PP_CALLERID, "cid_number", "CALLERID" },
143         { PP_TIMEZONE, "timezone", "TIMEZONE" },
144         { PP_LINENUMBER, "linenumber", "LINE" },
145         { PP_LINEKEYS, "linekeys", "LINEKEYS" },
146 };
147
148 /*! \brief structure to hold file data */
149 struct phoneprov_file {
150         AST_DECLARE_STRING_FIELDS(
151                 AST_STRING_FIELD(format);       /*!< After variable substitution, becomes route->uri */
152                 AST_STRING_FIELD(template); /*!< Template/physical file location */
153                 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
154         );
155         AST_LIST_ENTRY(phoneprov_file) entry;
156 };
157
158 /*! \brief structure to hold phone profiles read from phoneprov.conf */
159 struct phone_profile {
160         AST_DECLARE_STRING_FIELDS(
161                 AST_STRING_FIELD(name); /*!< Name of phone profile */
162                 AST_STRING_FIELD(default_mime_type);    /*!< Default mime type if it isn't provided */
163                 AST_STRING_FIELD(staticdir);    /*!< Subdirectory that static files are stored in */
164         );
165         struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
166         AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files;    /*!< List of static files */
167         AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files;   /*!< List of dynamic files */
168 };
169
170 struct extension {
171         AST_DECLARE_STRING_FIELDS(
172                 AST_STRING_FIELD(name);
173         );
174         int index;
175         struct varshead *headp; /*!< List of variables to substitute into templates */
176         AST_LIST_ENTRY(extension) entry;
177 };
178
179 /*! \brief structure to hold users read from users.conf */
180 struct user {
181         AST_DECLARE_STRING_FIELDS(
182                 AST_STRING_FIELD(macaddress);   /*!< Mac address of user's phone */
183         );
184         struct phone_profile *profile;  /*!< Profile the phone belongs to */
185         AST_LIST_HEAD_NOLOCK(, extension) extensions;
186 };
187
188 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
189 struct http_route {
190         AST_DECLARE_STRING_FIELDS(
191                 AST_STRING_FIELD(uri);  /*!< The URI requested */
192         );
193         struct phoneprov_file *file;    /*!< The file that links to the URI */
194         struct user *user;      /*!< The user that has variables to substitute into the file
195                                                  * NULL in the case of a static route */
196 };
197
198 static struct ao2_container *profiles;
199 static struct ao2_container *http_routes;
200 static struct ao2_container *users;
201
202 static char global_server[80] = "";     /*!< Server to substitute into templates */
203 static char global_serverport[6] = "";  /*!< Server port to substitute into templates */
204 static char global_default_profile[80] = "";    /*!< Default profile to use if one isn't specified */
205
206 /*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
207 static struct varshead global_variables;
208 static ast_mutex_t globals_lock;
209
210 /* iface is the interface (e.g. eth0); address is the return value */
211 static int lookup_iface(const char *iface, struct in_addr *address)
212 {
213         int mysock, res = 0;
214         struct ifreq ifr;
215         struct sockaddr_in *sin;
216
217         memset(&ifr, 0, sizeof(ifr));
218         ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
219
220         mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
221         if (mysock < 0) {
222                 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
223                 return -1;
224         }
225
226         res = ioctl(mysock, SIOCGIFADDR, &ifr);
227
228         close(mysock);
229
230         if (res < 0) {
231                 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
232                 memcpy(address, &__ourip, sizeof(__ourip));
233                 return -1;
234         } else {
235                 sin = (struct sockaddr_in *)&ifr.ifr_addr;
236                 memcpy(address, &sin->sin_addr, sizeof(*address));
237                 return 0;
238         }
239 }
240
241 static struct phone_profile *unref_profile(struct phone_profile *prof)
242 {
243         ao2_ref(prof, -1);
244
245         return NULL;
246 }
247
248 /*! \brief Return a phone profile looked up by name */
249 static struct phone_profile *find_profile(const char *name)
250 {
251         struct phone_profile tmp = {
252                 .name = name,
253         };
254
255         return ao2_find(profiles, &tmp, OBJ_POINTER);
256 }
257
258 static int profile_hash_fn(const void *obj, const int flags)
259 {
260         const struct phone_profile *profile = obj;
261
262         return ast_str_case_hash(profile->name);
263 }
264
265 static int profile_cmp_fn(void *obj, void *arg, int flags)
266 {
267         const struct phone_profile *profile1 = obj, *profile2 = arg;
268
269         return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH | CMP_STOP : 0;
270 }
271
272 static void delete_file(struct phoneprov_file *file)
273 {
274         ast_string_field_free_memory(file);
275         free(file);
276 }
277
278 static void profile_destructor(void *obj)
279 {
280         struct phone_profile *profile = obj;
281         struct phoneprov_file *file;
282         struct ast_var_t *var;
283
284         while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
285                 delete_file(file);
286
287         while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
288                 delete_file(file);
289
290         while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
291                 ast_var_delete(var);
292
293         ast_free(profile->headp);
294         ast_string_field_free_memory(profile);
295 }
296
297 static struct http_route *unref_route(struct http_route *route)
298 {
299         ao2_ref(route, -1);
300
301         return NULL;
302 }
303
304 static int routes_hash_fn(const void *obj, const int flags)
305 {
306         const struct http_route *route = obj;
307
308         return ast_str_case_hash(route->uri);
309 }
310
311 static int routes_cmp_fn(void *obj, void *arg, int flags)
312 {
313         const struct http_route *route1 = obj, *route2 = arg;
314
315         return !strcasecmp(route1->uri, route2->uri) ? CMP_MATCH | CMP_STOP : 0;
316 }
317
318 static void route_destructor(void *obj)
319 {
320         struct http_route *route = obj;
321
322         ast_string_field_free_memory(route);
323 }
324
325 /*! \brief Read a TEXT file into a string and return the length */
326 static int load_file(const char *filename, char **ret)
327 {
328         int len = 0;
329         FILE *f;
330
331         if (!(f = fopen(filename, "r"))) {
332                 *ret = NULL;
333                 return -1;
334         }
335
336         fseek(f, 0, SEEK_END);
337         len = ftell(f);
338         fseek(f, 0, SEEK_SET);
339         if (!(*ret = ast_malloc(len + 1)))
340                 return -2;
341
342         if (len != fread(*ret, sizeof(char), len, f)) {
343                 free(*ret);
344                 *ret = NULL;
345                 return -3;
346         }
347
348         fclose(f);
349
350         (*ret)[len] = '\0';
351
352         return len;
353 }
354
355 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
356         \param headp pointer to list of user variables
357         \param zone A time zone. NULL sets variables based on timezone of the machine
358 */
359 static void set_timezone_variables(struct varshead *headp, const char *zone)
360 {
361         time_t utc_time;
362         int dstenable;
363         time_t dststart;
364         time_t dstend;
365         struct ast_tm tm_info;
366         int tzoffset;
367         char buffer[21];
368         struct ast_var_t *var;
369         struct timeval when;
370
371         time(&utc_time);
372         ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
373         snprintf(buffer, sizeof(buffer), "%d", tzoffset);
374         var = ast_var_assign("TZOFFSET", buffer);
375         if (var)
376                 AST_LIST_INSERT_TAIL(headp, var, entries);
377
378         if (!dstenable)
379                 return;
380
381         if ((var = ast_var_assign("DST_ENABLE", "1")))
382                 AST_LIST_INSERT_TAIL(headp, var, entries);
383
384         when.tv_sec = dststart;
385         ast_localtime(&when, &tm_info, zone);
386
387         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
388         if ((var = ast_var_assign("DST_START_MONTH", buffer)))
389                 AST_LIST_INSERT_TAIL(headp, var, entries);
390
391         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
392         if ((var = ast_var_assign("DST_START_MDAY", buffer)))
393                 AST_LIST_INSERT_TAIL(headp, var, entries);
394
395         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
396         if ((var = ast_var_assign("DST_START_HOUR", buffer)))
397                 AST_LIST_INSERT_TAIL(headp, var, entries);
398
399         when.tv_sec = dstend;
400         ast_localtime(&when, &tm_info, zone);
401
402         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
403         if ((var = ast_var_assign("DST_END_MONTH", buffer)))
404                 AST_LIST_INSERT_TAIL(headp, var, entries);
405
406         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
407         if ((var = ast_var_assign("DST_END_MDAY", buffer)))
408                 AST_LIST_INSERT_TAIL(headp, var, entries);
409
410         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
411         if ((var = ast_var_assign("DST_END_HOUR", buffer)))
412                 AST_LIST_INSERT_TAIL(headp, var, entries);
413 }
414
415 /*! \brief Callback that is executed everytime an http request is received by this module */
416 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)
417 {
418         struct http_route *route;
419         struct http_route search_route = {
420                 .uri = uri,
421         };
422         struct ast_str *result;
423         char path[PATH_MAX];
424         char *file = NULL;
425         int len;
426         int fd;
427         struct ast_str *http_header;
428
429         if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) {
430                 ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method");
431                 return -1;
432         }
433
434         if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER))) {
435                 goto out404;
436         }
437
438         snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
439
440         if (!route->user) { /* Static file */
441
442                 fd = open(path, O_RDONLY);
443                 if (fd < 0) {
444                         goto out500;
445                 }
446
447                 len = lseek(fd, 0, SEEK_END);
448                 lseek(fd, 0, SEEK_SET);
449                 if (len < 0) {
450                         ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
451                         close(fd);
452                         goto out500;
453                 }
454
455                 http_header = ast_str_create(80);
456                 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
457                         route->file->mime_type);
458
459                 ast_http_send(ser, method, 200, NULL, http_header, NULL, fd, 0);
460
461                 close(fd);
462                 route = unref_route(route);
463                 return 0;
464         } else { /* Dynamic file */
465                 struct ast_str *tmp;
466
467                 len = load_file(path, &file);
468                 if (len < 0) {
469                         ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
470                         if (file) {
471                                 ast_free(file);
472                         }
473
474                         goto out500;
475                 }
476
477                 if (!file) {
478                         goto out500;
479                 }
480
481                 if (!(tmp = ast_str_create(len))) {
482                         if (file) {
483                                 ast_free(file);
484                         }
485
486                         goto out500;
487                 }
488
489                 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
490                  * the IP address we are listening on that the phone contacted for this config file */
491                 if (ast_strlen_zero(global_server)) {
492                         union {
493                                 struct sockaddr sa;
494                                 struct sockaddr_in sa_in;
495                         } name;
496                         socklen_t namelen = sizeof(name.sa);
497                         int res;
498
499                         if ((res = getsockname(ser->fd, &name.sa, &namelen))) {
500                                 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
501                         } else {
502                                 struct ast_var_t *var;
503                                 struct extension *exten_iter;
504
505                                 if ((var = ast_var_assign("SERVER", ast_inet_ntoa(name.sa_in.sin_addr)))) {
506                                         AST_LIST_TRAVERSE(&route->user->extensions, exten_iter, entry) {
507                                                 AST_LIST_INSERT_TAIL(exten_iter->headp, var, entries);
508                                         }
509                                 }
510                         }
511                 }
512
513                 ast_str_substitute_variables_varshead(&tmp, 0, AST_LIST_FIRST(&route->user->extensions)->headp, file);
514
515                 if (file) {
516                         ast_free(file);
517                 }
518
519                 http_header = ast_str_create(80);
520                 ast_str_set(&http_header, 0, "Content-type: %s\r\n",
521                         route->file->mime_type);
522
523                 if (!(result = ast_str_create(512))) {
524                         ast_log(LOG_ERROR, "Could not create result string!\n");
525                         if (tmp) {
526                                 ast_free(tmp);
527                         }
528                         ast_free(http_header);
529                         goto out500;
530                 }
531                 ast_str_append(&result, 0, "%s", ast_str_buffer(tmp)); 
532
533                 ast_http_send(ser, method, 200, NULL, http_header, result, 0, 0);
534                 if (tmp) {
535                         ast_free(tmp);
536                 }
537
538                 route = unref_route(route);
539
540                 return 0;
541         }
542
543 out404:
544         ast_http_error(ser, 404, "Not Found", "Nothing to see here.  Move along.");
545         return -1;
546
547 out500:
548         route = unref_route(route);
549         ast_http_error(ser, 500, "Internal Error", "An internal error has occured.");
550         return -1;
551 }
552
553 /*! \brief Build a route structure and add it to the list of available http routes
554         \param pp_file File to link to the route
555         \param user User to link to the route (NULL means static route)
556         \param uri URI of the route
557 */
558 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
559 {
560         struct http_route *route;
561
562         if (!(route = ao2_alloc(sizeof(*route), route_destructor))) {
563                 return;
564         }
565
566         if (ast_string_field_init(route, 32)) {
567                 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
568                 route = unref_route(route);
569                 return;
570         }
571
572         ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
573         route->user = user;
574         route->file = pp_file;
575
576         ao2_link(http_routes, route);
577
578         route = unref_route(route);
579 }
580
581 /*! \brief Build a phone profile and add it to the list of phone profiles
582         \param name the name of the profile
583         \param v ast_variable from parsing phoneprov.conf
584 */
585 static void build_profile(const char *name, struct ast_variable *v)
586 {
587         struct phone_profile *profile;
588         struct ast_var_t *var;
589
590         if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor))) {
591                 return;
592         }
593
594         if (ast_string_field_init(profile, 32)) {
595                 profile = unref_profile(profile);
596                 return;
597         }
598
599         if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
600                 profile = unref_profile(profile);
601                 return;
602         }
603
604         AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
605         AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
606
607         ast_string_field_set(profile, name, name);
608         for (; v; v = v->next) {
609                 if (!strcasecmp(v->name, "mime_type")) {
610                         ast_string_field_set(profile, default_mime_type, v->value);
611                 } else if (!strcasecmp(v->name, "setvar")) {
612                         struct ast_var_t *variable;
613                         char *value_copy = ast_strdupa(v->value);
614
615                         AST_DECLARE_APP_ARGS(args,
616                                 AST_APP_ARG(varname);
617                                 AST_APP_ARG(varval);
618                         );
619
620                         AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
621                         do {
622                                 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
623                                         break;
624                                 args.varname = ast_strip(args.varname);
625                                 args.varval = ast_strip(args.varval);
626                                 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
627                                         break;
628                                 if ((variable = ast_var_assign(args.varname, args.varval)))
629                                         AST_LIST_INSERT_TAIL(profile->headp, variable, entries);
630                         } while (0);
631                 } else if (!strcasecmp(v->name, "staticdir")) {
632                         ast_string_field_set(profile, staticdir, v->value);
633                 } else {
634                         struct phoneprov_file *pp_file;
635                         char *file_extension;
636                         char *value_copy = ast_strdupa(v->value);
637
638                         AST_DECLARE_APP_ARGS(args,
639                                 AST_APP_ARG(filename);
640                                 AST_APP_ARG(mimetype);
641                         );
642
643                         if (!(pp_file = ast_calloc_with_stringfields(1, struct phoneprov_file, 32))) {
644                                 profile = unref_profile(profile);
645                                 return;
646                         }
647
648                         if ((file_extension = strrchr(pp_file->format, '.')))
649                                 file_extension++;
650
651                         AST_STANDARD_APP_ARGS(args, value_copy);
652
653                         /* Mime type order of preference
654                          * 1) Specific mime-type defined for file in profile
655                          * 2) Mime determined by extension
656                          * 3) Default mime type specified in profile
657                          * 4) text/plain
658                          */
659                         ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype,
660                                 (S_OR(S_OR(ast_http_ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
661
662                         if (!strcasecmp(v->name, "static_file")) {
663                                 ast_string_field_set(pp_file, format, args.filename);
664                                 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
665                                 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
666                                 /* Add a route for the static files, as their filenames won't change per-user */
667                                 build_route(pp_file, NULL, NULL);
668                         } else {
669                                 ast_string_field_set(pp_file, format, v->name);
670                                 ast_string_field_set(pp_file, template, args.filename);
671                                 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
672                         }
673                 }
674         }
675
676         /* Append the global variables to the variables list for this profile.
677          * This is for convenience later, when we need to provide a single
678          * variable list for use in substitution. */
679         ast_mutex_lock(&globals_lock);
680         AST_LIST_TRAVERSE(&global_variables, var, entries) {
681                 struct ast_var_t *new_var;
682                 if ((new_var = ast_var_assign(var->name, var->value))) {
683                         AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
684                 }
685         }
686         ast_mutex_unlock(&globals_lock);
687
688         ao2_link(profiles, profile);
689
690         profile = unref_profile(profile);
691 }
692
693 static struct extension *delete_extension(struct extension *exten)
694 {
695         struct ast_var_t *var;
696         while ((var = AST_LIST_REMOVE_HEAD(exten->headp, entries))) {
697                 ast_var_delete(var);
698         }
699         ast_free(exten->headp);
700         ast_string_field_free_memory(exten);
701
702         ast_free(exten);
703
704         return NULL;
705 }
706
707 static struct extension *build_extension(struct ast_config *cfg, const char *name)
708 {
709         struct extension *exten;
710         struct ast_var_t *var;
711         const char *tmp;
712         int i;
713
714         if (!(exten = ast_calloc_with_stringfields(1, struct extension, 32))) {
715                 return NULL;
716         }
717
718         ast_string_field_set(exten, name, name);
719
720         if (!(exten->headp = ast_calloc(1, sizeof(*exten->headp)))) {
721                 ast_free(exten);
722                 exten = NULL;
723                 return NULL;
724         }
725
726         for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
727                 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
728
729                 /* If we didn't get a USERNAME variable, set it to the user->name */
730                 if (i == PP_USERNAME && !tmp) {
731                         if ((var = ast_var_assign(pp_variable_list[PP_USERNAME].template_var, exten->name))) {
732                                 AST_LIST_INSERT_TAIL(exten->headp, var, entries);
733                         }
734                         continue;
735                 } else if (i == PP_TIMEZONE) {
736                         /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
737                         set_timezone_variables(exten->headp, tmp);
738                 } else if (i == PP_LINENUMBER) {
739                         if (!tmp) {
740                                 tmp = "1";
741                         }
742                         exten->index = atoi(tmp);
743                 } else if (i == PP_LINEKEYS) {
744                         if (!tmp) {
745                                 tmp = "1";
746                         }
747                 }
748
749                 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp))) {
750                         AST_LIST_INSERT_TAIL(exten->headp, var, entries);
751                 }
752         }
753
754         if (!ast_strlen_zero(global_server)) {
755                 if ((var = ast_var_assign("SERVER", global_server)))
756                         AST_LIST_INSERT_TAIL(exten->headp, var, entries);
757         }
758
759         if (!ast_strlen_zero(global_serverport)) {
760                 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
761                         AST_LIST_INSERT_TAIL(exten->headp, var, entries);
762         }
763
764         return exten;
765 }
766
767 static struct user *unref_user(struct user *user)
768 {
769         ao2_ref(user, -1);
770
771         return NULL;
772 }
773
774 /*! \brief Return a user looked up by name */
775 static struct user *find_user(const char *macaddress)
776 {
777         struct user tmp = {
778                 .macaddress = macaddress,
779         };
780
781         return ao2_find(users, &tmp, OBJ_POINTER);
782 }
783
784 static int users_hash_fn(const void *obj, const int flags)
785 {
786         const struct user *user = obj;
787
788         return ast_str_case_hash(user->macaddress);
789 }
790
791 static int users_cmp_fn(void *obj, void *arg, int flags)
792 {
793         const struct user *user1 = obj, *user2 = arg;
794
795         return !strcasecmp(user1->macaddress, user2->macaddress) ? CMP_MATCH | CMP_STOP : 0;
796 }
797
798 /*! \brief Free all memory associated with a user */
799 static void user_destructor(void *obj)
800 {
801         struct user *user = obj;
802         struct extension *exten;
803
804         while ((exten = AST_LIST_REMOVE_HEAD(&user->extensions, entry))) {
805                 exten = delete_extension(exten);
806         }
807
808         if (user->profile) {
809                 user->profile = unref_profile(user->profile);
810         }
811
812         ast_string_field_free_memory(user);
813 }
814
815 /*! \brief Delete all users */
816 static void delete_users(void)
817 {
818         struct ao2_iterator i;
819         struct user *user;
820
821         i = ao2_iterator_init(users, 0);
822         while ((user = ao2_iterator_next(&i))) {
823                 ao2_unlink(users, user);
824                 user = unref_user(user);
825         }
826         ao2_iterator_destroy(&i);
827 }
828
829 /*! \brief Build and return a user structure based on gathered config data */
830 static struct user *build_user(const char *mac, struct phone_profile *profile)
831 {
832         struct user *user;
833
834         if (!(user = ao2_alloc(sizeof(*user), user_destructor))) {
835                 profile = unref_profile(profile);
836                 return NULL;
837         }
838
839         if (ast_string_field_init(user, 32)) {
840                 profile = unref_profile(profile);
841                 user = unref_user(user);
842                 return NULL;
843         }
844
845         ast_string_field_set(user, macaddress, mac);
846         user->profile = profile; /* already ref counted by find_profile */
847
848         return user;
849 }
850
851 /*! \brief Add an extension to a user ordered by index/linenumber */
852 static int add_user_extension(struct user *user, struct extension *exten)
853 {
854         struct ast_var_t *var;
855         struct ast_str *str = ast_str_create(16);
856
857         if (!str) {
858                 return -1;
859         }
860
861         /* Append profile variables here, and substitute variables on profile
862          * setvars, so that we can use user specific variables in them */
863         AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
864                 struct ast_var_t *var2;
865
866                 ast_str_substitute_variables_varshead(&str, 0, exten->headp, var->value);
867                 if ((var2 = ast_var_assign(var->name, ast_str_buffer(str)))) {
868                         AST_LIST_INSERT_TAIL(exten->headp, var2, entries);
869                 }
870         }
871
872         ast_free(str);
873
874         if (AST_LIST_EMPTY(&user->extensions)) {
875                 AST_LIST_INSERT_HEAD(&user->extensions, exten, entry);
876         } else {
877                 struct extension *exten_iter;
878
879                 AST_LIST_TRAVERSE_SAFE_BEGIN(&user->extensions, exten_iter, entry) {
880                         if (exten->index < exten_iter->index) {
881                                 AST_LIST_INSERT_BEFORE_CURRENT(exten, entry);
882                         } else if (exten->index == exten_iter->index) {
883                                 ast_log(LOG_WARNING, "Duplicate linenumber=%d for %s\n", exten->index, user->macaddress);
884                                 return -1;
885                         } else if (!AST_LIST_NEXT(exten_iter, entry)) {
886                                 AST_LIST_INSERT_TAIL(&user->extensions, exten, entry);
887                         }
888                 }
889                 AST_LIST_TRAVERSE_SAFE_END;
890         }
891
892         return 0;
893 }
894
895 /*! \brief Add an http route for dynamic files attached to the profile of the user */
896 static int build_user_routes(struct user *user)
897 {
898         struct phoneprov_file *pp_file;
899         struct ast_str *str;
900
901         if (!(str = ast_str_create(16))) {
902                 return -1;
903         }
904
905         AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
906                 ast_str_substitute_variables_varshead(&str, 0, AST_LIST_FIRST(&user->extensions)->headp, pp_file->format);
907                 build_route(pp_file, user, ast_str_buffer(str));
908         }
909
910         ast_free(str);
911         return 0;
912 }
913
914 /* \brief Parse config files and create appropriate structures */
915 static int set_config(void)
916 {
917         struct ast_config *cfg, *phoneprov_cfg;
918         char *cat;
919         struct ast_variable *v;
920         struct ast_flags config_flags = { 0 };
921         struct ast_var_t *var;
922
923         /* Try to grab the port from sip.conf.  If we don't get it here, we'll set it
924          * to whatever is set in phoneprov.conf or default to 5060 */
925         if ((cfg = ast_config_load("sip.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
926                 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
927                 ast_config_destroy(cfg);
928         }
929
930         if (!(cfg = ast_config_load("users.conf", config_flags)) || cfg == CONFIG_STATUS_FILEINVALID) {
931                 ast_log(LOG_WARNING, "Unable to load users.conf\n");
932                 return 0;
933         }
934
935         /* Go ahead and load global variables from users.conf so we can append to profiles */
936         for (v = ast_variable_browse(cfg, "general"); v; v = v->next) {
937                 if (!strcasecmp(v->name, "vmexten")) {
938                         if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value))) {
939                                 ast_mutex_lock(&globals_lock);
940                                 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
941                                 ast_mutex_unlock(&globals_lock);
942                         }
943                 }
944                 if (!strcasecmp(v->name, "localextenlength")) {
945                         if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
946                                 ast_mutex_lock(&globals_lock);
947                                 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
948                                 ast_mutex_unlock(&globals_lock);
949                 }
950         }
951
952         if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags)) || phoneprov_cfg == CONFIG_STATUS_FILEINVALID) {
953                 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
954                 ast_config_destroy(cfg);
955                 return -1;
956         }
957
958         cat = NULL;
959         while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
960                 if (!strcasecmp(cat, "general")) {
961                         for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
962                                 if (!strcasecmp(v->name, "serveraddr"))
963                                         ast_copy_string(global_server, v->value, sizeof(global_server));
964                                 else if (!strcasecmp(v->name, "serveriface")) {
965                                         struct in_addr addr;
966                                         lookup_iface(v->value, &addr);
967                                         ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
968                                 } else if (!strcasecmp(v->name, "serverport"))
969                                         ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
970                                 else if (!strcasecmp(v->name, "default_profile"))
971                                         ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
972                         }
973                 } else
974                         build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
975         }
976
977         ast_config_destroy(phoneprov_cfg);
978
979         cat = NULL;
980         while ((cat = ast_category_browse(cfg, cat))) {
981                 const char *tmp, *mac;
982                 struct user *user;
983                 struct phone_profile *profile;
984                 struct extension *exten;
985
986                 if (!strcasecmp(cat, "general")) {
987                         continue;
988                 }
989
990                 if (!strcasecmp(cat, "authentication"))
991                         continue;
992
993                 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))
994                         continue;
995
996                 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
997                         ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
998                         continue;
999                 }
1000
1001                 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
1002                 if (ast_strlen_zero(tmp)) {
1003                         ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
1004                         continue;
1005                 }
1006
1007                 if (!(user = find_user(mac))) {
1008                         if (!(profile = find_profile(tmp))) {
1009                                 ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
1010                                 continue;
1011                         }
1012
1013                         if (!(user = build_user(mac, profile))) {
1014                                 ast_log(LOG_WARNING, "Could not create user for '%s' - skipping\n", mac);
1015                                 continue;
1016                         }
1017
1018                         if (!(exten = build_extension(cfg, cat))) {
1019                                 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
1020                                 user = unref_user(user);
1021                                 continue;
1022                         }
1023
1024                         if (add_user_extension(user, exten)) {
1025                                 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1026                                 user = unref_user(user);
1027                                 exten = delete_extension(exten);
1028                                 continue;
1029                         }
1030
1031                         if (build_user_routes(user)) {
1032                                 ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->macaddress);
1033                                 user = unref_user(user);
1034                                 continue;
1035                         }
1036
1037                         ao2_link(users, user);
1038                         user = unref_user(user);
1039                 } else {
1040                         if (!(exten = build_extension(cfg, cat))) {
1041                                 ast_log(LOG_WARNING, "Could not create extension for %s - skipping\n", user->macaddress);
1042                                 user = unref_user(user);
1043                                 continue;
1044                         }
1045
1046                         if (add_user_extension(user, exten)) {
1047                                 ast_log(LOG_WARNING, "Could not add extension '%s' to user '%s'\n", exten->name, user->macaddress);
1048                                 user = unref_user(user);
1049                                 exten = delete_extension(exten);
1050                                 continue;
1051                         }
1052
1053                         user = unref_user(user);
1054                 }
1055         }
1056
1057         ast_config_destroy(cfg);
1058
1059         return 0;
1060 }
1061
1062 /*! \brief Delete all http routes, freeing their memory */
1063 static void delete_routes(void)
1064 {
1065         struct ao2_iterator i;
1066         struct http_route *route;
1067
1068         i = ao2_iterator_init(http_routes, 0);
1069         while ((route = ao2_iterator_next(&i))) {
1070                 ao2_unlink(http_routes, route);
1071                 route = unref_route(route);
1072         }
1073         ao2_iterator_destroy(&i);
1074 }
1075
1076 /*! \brief Delete all phone profiles, freeing their memory */
1077 static void delete_profiles(void)
1078 {
1079         struct ao2_iterator i;
1080         struct phone_profile *profile;
1081
1082         i = ao2_iterator_init(profiles, 0);
1083         while ((profile = ao2_iterator_next(&i))) {
1084                 ao2_unlink(profiles, profile);
1085                 profile = unref_profile(profile);
1086         }
1087         ao2_iterator_destroy(&i);
1088 }
1089
1090 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
1091 static int pp_each_user_helper(struct ast_channel *chan, char *data, char *buf, struct ast_str **bufstr, int len)
1092 {
1093         char *tmp;
1094         struct ao2_iterator i;
1095         struct user *user;
1096         struct ast_str *str;
1097         AST_DECLARE_APP_ARGS(args,
1098                 AST_APP_ARG(string);
1099                 AST_APP_ARG(exclude_mac);
1100         );
1101         AST_STANDARD_APP_ARGS(args, data);
1102
1103         if (!(str = ast_str_create(16))) {
1104                 return -1;
1105         }
1106
1107         /* Fix data by turning %{ into ${ */
1108         while ((tmp = strstr(args.string, "%{")))
1109                 *tmp = '$';
1110
1111         i = ao2_iterator_init(users, 0);
1112         while ((user = ao2_iterator_next(&i))) {
1113                 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac)) {
1114                         continue;
1115                 }
1116                 ast_str_substitute_variables_varshead(&str, len, AST_LIST_FIRST(&user->extensions)->headp, args.string);
1117                 if (buf) {
1118                         size_t slen = len;
1119                         ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1120                 } else {
1121                         ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1122                 }
1123                 user = unref_user(user);
1124         }
1125         ao2_iterator_destroy(&i);
1126
1127         ast_free(str);
1128         return 0;
1129 }
1130
1131 static int pp_each_user_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1132 {
1133         return pp_each_user_helper(chan, data, buf, NULL, len);
1134 }
1135
1136 static int pp_each_user_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1137 {
1138         return pp_each_user_helper(chan, data, NULL, buf, len);
1139 }
1140
1141 static struct ast_custom_function pp_each_user_function = {
1142         .name = "PP_EACH_USER",
1143         .read = pp_each_user_read,
1144         .read2 = pp_each_user_read2,
1145 };
1146
1147 /*! \brief A dialplan function that can be used to output a template for each extension attached to a user */
1148 static int pp_each_extension_helper(struct ast_channel *chan, const char *cmd, char *data, char *buf, struct ast_str **bufstr, int len)
1149 {
1150         struct user *user;
1151         struct extension *exten;
1152         char path[PATH_MAX];
1153         char *file;
1154         int filelen;
1155         struct ast_str *str;
1156         AST_DECLARE_APP_ARGS(args,
1157                 AST_APP_ARG(mac);
1158                 AST_APP_ARG(template);
1159         );
1160
1161         AST_STANDARD_APP_ARGS(args, data);
1162
1163         if (ast_strlen_zero(args.mac) || ast_strlen_zero(args.template)) {
1164                 ast_log(LOG_WARNING, "PP_EACH_EXTENSION requries both a macaddress and template filename.\n");
1165                 return 0;
1166         }
1167
1168         if (!(user = find_user(args.mac))) {
1169                 ast_log(LOG_WARNING, "Could not find user with mac = '%s'\n", args.mac);
1170                 return 0;
1171         }
1172
1173         snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, args.template);
1174         filelen = load_file(path, &file);
1175         if (filelen < 0) {
1176                 ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, filelen);
1177                 if (file) {
1178                         ast_free(file);
1179                 }
1180                 return 0;
1181         }
1182
1183         if (!file) {
1184                 return 0;
1185         }
1186
1187         if (!(str = ast_str_create(filelen))) {
1188                 return 0;
1189         }
1190
1191         AST_LIST_TRAVERSE(&user->extensions, exten, entry) {
1192                 ast_str_substitute_variables_varshead(&str, 0, exten->headp, file);
1193                 if (buf) {
1194                         size_t slen = len;
1195                         ast_build_string(&buf, &slen, "%s", ast_str_buffer(str));
1196                 } else {
1197                         ast_str_append(bufstr, len, "%s", ast_str_buffer(str));
1198                 }
1199         }
1200
1201         ast_free(file);
1202         ast_free(str);
1203
1204         user = unref_user(user);
1205
1206         return 0;
1207 }
1208
1209 static int pp_each_extension_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
1210 {
1211         return pp_each_extension_helper(chan, cmd, data, buf, NULL, len);
1212 }
1213
1214 static int pp_each_extension_read2(struct ast_channel *chan, const char *cmd, char *data, struct ast_str **buf, ssize_t len)
1215 {
1216         return pp_each_extension_helper(chan, cmd, data, NULL, buf, len);
1217 }
1218
1219 static struct ast_custom_function pp_each_extension_function = {
1220         .name = "PP_EACH_EXTENSION",
1221         .read = pp_each_extension_read,
1222         .read2 = pp_each_extension_read2,
1223 };
1224
1225 /*! \brief CLI command to list static and dynamic routes */
1226 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1227 {
1228 #define FORMAT "%-40.40s  %-30.30s\n"
1229         struct ao2_iterator i;
1230         struct http_route *route;
1231
1232         switch(cmd) {
1233         case CLI_INIT:
1234                 e->command = "phoneprov show routes";
1235                 e->usage =
1236                         "Usage: phoneprov show routes\n"
1237                         "       Lists all registered phoneprov http routes.\n";
1238                 return NULL;
1239         case CLI_GENERATE:
1240                 return NULL;
1241         }
1242
1243         /* This currently iterates over routes twice, but it is the only place I've needed
1244          * to really separate static an dynamic routes, so I've just left it this way. */
1245         ast_cli(a->fd, "Static routes\n\n");
1246         ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
1247         i = ao2_iterator_init(http_routes, 0);
1248         while ((route = ao2_iterator_next(&i))) {
1249                 if (!route->user)
1250                         ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1251                 route = unref_route(route);
1252         }
1253         ao2_iterator_destroy(&i);
1254
1255         ast_cli(a->fd, "\nDynamic routes\n\n");
1256         ast_cli(a->fd, FORMAT, "Relative URI", "Template");
1257
1258         i = ao2_iterator_init(http_routes, 0);
1259         while ((route = ao2_iterator_next(&i))) {
1260                 if (route->user)
1261                         ast_cli(a->fd, FORMAT, route->uri, route->file->template);
1262                 route = unref_route(route);
1263         }
1264         ao2_iterator_destroy(&i);
1265
1266         return CLI_SUCCESS;
1267 }
1268
1269 static struct ast_cli_entry pp_cli[] = {
1270         AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
1271 };
1272
1273 static struct ast_http_uri phoneprovuri = {
1274         .callback = phoneprov_callback,
1275         .description = "Asterisk HTTP Phone Provisioning Tool",
1276         .uri = "phoneprov",
1277         .has_subtree = 1,
1278         .data = NULL,
1279         .key = __FILE__,
1280 };
1281
1282 /*!
1283  * \brief Load the module
1284  *
1285  * Module loading including tests for configuration or dependencies.
1286  * This function can return AST_MODULE_LOAD_FAILURE, AST_MODULE_LOAD_DECLINE,
1287  * or AST_MODULE_LOAD_SUCCESS. If a dependency or environment variable fails
1288  * tests return AST_MODULE_LOAD_FAILURE. If the module can not load the 
1289  * configuration file or other non-critical problem return 
1290  * AST_MODULE_LOAD_DECLINE. On success return AST_MODULE_LOAD_SUCCESS.
1291  */
1292 static int load_module(void)
1293 {
1294         profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
1295
1296         http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
1297
1298         users = ao2_container_alloc(MAX_USER_BUCKETS, users_hash_fn, users_cmp_fn);
1299
1300         AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
1301         ast_mutex_init(&globals_lock);
1302
1303         ast_custom_function_register(&pp_each_user_function);
1304         ast_custom_function_register(&pp_each_extension_function);
1305         ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
1306
1307         set_config();
1308         ast_http_uri_link(&phoneprovuri);
1309
1310         return 0;
1311 }
1312
1313 static int unload_module(void)
1314 {
1315         struct ast_var_t *var;
1316
1317         ast_http_uri_unlink(&phoneprovuri);
1318         ast_custom_function_unregister(&pp_each_user_function);
1319         ast_custom_function_unregister(&pp_each_extension_function);
1320         ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1321
1322         delete_routes();
1323         delete_users();
1324         delete_profiles();
1325         ao2_ref(profiles, -1);
1326         ao2_ref(http_routes, -1);
1327         ao2_ref(users, -1);
1328
1329         ast_mutex_lock(&globals_lock);
1330         while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1331                 ast_var_delete(var);
1332         }
1333         ast_mutex_unlock(&globals_lock);
1334
1335         ast_mutex_destroy(&globals_lock);
1336
1337         return 0;
1338 }
1339
1340 static int reload(void)
1341 {
1342         struct ast_var_t *var;
1343
1344         delete_routes();
1345         delete_users();
1346         delete_profiles();
1347
1348         ast_mutex_lock(&globals_lock);
1349         while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries))) {
1350                 ast_var_delete(var);
1351         }
1352         ast_mutex_unlock(&globals_lock);
1353
1354         set_config();
1355
1356         return 0;
1357 }
1358
1359 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "HTTP Phone Provisioning",
1360                 .load = load_module,
1361                 .unload = unload_module,
1362                 .reload = reload,
1363         );