Make users list static
[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  * \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 #include "asterisk.h"
30
31 #include <sys/ioctl.h>
32 #include <sys/socket.h>
33 #include <net/if.h>
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 96773 $")
36
37 #include "asterisk/file.h"
38 #include "asterisk/paths.h"
39 #include "asterisk/pbx.h"
40 #include "asterisk/cli.h"
41 #include "asterisk/module.h"
42 #include "asterisk/http.h"
43 #include "asterisk/utils.h"
44 #include "asterisk/app.h"
45 #include "asterisk/strings.h"
46 #include "asterisk/stringfields.h"
47 #include "asterisk/options.h"
48 #include "asterisk/config.h"
49 #include "asterisk/acl.h"
50 #include "asterisk/astobj2.h"
51 #include "asterisk/version.h"
52
53 #ifdef LOW_MEMORY
54 #define MAX_PROFILE_BUCKETS 1
55 #define MAX_ROUTE_BUCKETS 1
56 #else
57 #define MAX_PROFILE_BUCKETS 17
58 #define MAX_ROUTE_BUCKETS 563
59 #endif /* LOW_MEMORY */
60
61 #define VAR_BUF_SIZE 4096
62
63 /*! \brief for use in lookup_iface */
64 static struct in_addr __ourip = { .s_addr = 0x00000000, };
65
66 /* \note This enum and the pp_variable_list must be in the same order or
67  * bad things happen! */
68 enum pp_variables {
69         PP_MACADDRESS,
70         PP_USERNAME,
71         PP_FULLNAME,
72         PP_SECRET,
73         PP_LABEL,
74         PP_CALLERID,
75         PP_TIMEZONE,
76         PP_VAR_LIST_LENGTH,     /* This entry must always be the last in the list */
77 };
78
79 /*! \brief Lookup table to translate between users.conf property names and
80  * variables for use in phoneprov templates */
81 static const struct pp_variable_lookup {
82         enum pp_variables id;
83         const char * const user_var;
84         const char * const template_var;
85 } pp_variable_list[] = {
86         { PP_MACADDRESS, "macaddress", "MAC" },
87         { PP_USERNAME, "username", "USERNAME" },
88         { PP_FULLNAME, "fullname", "DISPLAY_NAME" },
89         { PP_SECRET, "secret", "SECRET" },
90         { PP_LABEL, "label", "LABEL" },
91         { PP_CALLERID, "cid_number", "CALLERID" },
92         { PP_TIMEZONE, "timezone", "TIMEZONE" },
93 };
94
95 /*! \brief structure to hold file data */
96 struct phoneprov_file {
97         AST_DECLARE_STRING_FIELDS(
98                 AST_STRING_FIELD(format);       /*!< After variable substitution, becomes route->uri */
99                 AST_STRING_FIELD(template); /*!< Template/physical file location */
100                 AST_STRING_FIELD(mime_type);/*!< Mime-type of the file */
101         );
102         AST_LIST_ENTRY(phoneprov_file) entry;
103 };
104
105 /*! \brief structure to hold phone profiles read from phoneprov.conf */
106 struct phone_profile {
107         AST_DECLARE_STRING_FIELDS(
108                 AST_STRING_FIELD(name); /*!< Name of phone profile */
109                 AST_STRING_FIELD(default_mime_type);    /*!< Default mime type if it isn't provided */
110                 AST_STRING_FIELD(staticdir);    /*!< Subdirectory that static files are stored in */
111         );
112         struct varshead *headp; /*!< List of variables set with 'setvar' in phoneprov.conf */
113         AST_LIST_HEAD_NOLOCK(, phoneprov_file) static_files;    /*!< List of static files */
114         AST_LIST_HEAD_NOLOCK(, phoneprov_file) dynamic_files;   /*!< List of dynamic files */
115 };
116
117 /*! \brief structure to hold users read from users.conf */
118 struct user {
119         AST_DECLARE_STRING_FIELDS(
120                 AST_STRING_FIELD(name); /*!< Name of user */
121                 AST_STRING_FIELD(macaddress);   /*!< Mac address of user's phone */
122         );
123         struct phone_profile *profile;  /*!< Profile the phone belongs to */
124         struct varshead *headp; /*!< List of variables to substitute into templates */
125         AST_LIST_ENTRY(user) entry;
126 };
127
128 /*! \brief structure to hold http routes (valid URIs, and the files they link to) */
129 struct http_route {
130         AST_DECLARE_STRING_FIELDS(
131                 AST_STRING_FIELD(uri);  /*!< The URI requested */
132         );
133         struct phoneprov_file *file;    /*!< The file that links to the URI */
134         struct user *user;      /*!< The user that has variables to substitute into the file
135                                                  * NULL in the case of a static route */
136 };
137
138 static struct ao2_container *profiles;
139 static struct ao2_container *http_routes;
140 static AST_RWLIST_HEAD_STATIC(users, user);
141
142 /*! \brief Extensions whose mime types we think we know */
143 static struct {
144         char *ext;
145         char *mtype;
146 } mimetypes[] = {
147         { "png", "image/png" },
148         { "xml", "text/xml" },
149         { "jpg", "image/jpeg" },
150         { "js", "application/x-javascript" },
151         { "wav", "audio/x-wav" },
152         { "mp3", "audio/mpeg" },
153 };
154
155 char global_server[80] = "";    /*!< Server to substitute into templates */
156 char global_serverport[6] = "5060";     /*!< Server port to substitute into templates */
157 char global_default_profile[80] = "";   /*!< Default profile to use if one isn't specified */   
158
159 /*! \brief List of global variables currently available: VOICEMAIL_EXTEN, EXTENSION_LENGTH */
160 struct varshead global_variables;
161
162 /*! \brief Return mime type based on extension */
163 static char *ftype2mtype(const char *ftype)
164 {
165         int x;
166
167         if (ast_strlen_zero(ftype))
168                 return NULL;
169
170         for (x = 0;x < ARRAY_LEN(mimetypes);x++) {
171                 if (!strcasecmp(ftype, mimetypes[x].ext))
172                         return mimetypes[x].mtype;
173         }
174         
175         return NULL;
176 }
177
178 /* iface is the interface (e.g. eth0); address is the return value */
179 static int lookup_iface(const char *iface, struct in_addr *address)
180 {
181         int mysock, res = 0;
182         struct ifreq ifr;
183         struct sockaddr_in *sin;
184
185         memset(&ifr, 0, sizeof(ifr));
186         ast_copy_string(ifr.ifr_name, iface, sizeof(ifr.ifr_name));
187
188         mysock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
189         if (mysock < 0) {
190                 ast_log(LOG_ERROR, "Failed to create socket: %s\n", strerror(errno));
191                 return -1;
192         }
193
194         res = ioctl(mysock, SIOCGIFADDR, &ifr);
195
196         close(mysock);
197
198         if (res < 0) {
199                 ast_log(LOG_WARNING, "Unable to get IP of %s: %s\n", iface, strerror(errno));
200                 memcpy(address, &__ourip, sizeof(__ourip));
201                 return -1;
202         } else {
203                 sin = (struct sockaddr_in *)&ifr.ifr_addr;
204                 memcpy(address, &sin->sin_addr, sizeof(*address));
205                 return 0;
206         }
207 }
208
209 static struct phone_profile *unref_profile(struct phone_profile *prof)
210 {
211         ao2_ref(prof, -1);
212
213         return NULL;
214 }
215
216 /*! \brief Return a phone profile looked up by name */
217 static struct phone_profile *find_profile(const char *name)
218 {
219         struct phone_profile tmp = {
220                 .name = name,
221         };
222
223         return ao2_find(profiles, &tmp, OBJ_POINTER);
224 }
225
226 static int profile_hash_fn(const void *obj, const int flags)
227 {
228         const struct phone_profile *profile = obj;
229         
230         return ast_str_hash(profile->name);
231 }
232
233 static int profile_cmp_fn(void *obj, void *arg, int flags)
234 {
235         const struct phone_profile *profile1 = obj, *profile2 = arg;
236
237         return !strcasecmp(profile1->name, profile2->name) ? CMP_MATCH : 0;
238 }
239
240 static void delete_file(struct phoneprov_file *file)
241 {
242         ast_string_field_free_memory(file);
243         free(file);
244 }
245
246 static void profile_destructor(void *obj)
247 {
248         struct phone_profile *profile = obj;
249         struct phoneprov_file *file;
250         struct ast_var_t *var;
251
252         while ((file = AST_LIST_REMOVE_HEAD(&profile->static_files, entry)))
253                 delete_file(file);
254
255         while ((file = AST_LIST_REMOVE_HEAD(&profile->dynamic_files, entry)))
256                 delete_file(file);
257
258         while ((var = AST_LIST_REMOVE_HEAD(profile->headp, entries)))
259                 ast_var_delete(var);
260
261         free(profile->headp);
262         ast_string_field_free_memory(profile);
263 }
264
265 static struct http_route *unref_route(struct http_route *route)
266 {
267         ao2_ref(route, -1);
268
269         return NULL;
270 }
271
272 static int routes_hash_fn(const void *obj, const int flags)
273 {
274         const struct http_route *route = obj;
275         
276         return ast_str_hash(route->uri);
277 }
278
279 static int routes_cmp_fn(void *obj, void *arg, int flags)
280 {
281         const struct http_route *route1 = obj, *route2 = arg;
282
283         return !strcmp(route1->uri, route2->uri) ? CMP_MATCH : 0;
284 }
285
286 static void route_destructor(void *obj)
287 {
288         struct http_route *route = obj;
289
290         ast_string_field_free_memory(route);
291 }
292
293 /*! \brief Read a TEXT file into a string and return the length */
294 static int load_file(const char *filename, char **ret)
295 {
296         int len = 0;
297         FILE *f;
298         
299         if (!(f = fopen(filename, "r"))) {
300                 *ret = NULL;
301                 return -1;
302         }
303
304         fseek(f, 0, SEEK_END);
305         len = ftell(f);
306         fseek(f, 0, SEEK_SET);
307         if (!(*ret = ast_malloc(len + 1)))
308                 return -2;
309
310         if (len != fread(*ret, sizeof(char), len, f)) {
311                 free(*ret);
312                 *ret = NULL;
313                 return -3;
314         }
315
316         fclose(f);
317
318         (*ret)[len] = '\0';
319
320         return len;
321 }
322
323 /*! \brief Callback that is executed everytime an http request is received by this module */
324 static struct ast_str *phoneprov_callback(struct server_instance *ser, const char *uri, struct ast_variable *vars, int *status, char **title, int *contentlength)
325 {
326         struct http_route *route;
327         struct http_route search_route = {
328                 .uri = uri,
329         };
330         struct ast_str *result = ast_str_create(512);
331         char path[PATH_MAX];
332         char *file = NULL;
333         int len;
334         int fd;
335         char buf[256];
336         struct timeval tv = ast_tvnow();
337         struct ast_tm tm;
338
339         if (!(route = ao2_find(http_routes, &search_route, OBJ_POINTER)))
340                 goto out404;
341
342         snprintf(path, sizeof(path), "%s/phoneprov/%s", ast_config_AST_DATA_DIR, route->file->template);
343
344         if (!route->user) { /* Static file */
345
346                 fd = open(path, O_RDONLY);
347                 if (fd < 0)
348                         goto out500;
349
350                 len = lseek(fd, 0, SEEK_END);
351                 lseek(fd, 0, SEEK_SET);
352                 if (len < 0) {
353                         ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
354                         close(fd);
355                         goto out500;
356                 }
357
358                 ast_strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %Z", ast_localtime(&tv, &tm, "GMT"));
359             fprintf(ser->f, "HTTP/1.1 200 OK\r\n"
360                         "Server: Asterisk/%s\r\n"
361                         "Date: %s\r\n"
362                         "Connection: close\r\n"
363                         "Cache-Control: no-cache, no-store\r\n"
364                         "Content-Length: %d\r\n"
365                         "Content-Type: %s\r\n\r\n",
366                         ast_get_version(), buf, len, route->file->mime_type);
367                 
368                 while ((len = read(fd, buf, sizeof(buf))) > 0)
369                         fwrite(buf, 1, len, ser->f);
370
371                 close(fd);
372                 route = unref_route(route);
373                 return NULL;
374         } else { /* Dynamic file */
375                 int bufsize;
376                 char *tmp;
377
378                 len = load_file(path, &file);
379                 if (len < 0) {
380                         ast_log(LOG_WARNING, "Could not load file: %s (%d)\n", path, len);
381                         if (file)
382                                 ast_free(file);
383                         goto out500;
384                 }
385
386                 if (!file)
387                         goto out500;
388
389                 /* XXX This is a hack -- maybe sum length of all variables in route->user->headp and add that? */
390                 bufsize = len + VAR_BUF_SIZE;
391                 
392                 /* malloc() instead of alloca() here, just in case the file is bigger than
393                  * we have enough stack space for. */
394                 if (!(tmp = ast_calloc(1, bufsize))) {
395                         if (file)
396                                 ast_free(file);
397                         goto out500;
398                 }
399
400                 pbx_substitute_variables_varshead(route->user->headp, file, tmp, bufsize);
401         
402                 if (file)
403                         ast_free(file);
404
405                 ast_str_append(&result, 0,
406                         "Content-Type: %s\r\n"
407                         "Content-length: %d\r\n"
408                         "\r\n"
409                         "%s", route->file->mime_type, (int) strlen(tmp), tmp);
410
411                 if (tmp)
412                         ast_free(tmp);
413
414                 route = unref_route(route);
415
416                 return result;
417         }
418
419 out404:
420         *status = 404;
421         *title = strdup("Not Found");
422         *contentlength = 0;
423         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
424
425 out500:
426         route = unref_route(route);
427         *status = 500;
428         *title = strdup("Internal Server Error");
429         *contentlength = 0;
430         return ast_http_error(500, "Internal Error", NULL, "An internal error has occured.");
431 }
432
433 /*! \brief Build a route structure and add it to the list of available http routes
434         \param pp_file File to link to the route
435         \param user User to link to the route (NULL means static route)
436         \param uri URI of the route
437 */
438 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
439 {
440         struct http_route *route;
441         
442         if (!(route = ao2_alloc(sizeof(*route), route_destructor)))
443                 return;
444
445         if (ast_string_field_init(route, 32)) {
446                 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
447                 route = unref_route(route);
448                 return;
449         }
450
451         ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
452         route->user = user;
453         route->file = pp_file;
454
455         ao2_link(http_routes, route);
456
457         route = unref_route(route);
458 }
459
460 /*! \brief Build a phone profile and add it to the list of phone profiles
461         \param name the name of the profile
462         \param v ast_variable from parsing phoneprov.conf
463 */
464 static void build_profile(const char *name, struct ast_variable *v)
465 {
466         struct phone_profile *profile;
467         struct ast_var_t *var;
468
469         if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor)))
470                 return;
471
472         if (ast_string_field_init(profile, 32)) {
473                 profile = unref_profile(profile);
474                 return;
475         }
476         
477         if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
478                 profile = unref_profile(profile);
479                 return;
480         }
481
482         AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
483         AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
484
485         ast_string_field_set(profile, name, name);
486         for (; v; v = v->next) {
487                 if (!strcasecmp(v->name, "mime_type")) {
488                         ast_string_field_set(profile, default_mime_type, v->value);
489                 } else if (!strcasecmp(v->name, "setvar")) {
490                         struct ast_var_t *var;
491                         char *value_copy = ast_strdupa(v->value);
492
493                         AST_DECLARE_APP_ARGS(args,
494                                 AST_APP_ARG(varname);
495                                 AST_APP_ARG(varval);
496                         );
497                         
498                         AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
499                         do {
500                                 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
501                                         break;
502                                 args.varname = ast_strip(args.varname);
503                                 args.varval = ast_strip(args.varval);
504                                 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
505                                         break;
506                                 if ((var = ast_var_assign(args.varname, args.varval)))
507                                         AST_LIST_INSERT_TAIL(profile->headp, var, entries);
508                         } while (0);
509                 } else if (!strcasecmp(v->name, "staticdir")) {
510                         ast_string_field_set(profile, staticdir, v->value);
511                 } else {
512                         struct phoneprov_file *pp_file;
513                         char *file_extension;
514                         char *value_copy = ast_strdupa(v->value); 
515
516                         AST_DECLARE_APP_ARGS(args,
517                                 AST_APP_ARG(filename);
518                                 AST_APP_ARG(mimetype);
519                         );
520
521                         if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) {
522                                 profile = unref_profile(profile);
523                                 return;
524                         }
525                         if (ast_string_field_init(pp_file, 32)) {
526                                 ast_free(pp_file);
527                                 profile = unref_profile(profile);
528                                 return;
529                         }
530
531                         if ((file_extension = strrchr(pp_file->format, '.')))
532                                 file_extension++;
533
534                         AST_STANDARD_APP_ARGS(args, value_copy);
535
536                         /* Mime type order of preference
537                          * 1) Specific mime-type defined for file in profile
538                          * 2) Mime determined by extension
539                          * 3) Default mime type specified in profile
540                          * 4) text/plain
541                          */
542                         ast_string_field_set(pp_file, mime_type, S_OR(args.mimetype, (S_OR(S_OR(ftype2mtype(file_extension), profile->default_mime_type), "text/plain"))));
543
544                         if (!strcasecmp(v->name, "static_file")) {
545                                 ast_string_field_set(pp_file, format, args.filename);
546                                 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
547                                 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
548                                 /* Add a route for the static files, as their filenames won't change per-user */
549                                 build_route(pp_file, NULL, NULL);
550                         } else {
551                                 ast_string_field_set(pp_file, format, v->name);
552                                 ast_string_field_set(pp_file, template, args.filename);
553                                 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
554                         }
555                 }
556         }
557
558         /* Append the global variables to the variables list for this profile.
559          * This is for convenience later, when we need to provide a single
560          * variable list for use in substitution. */
561         AST_LIST_TRAVERSE(&global_variables, var, entries) {
562                 struct ast_var_t *new_var;
563                 if ((new_var = ast_var_assign(var->name, var->value)))
564                         AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
565         }
566
567         ao2_link(profiles, profile);
568
569         profile = unref_profile(profile);
570 }
571
572 /*! \brief Free all memory associated with a user */
573 static void delete_user(struct user *user)
574 {
575         struct ast_var_t *var;
576
577         while ((var = AST_LIST_REMOVE_HEAD(user->headp, entries)))
578                 ast_var_delete(var);
579
580         ast_free(user->headp);
581         ast_string_field_free_memory(user);
582         user->profile = unref_profile(user->profile);
583         free(user);
584 }
585
586 /*! \brief Destroy entire user list */
587 static void delete_users(void)
588 {
589         struct user *user;
590
591         AST_RWLIST_WRLOCK(&users);
592         while ((user = AST_LIST_REMOVE_HEAD(&users, entry)))
593                 delete_user(user);
594         AST_RWLIST_UNLOCK(&users);
595 }
596
597 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
598         \param headp pointer to list of user variables
599         \param zone A time zone. NULL sets variables based on timezone of the machine
600 */
601 static void set_timezone_variables(struct varshead *headp, const char *zone)
602 {
603         time_t utc_time;
604         int dstenable;
605         time_t dststart;
606         time_t dstend;
607         struct ast_tm tm_info;
608         int tzoffset;
609         char buffer[21];
610         struct ast_var_t *var;
611         struct timeval tv;
612
613         time(&utc_time);
614         ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
615         snprintf(buffer, sizeof(buffer), "%d", tzoffset);
616         var = ast_var_assign("TZOFFSET", buffer);
617         if (var)
618                 AST_LIST_INSERT_TAIL(headp, var, entries); 
619
620         if (!dstenable)
621                 return;
622
623         if ((var = ast_var_assign("DST_ENABLE", "1")))
624                 AST_LIST_INSERT_TAIL(headp, var, entries);
625
626         tv.tv_sec = dststart; 
627         ast_localtime(&tv, &tm_info, zone);
628
629         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
630         if ((var = ast_var_assign("DST_START_MONTH", buffer)))
631                 AST_LIST_INSERT_TAIL(headp, var, entries);
632
633         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
634         if ((var = ast_var_assign("DST_START_MDAY", buffer)))
635                 AST_LIST_INSERT_TAIL(headp, var, entries);
636
637         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
638         if ((var = ast_var_assign("DST_START_HOUR", buffer)))
639                 AST_LIST_INSERT_TAIL(headp, var, entries);
640
641         tv.tv_sec = dstend;
642         ast_localtime(&tv, &tm_info, zone);
643
644         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
645         if ((var = ast_var_assign("DST_END_MONTH", buffer)))
646                 AST_LIST_INSERT_TAIL(headp, var, entries);
647
648         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
649         if ((var = ast_var_assign("DST_END_MDAY", buffer)))
650                 AST_LIST_INSERT_TAIL(headp, var, entries);
651
652         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
653         if ((var = ast_var_assign("DST_END_HOUR", buffer)))
654                 AST_LIST_INSERT_TAIL(headp, var, entries);
655 }
656
657 /*! \brief Build and return a user structure based on gathered config data */
658 static struct user *build_user(struct ast_config *cfg, const char *name, const char *mac, struct phone_profile *profile)
659 {
660         struct user *user;
661         struct ast_var_t *var;
662         const char *tmp;
663         int i;
664         
665         if (!(user = ast_calloc(1, sizeof(*user)))) {
666                 profile = unref_profile(profile);
667                 return NULL;
668         }
669         
670         if (!(user->headp = ast_calloc(1, sizeof(*user->headp)))) {
671                 profile = unref_profile(profile);
672                 ast_free(user);
673                 return NULL;
674         }
675
676         if (ast_string_field_init(user, 32)) {
677                 profile = unref_profile(profile);
678                 delete_user(user);
679                 return NULL;
680         }
681
682         ast_string_field_set(user, name, name);
683         ast_string_field_set(user, macaddress, mac);
684         user->profile = profile; /* already ref counted by find_profile */
685
686         for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
687                 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
688
689                 if (i == PP_TIMEZONE) {
690                         /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
691                         set_timezone_variables(user->headp, tmp);
692                 }
693
694                 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp)))
695                         AST_LIST_INSERT_TAIL(user->headp, var, entries);
696         }
697
698         if (!ast_strlen_zero(global_server)) {
699                 if ((var = ast_var_assign("SERVER", global_server)))
700                         AST_LIST_INSERT_TAIL(user->headp, var, entries);
701                 if (!ast_strlen_zero(global_serverport)) {
702                         if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
703                                 AST_LIST_INSERT_TAIL(user->headp, var, entries);
704                 }
705         }
706
707         /* Append profile variables here, and substitute variables on profile
708          * setvars, so that we can use user specific variables in them */
709         AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
710                 char expand_buf[VAR_BUF_SIZE] = {0,};
711                 struct ast_var_t *var2;
712
713                 pbx_substitute_variables_varshead(user->headp, var->value, expand_buf, sizeof(expand_buf));
714                 if ((var2 = ast_var_assign(var->name, expand_buf)))
715                         AST_LIST_INSERT_TAIL(user->headp, var2, entries);
716         }
717
718         return user;
719 }
720
721 /*! \brief Add an http route for dynamic files attached to the profile of the user */
722 static int build_user_routes(struct user *user)
723 {
724         struct phoneprov_file *pp_file;
725
726         AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
727                 char expand_buf[VAR_BUF_SIZE] = { 0, };
728
729                 pbx_substitute_variables_varshead(user->headp, pp_file->format, expand_buf, sizeof(expand_buf));
730                 build_route(pp_file, user, expand_buf);
731         }
732
733         return 0;
734 }
735
736 /* \brief Parse config files and create appropriate structures */
737 static int set_config(void)
738 {
739         struct ast_config *phoneprov_cfg, *users_cfg;
740         char *cat;
741         struct ast_variable *v;
742         struct ast_flags config_flags = { 0 };
743
744         if (!(phoneprov_cfg = ast_config_load("phoneprov.conf", config_flags))) {
745                 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
746                 return -1;
747         }
748
749         cat = NULL;
750         while ((cat = ast_category_browse(phoneprov_cfg, cat))) {
751                 if (!strcasecmp(cat, "general")) {
752                         for (v = ast_variable_browse(phoneprov_cfg, cat); v; v = v->next) {
753                                 if (!strcasecmp(v->name, "serveraddr"))
754                                         ast_copy_string(global_server, v->value, sizeof(global_server));
755                                 else if (!strcasecmp(v->name, "serveriface")) {
756                                         struct in_addr addr;
757                                         lookup_iface(v->value, &addr);
758                                         ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
759                                 } else if (!strcasecmp(v->name, "serverport"))
760                                         ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
761                                 else if (!strcasecmp(v->name, "default_profile"))
762                                         ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
763                         }
764                         if (ast_strlen_zero(global_server))
765                                 ast_log(LOG_WARNING, "No serveraddr/serveriface set in phoneprov.conf.  Breakage likely.\n");
766                 } else 
767                         build_profile(cat, ast_variable_browse(phoneprov_cfg, cat));
768         }
769
770         ast_config_destroy(phoneprov_cfg);
771
772         if (!(users_cfg = ast_config_load("users.conf", config_flags))) {
773                 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
774                 return 0;
775         }
776
777         cat = NULL;
778         while ((cat = ast_category_browse(users_cfg, cat))) {
779                 const char *tmp, *mac;
780                 struct user *user;
781                 struct phone_profile *profile;
782                 struct ast_var_t *var;
783
784                 if (!strcasecmp(cat, "general")) {
785                         for (v = ast_variable_browse(users_cfg, cat); v; v = v->next) {
786                                 if (!strcasecmp(v->name, "vmexten")) {
787                                         if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value)))
788                                                 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
789                                 }
790                                 if (!strcasecmp(v->name, "localextenlength")) {
791                                         if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
792                                                 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
793                                 }
794                         }
795                 }
796                           
797                 if (!strcasecmp(cat, "authentication"))
798                         continue;
799
800                 if (!((tmp = ast_variable_retrieve(users_cfg, cat, "autoprov")) && ast_true(tmp)))      
801                         continue;
802
803                 if (!(mac = ast_variable_retrieve(users_cfg, cat, "macaddress"))) {
804                         ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
805                         continue;
806                 }
807
808                 tmp = S_OR(ast_variable_retrieve(users_cfg, cat, "profile"), global_default_profile);
809                 if (ast_strlen_zero(tmp)) {
810                         ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
811                         continue;
812                 }
813
814                 if (!(profile = find_profile(tmp))) {
815                         ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
816                         continue;
817                 }
818
819                 if (!(user = build_user(users_cfg, cat, mac, profile))) {
820                         ast_log(LOG_WARNING, "Could not create user %s - skipping.\n", cat);
821                         continue;
822                 }
823
824                 if (build_user_routes(user)) {
825                         ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->name);
826                         delete_user(user);
827                         continue;
828                 }
829
830                 AST_RWLIST_WRLOCK(&users);
831                 AST_RWLIST_INSERT_TAIL(&users, user, entry);
832                 AST_RWLIST_UNLOCK(&users);
833         }
834
835         ast_config_destroy(users_cfg);
836
837         return 0;
838 }
839
840 /*! \brief Delete all http routes, freeing their memory */
841 static void delete_routes(void)
842 {
843         struct ao2_iterator i;
844         struct http_route *route;
845         
846         i = ao2_iterator_init(http_routes, 0);
847         while ((route = ao2_iterator_next(&i))) {
848                 ao2_unlink(http_routes, route);
849                 route = unref_route(route);
850         }
851 }
852
853 /*! \brief Delete all phone profiles, freeing their memory */
854 static void delete_profiles(void)
855 {
856         struct ao2_iterator i;
857         struct phone_profile *profile;
858
859         i = ao2_iterator_init(profiles, 0);
860         while ((profile = ao2_iterator_next(&i))) {
861                 ao2_unlink(profiles, profile);
862                 profile = unref_profile(profile);
863         }
864 }
865
866 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
867 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
868 {
869         char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
870         struct user *user;
871         AST_DECLARE_APP_ARGS(args,
872                 AST_APP_ARG(string);
873                 AST_APP_ARG(exclude_mac);
874         );
875         AST_STANDARD_APP_ARGS(args, data);
876
877         /* Fix data by turning %{ into ${ */
878         while ((tmp = strstr(args.string, "%{")))
879                 *tmp = '$';
880
881         AST_RWLIST_RDLOCK(&users);
882         AST_RWLIST_TRAVERSE(&users, user, entry) {
883                 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac))
884                         continue;
885                 pbx_substitute_variables_varshead(user->headp, args.string, expand_buf, sizeof(expand_buf));
886                 ast_build_string(&buf, &len, expand_buf);
887         }
888         AST_RWLIST_UNLOCK(&users);
889
890         return 0;
891 }
892
893 static struct ast_custom_function pp_each_user_function = {
894         .name = "PP_EACH_USER",
895         .synopsis = "Generate a string for each phoneprov user",
896         .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
897         .desc =
898                 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
899                 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
900                 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
901                 "res_phoneprov.\n"
902                 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
903         .read = pp_each_user_exec,
904 };
905
906 /*! \brief CLI command to list static and dynamic routes */
907 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
908 {
909 #define FORMAT "%-40.40s  %-30.30s\n"
910         struct ao2_iterator i;
911         struct http_route *route;
912         
913         switch(cmd) {
914         case CLI_INIT:
915                 e->command = "phoneprov show routes";
916                 e->usage =
917                         "Usage: phoneprov show routes\n"
918                         "       Lists all registered phoneprov http routes.\n";
919                 return NULL;
920         case CLI_GENERATE:
921                 return NULL;
922         }
923
924         /* This currently iterates over routes twice, but it is the only place I've needed
925          * to really separate static an dynamic routes, so I've just left it this way. */
926         ast_cli(a->fd, "Static routes\n\n");
927         ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
928         i = ao2_iterator_init(http_routes, 0);
929         while ((route = ao2_iterator_next(&i))) {
930                 if (!route->user)
931                         ast_cli(a->fd, FORMAT, route->uri, route->file->template);
932                 route = unref_route(route);
933         }
934
935         ast_cli(a->fd, "\nDynamic routes\n\n");
936         ast_cli(a->fd, FORMAT, "Relative URI", "Template");
937
938         i = ao2_iterator_init(http_routes, 0);
939         while ((route = ao2_iterator_next(&i))) {
940                 if (route->user)
941                         ast_cli(a->fd, FORMAT, route->uri, route->file->template);
942                 route = unref_route(route);
943         }
944
945         return CLI_SUCCESS;
946 }
947
948 static struct ast_cli_entry pp_cli[] = {
949         AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
950 };
951
952 static struct ast_http_uri phoneprovuri = {
953         .callback = phoneprov_callback,
954         .description = "Asterisk HTTP Phone Provisioning Tool",
955         .uri = "phoneprov",
956         .has_subtree = 1,
957 };
958
959 static int load_module(void)
960 {
961         profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
962
963         http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
964
965         AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
966         
967         ast_custom_function_register(&pp_each_user_function);
968         ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
969
970         set_config();
971         ast_http_uri_link(&phoneprovuri); 
972
973         return 0;
974 }
975
976 static int unload_module(void)
977 {
978         struct ast_var_t *var;
979
980         ast_http_uri_unlink(&phoneprovuri);
981         ast_custom_function_unregister(&pp_each_user_function);
982         ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
983
984         delete_routes();
985         delete_users();
986         delete_profiles();
987         ao2_ref(profiles, -1);
988         ao2_ref(http_routes, -1);
989
990         while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries)))
991                 ast_var_delete(var);
992
993         return 0;
994 }
995
996 static int reload(void) 
997 {
998         delete_routes();
999         delete_users();
1000         delete_profiles();
1001         set_config();
1002
1003         return 0;
1004 }
1005
1006 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1007                 .load = load_module,
1008                 .unload = unload_module,
1009                 .reload = reload,
1010         );