bffe1a2df3449eaea9f647c98fc0bd2b3efe6d3d
[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] = ""; /*!< 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                 /* Unless we are overridden by serveriface or serveraddr, we set the SERVER variable to
401                  * the IP address we are listening on that the phone contacted for this config file */
402                 if (ast_strlen_zero(global_server)) {
403                         struct sockaddr name;
404                         socklen_t namelen = sizeof(name);
405                         int res;
406
407                         if ((res = getsockname(ser->fd, &name, &namelen)))
408                                 ast_log(LOG_WARNING, "Could not get server IP, breakage likely.\n");
409                         else {
410                                 struct ast_var_t *var;
411
412                                 if ((var = ast_var_assign("SERVER", ast_inet_ntoa(((struct sockaddr_in *)&name)->sin_addr))))
413                                         AST_LIST_INSERT_TAIL(route->user->headp, var, entries);
414                         }
415                 }
416
417                 pbx_substitute_variables_varshead(route->user->headp, file, tmp, bufsize);
418         
419                 if (file)
420                         ast_free(file);
421
422                 ast_str_append(&result, 0,
423                         "Content-Type: %s\r\n"
424                         "Content-length: %d\r\n"
425                         "\r\n"
426                         "%s", route->file->mime_type, (int) strlen(tmp), tmp);
427
428                 if (tmp)
429                         ast_free(tmp);
430
431                 route = unref_route(route);
432
433                 return result;
434         }
435
436 out404:
437         *status = 404;
438         *title = strdup("Not Found");
439         *contentlength = 0;
440         return ast_http_error(404, "Not Found", NULL, "Nothing to see here.  Move along.");
441
442 out500:
443         route = unref_route(route);
444         *status = 500;
445         *title = strdup("Internal Server Error");
446         *contentlength = 0;
447         return ast_http_error(500, "Internal Error", NULL, "An internal error has occured.");
448 }
449
450 /*! \brief Build a route structure and add it to the list of available http routes
451         \param pp_file File to link to the route
452         \param user User to link to the route (NULL means static route)
453         \param uri URI of the route
454 */
455 static void build_route(struct phoneprov_file *pp_file, struct user *user, char *uri)
456 {
457         struct http_route *route;
458         
459         if (!(route = ao2_alloc(sizeof(*route), route_destructor)))
460                 return;
461
462         if (ast_string_field_init(route, 32)) {
463                 ast_log(LOG_ERROR, "Couldn't create string fields for %s\n", pp_file->format);
464                 route = unref_route(route);
465                 return;
466         }
467
468         ast_string_field_set(route, uri, S_OR(uri, pp_file->format));
469         route->user = user;
470         route->file = pp_file;
471
472         ao2_link(http_routes, route);
473
474         route = unref_route(route);
475 }
476
477 /*! \brief Build a phone profile and add it to the list of phone profiles
478         \param name the name of the profile
479         \param v ast_variable from parsing phoneprov.conf
480 */
481 static void build_profile(const char *name, struct ast_variable *v)
482 {
483         struct phone_profile *profile;
484         struct ast_var_t *var;
485
486         if (!(profile = ao2_alloc(sizeof(*profile), profile_destructor)))
487                 return;
488
489         if (ast_string_field_init(profile, 32)) {
490                 profile = unref_profile(profile);
491                 return;
492         }
493         
494         if (!(profile->headp = ast_calloc(1, sizeof(*profile->headp)))) {
495                 profile = unref_profile(profile);
496                 return;
497         }
498
499         AST_LIST_HEAD_INIT_NOLOCK(&profile->static_files);
500         AST_LIST_HEAD_INIT_NOLOCK(&profile->dynamic_files);
501
502         ast_string_field_set(profile, name, name);
503         for (; v; v = v->next) {
504                 if (!strcasecmp(v->name, "mime_type")) {
505                         ast_string_field_set(profile, default_mime_type, v->value);
506                 } else if (!strcasecmp(v->name, "setvar")) {
507                         struct ast_var_t *var;
508                         char *value_copy = ast_strdupa(v->value);
509
510                         AST_DECLARE_APP_ARGS(args,
511                                 AST_APP_ARG(varname);
512                                 AST_APP_ARG(varval);
513                         );
514                         
515                         AST_NONSTANDARD_APP_ARGS(args, value_copy, '=');
516                         do {
517                                 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
518                                         break;
519                                 args.varname = ast_strip(args.varname);
520                                 args.varval = ast_strip(args.varval);
521                                 if (ast_strlen_zero(args.varname) || ast_strlen_zero(args.varval))
522                                         break;
523                                 if ((var = ast_var_assign(args.varname, args.varval)))
524                                         AST_LIST_INSERT_TAIL(profile->headp, var, entries);
525                         } while (0);
526                 } else if (!strcasecmp(v->name, "staticdir")) {
527                         ast_string_field_set(profile, staticdir, v->value);
528                 } else {
529                         struct phoneprov_file *pp_file;
530                         char *file_extension;
531                         char *value_copy = ast_strdupa(v->value); 
532
533                         AST_DECLARE_APP_ARGS(args,
534                                 AST_APP_ARG(filename);
535                                 AST_APP_ARG(mimetype);
536                         );
537
538                         if (!(pp_file = ast_calloc(1, sizeof(*pp_file)))) {
539                                 profile = unref_profile(profile);
540                                 return;
541                         }
542                         if (ast_string_field_init(pp_file, 32)) {
543                                 ast_free(pp_file);
544                                 profile = unref_profile(profile);
545                                 return;
546                         }
547
548                         if ((file_extension = strrchr(pp_file->format, '.')))
549                                 file_extension++;
550
551                         AST_STANDARD_APP_ARGS(args, value_copy);
552
553                         /* Mime type order of preference
554                          * 1) Specific mime-type defined for file in profile
555                          * 2) Mime determined by extension
556                          * 3) Default mime type specified in profile
557                          * 4) text/plain
558                          */
559                         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"))));
560
561                         if (!strcasecmp(v->name, "static_file")) {
562                                 ast_string_field_set(pp_file, format, args.filename);
563                                 ast_string_field_build(pp_file, template, "%s%s", profile->staticdir, args.filename);
564                                 AST_LIST_INSERT_TAIL(&profile->static_files, pp_file, entry);
565                                 /* Add a route for the static files, as their filenames won't change per-user */
566                                 build_route(pp_file, NULL, NULL);
567                         } else {
568                                 ast_string_field_set(pp_file, format, v->name);
569                                 ast_string_field_set(pp_file, template, args.filename);
570                                 AST_LIST_INSERT_TAIL(&profile->dynamic_files, pp_file, entry);
571                         }
572                 }
573         }
574
575         /* Append the global variables to the variables list for this profile.
576          * This is for convenience later, when we need to provide a single
577          * variable list for use in substitution. */
578         AST_LIST_TRAVERSE(&global_variables, var, entries) {
579                 struct ast_var_t *new_var;
580                 if ((new_var = ast_var_assign(var->name, var->value)))
581                         AST_LIST_INSERT_TAIL(profile->headp, new_var, entries);
582         }
583
584         ao2_link(profiles, profile);
585
586         profile = unref_profile(profile);
587 }
588
589 /*! \brief Free all memory associated with a user */
590 static void delete_user(struct user *user)
591 {
592         struct ast_var_t *var;
593
594         while ((var = AST_LIST_REMOVE_HEAD(user->headp, entries)))
595                 ast_var_delete(var);
596
597         ast_free(user->headp);
598         ast_string_field_free_memory(user);
599         user->profile = unref_profile(user->profile);
600         free(user);
601 }
602
603 /*! \brief Destroy entire user list */
604 static void delete_users(void)
605 {
606         struct user *user;
607
608         AST_RWLIST_WRLOCK(&users);
609         while ((user = AST_LIST_REMOVE_HEAD(&users, entry)))
610                 delete_user(user);
611         AST_RWLIST_UNLOCK(&users);
612 }
613
614 /*! \brief Set all timezone-related variables based on a zone (i.e. America/New_York)
615         \param headp pointer to list of user variables
616         \param zone A time zone. NULL sets variables based on timezone of the machine
617 */
618 static void set_timezone_variables(struct varshead *headp, const char *zone)
619 {
620         time_t utc_time;
621         int dstenable;
622         time_t dststart;
623         time_t dstend;
624         struct ast_tm tm_info;
625         int tzoffset;
626         char buffer[21];
627         struct ast_var_t *var;
628         struct timeval tv;
629
630         time(&utc_time);
631         ast_get_dst_info(&utc_time, &dstenable, &dststart, &dstend, &tzoffset, zone);
632         snprintf(buffer, sizeof(buffer), "%d", tzoffset);
633         var = ast_var_assign("TZOFFSET", buffer);
634         if (var)
635                 AST_LIST_INSERT_TAIL(headp, var, entries); 
636
637         if (!dstenable)
638                 return;
639
640         if ((var = ast_var_assign("DST_ENABLE", "1")))
641                 AST_LIST_INSERT_TAIL(headp, var, entries);
642
643         tv.tv_sec = dststart; 
644         ast_localtime(&tv, &tm_info, zone);
645
646         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon+1);
647         if ((var = ast_var_assign("DST_START_MONTH", buffer)))
648                 AST_LIST_INSERT_TAIL(headp, var, entries);
649
650         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
651         if ((var = ast_var_assign("DST_START_MDAY", buffer)))
652                 AST_LIST_INSERT_TAIL(headp, var, entries);
653
654         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
655         if ((var = ast_var_assign("DST_START_HOUR", buffer)))
656                 AST_LIST_INSERT_TAIL(headp, var, entries);
657
658         tv.tv_sec = dstend;
659         ast_localtime(&tv, &tm_info, zone);
660
661         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mon + 1);
662         if ((var = ast_var_assign("DST_END_MONTH", buffer)))
663                 AST_LIST_INSERT_TAIL(headp, var, entries);
664
665         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_mday);
666         if ((var = ast_var_assign("DST_END_MDAY", buffer)))
667                 AST_LIST_INSERT_TAIL(headp, var, entries);
668
669         snprintf(buffer, sizeof(buffer), "%d", tm_info.tm_hour);
670         if ((var = ast_var_assign("DST_END_HOUR", buffer)))
671                 AST_LIST_INSERT_TAIL(headp, var, entries);
672 }
673
674 /*! \brief Build and return a user structure based on gathered config data */
675 static struct user *build_user(struct ast_config *cfg, const char *name, const char *mac, struct phone_profile *profile)
676 {
677         struct user *user;
678         struct ast_var_t *var;
679         const char *tmp;
680         int i;
681         
682         if (!(user = ast_calloc(1, sizeof(*user)))) {
683                 profile = unref_profile(profile);
684                 return NULL;
685         }
686         
687         if (!(user->headp = ast_calloc(1, sizeof(*user->headp)))) {
688                 profile = unref_profile(profile);
689                 ast_free(user);
690                 return NULL;
691         }
692
693         if (ast_string_field_init(user, 32)) {
694                 profile = unref_profile(profile);
695                 delete_user(user);
696                 return NULL;
697         }
698
699         ast_string_field_set(user, name, name);
700         ast_string_field_set(user, macaddress, mac);
701         user->profile = profile; /* already ref counted by find_profile */
702
703         for (i = 0; i < PP_VAR_LIST_LENGTH; i++) {
704                 tmp = ast_variable_retrieve(cfg, name, pp_variable_list[i].user_var);
705
706                 if (i == PP_TIMEZONE) {
707                         /* perfectly ok if tmp is NULL, will set variables based on server's time zone */
708                         set_timezone_variables(user->headp, tmp);
709                 }
710
711                 if (tmp && (var = ast_var_assign(pp_variable_list[i].template_var, tmp)))
712                         AST_LIST_INSERT_TAIL(user->headp, var, entries);
713         }
714
715         if (!ast_strlen_zero(global_server)) {
716                 if ((var = ast_var_assign("SERVER", global_server)))
717                         AST_LIST_INSERT_TAIL(user->headp, var, entries);
718         }
719
720         if (!ast_strlen_zero(global_serverport)) {
721                 if ((var = ast_var_assign("SERVER_PORT", global_serverport)))
722                         AST_LIST_INSERT_TAIL(user->headp, var, entries);
723         }
724
725         /* Append profile variables here, and substitute variables on profile
726          * setvars, so that we can use user specific variables in them */
727         AST_LIST_TRAVERSE(user->profile->headp, var, entries) {
728                 char expand_buf[VAR_BUF_SIZE] = {0,};
729                 struct ast_var_t *var2;
730
731                 pbx_substitute_variables_varshead(user->headp, var->value, expand_buf, sizeof(expand_buf));
732                 if ((var2 = ast_var_assign(var->name, expand_buf)))
733                         AST_LIST_INSERT_TAIL(user->headp, var2, entries);
734         }
735
736         return user;
737 }
738
739 /*! \brief Add an http route for dynamic files attached to the profile of the user */
740 static int build_user_routes(struct user *user)
741 {
742         struct phoneprov_file *pp_file;
743
744         AST_LIST_TRAVERSE(&user->profile->dynamic_files, pp_file, entry) {
745                 char expand_buf[VAR_BUF_SIZE] = { 0, };
746
747                 pbx_substitute_variables_varshead(user->headp, pp_file->format, expand_buf, sizeof(expand_buf));
748                 build_route(pp_file, user, expand_buf);
749         }
750
751         return 0;
752 }
753
754 /* \brief Parse config files and create appropriate structures */
755 static int set_config(void)
756 {
757         struct ast_config *cfg;
758         char *cat;
759         struct ast_variable *v;
760         struct ast_flags config_flags = { 0 };
761
762         /* Try to grab the port from sip.conf.  If we don't get it here, we'll set it
763          * to whatever is set in phoneprov.conf or default to 5060 */
764         if ((cfg = ast_config_load("sip.conf", config_flags))) {
765                 ast_copy_string(global_serverport, S_OR(ast_variable_retrieve(cfg, "general", "bindport"), "5060"), sizeof(global_serverport));
766                 ast_config_destroy(cfg);
767         }
768
769         if (!(cfg = ast_config_load("phoneprov.conf", config_flags))) {
770                 ast_log(LOG_ERROR, "Unable to load config phoneprov.conf\n");
771                 return -1;
772         }
773
774         cat = NULL;
775         while ((cat = ast_category_browse(cfg, cat))) {
776                 if (!strcasecmp(cat, "general")) {
777                         for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
778                                 if (!strcasecmp(v->name, "serveraddr"))
779                                         ast_copy_string(global_server, v->value, sizeof(global_server));
780                                 else if (!strcasecmp(v->name, "serveriface")) {
781                                         struct in_addr addr;
782                                         lookup_iface(v->value, &addr);
783                                         ast_copy_string(global_server, ast_inet_ntoa(addr), sizeof(global_server));
784                                 } else if (!strcasecmp(v->name, "serverport"))
785                                         ast_copy_string(global_serverport, v->value, sizeof(global_serverport));
786                                 else if (!strcasecmp(v->name, "default_profile"))
787                                         ast_copy_string(global_default_profile, v->value, sizeof(global_default_profile));
788                         }
789                 } else 
790                         build_profile(cat, ast_variable_browse(cfg, cat));
791         }
792
793         ast_config_destroy(cfg);
794
795         if (!(cfg = ast_config_load("users.conf", config_flags))) {
796                 ast_log(LOG_WARNING, "Unable to load users.cfg\n");
797                 return 0;
798         }
799
800         cat = NULL;
801         while ((cat = ast_category_browse(cfg, cat))) {
802                 const char *tmp, *mac;
803                 struct user *user;
804                 struct phone_profile *profile;
805                 struct ast_var_t *var;
806
807                 if (!strcasecmp(cat, "general")) {
808                         for (v = ast_variable_browse(cfg, cat); v; v = v->next) {
809                                 if (!strcasecmp(v->name, "vmexten")) {
810                                         if ((var = ast_var_assign("VOICEMAIL_EXTEN", v->value)))
811                                                 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
812                                 }
813                                 if (!strcasecmp(v->name, "localextenlength")) {
814                                         if ((var = ast_var_assign("EXTENSION_LENGTH", v->value)))
815                                                 AST_LIST_INSERT_TAIL(&global_variables, var, entries);
816                                 }
817                         }
818                 }
819                           
820                 if (!strcasecmp(cat, "authentication"))
821                         continue;
822
823                 if (!((tmp = ast_variable_retrieve(cfg, cat, "autoprov")) && ast_true(tmp)))    
824                         continue;
825
826                 if (!(mac = ast_variable_retrieve(cfg, cat, "macaddress"))) {
827                         ast_log(LOG_WARNING, "autoprov set for %s, but no mac address - skipping.\n", cat);
828                         continue;
829                 }
830
831                 tmp = S_OR(ast_variable_retrieve(cfg, cat, "profile"), global_default_profile);
832                 if (ast_strlen_zero(tmp)) {
833                         ast_log(LOG_WARNING, "No profile for user [%s] with mac '%s' - skipping\n", cat, mac);
834                         continue;
835                 }
836
837                 if (!(profile = find_profile(tmp))) {
838                         ast_log(LOG_WARNING, "Could not look up profile '%s' - skipping.\n", tmp);
839                         continue;
840                 }
841
842                 if (!(user = build_user(cfg, cat, mac, profile))) {
843                         ast_log(LOG_WARNING, "Could not create user %s - skipping.\n", cat);
844                         continue;
845                 }
846
847                 if (build_user_routes(user)) {
848                         ast_log(LOG_WARNING, "Could not create http routes for %s - skipping\n", user->name);
849                         delete_user(user);
850                         continue;
851                 }
852
853                 AST_RWLIST_WRLOCK(&users);
854                 AST_RWLIST_INSERT_TAIL(&users, user, entry);
855                 AST_RWLIST_UNLOCK(&users);
856         }
857
858         ast_config_destroy(cfg);
859
860         return 0;
861 }
862
863 /*! \brief Delete all http routes, freeing their memory */
864 static void delete_routes(void)
865 {
866         struct ao2_iterator i;
867         struct http_route *route;
868         
869         i = ao2_iterator_init(http_routes, 0);
870         while ((route = ao2_iterator_next(&i))) {
871                 ao2_unlink(http_routes, route);
872                 route = unref_route(route);
873         }
874 }
875
876 /*! \brief Delete all phone profiles, freeing their memory */
877 static void delete_profiles(void)
878 {
879         struct ao2_iterator i;
880         struct phone_profile *profile;
881
882         i = ao2_iterator_init(profiles, 0);
883         while ((profile = ao2_iterator_next(&i))) {
884                 ao2_unlink(profiles, profile);
885                 profile = unref_profile(profile);
886         }
887 }
888
889 /*! \brief A dialplan function that can be used to print a string for each phoneprov user */
890 static int pp_each_user_exec(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
891 {
892         char *tmp, expand_buf[VAR_BUF_SIZE] = {0,};
893         struct user *user;
894         AST_DECLARE_APP_ARGS(args,
895                 AST_APP_ARG(string);
896                 AST_APP_ARG(exclude_mac);
897         );
898         AST_STANDARD_APP_ARGS(args, data);
899
900         /* Fix data by turning %{ into ${ */
901         while ((tmp = strstr(args.string, "%{")))
902                 *tmp = '$';
903
904         AST_RWLIST_RDLOCK(&users);
905         AST_RWLIST_TRAVERSE(&users, user, entry) {
906                 if (!ast_strlen_zero(args.exclude_mac) && !strcasecmp(user->macaddress, args.exclude_mac))
907                         continue;
908                 pbx_substitute_variables_varshead(user->headp, args.string, expand_buf, sizeof(expand_buf));
909                 ast_build_string(&buf, &len, expand_buf);
910         }
911         AST_RWLIST_UNLOCK(&users);
912
913         return 0;
914 }
915
916 static struct ast_custom_function pp_each_user_function = {
917         .name = "PP_EACH_USER",
918         .synopsis = "Generate a string for each phoneprov user",
919         .syntax = "PP_EACH_USER(<string>|<exclude_mac>)",
920         .desc =
921                 "Pass in a string, with phoneprov variables you want substituted in the format of\n"
922                 "%{VARNAME}, and you will get the string rendered for each user in phoneprov\n"
923                 "excluding ones with MAC address <exclude_mac>. Probably not useful outside of\n"
924                 "res_phoneprov.\n"
925                 "\nExample: ${PP_EACH_USER(<item><fn>%{DISPLAY_NAME}</fn></item>|${MAC})",
926         .read = pp_each_user_exec,
927 };
928
929 /*! \brief CLI command to list static and dynamic routes */
930 static char *handle_show_routes(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
931 {
932 #define FORMAT "%-40.40s  %-30.30s\n"
933         struct ao2_iterator i;
934         struct http_route *route;
935         
936         switch(cmd) {
937         case CLI_INIT:
938                 e->command = "phoneprov show routes";
939                 e->usage =
940                         "Usage: phoneprov show routes\n"
941                         "       Lists all registered phoneprov http routes.\n";
942                 return NULL;
943         case CLI_GENERATE:
944                 return NULL;
945         }
946
947         /* This currently iterates over routes twice, but it is the only place I've needed
948          * to really separate static an dynamic routes, so I've just left it this way. */
949         ast_cli(a->fd, "Static routes\n\n");
950         ast_cli(a->fd, FORMAT, "Relative URI", "Physical location");
951         i = ao2_iterator_init(http_routes, 0);
952         while ((route = ao2_iterator_next(&i))) {
953                 if (!route->user)
954                         ast_cli(a->fd, FORMAT, route->uri, route->file->template);
955                 route = unref_route(route);
956         }
957
958         ast_cli(a->fd, "\nDynamic routes\n\n");
959         ast_cli(a->fd, FORMAT, "Relative URI", "Template");
960
961         i = ao2_iterator_init(http_routes, 0);
962         while ((route = ao2_iterator_next(&i))) {
963                 if (route->user)
964                         ast_cli(a->fd, FORMAT, route->uri, route->file->template);
965                 route = unref_route(route);
966         }
967
968         return CLI_SUCCESS;
969 }
970
971 static struct ast_cli_entry pp_cli[] = {
972         AST_CLI_DEFINE(handle_show_routes, "Show registered phoneprov http routes"),
973 };
974
975 static struct ast_http_uri phoneprovuri = {
976         .callback = phoneprov_callback,
977         .description = "Asterisk HTTP Phone Provisioning Tool",
978         .uri = "phoneprov",
979         .has_subtree = 1,
980 };
981
982 static int load_module(void)
983 {
984         profiles = ao2_container_alloc(MAX_PROFILE_BUCKETS, profile_hash_fn, profile_cmp_fn);
985
986         http_routes = ao2_container_alloc(MAX_ROUTE_BUCKETS, routes_hash_fn, routes_cmp_fn);
987
988         AST_LIST_HEAD_INIT_NOLOCK(&global_variables);
989         
990         ast_custom_function_register(&pp_each_user_function);
991         ast_cli_register_multiple(pp_cli, ARRAY_LEN(pp_cli));
992
993         set_config();
994         ast_http_uri_link(&phoneprovuri); 
995
996         return 0;
997 }
998
999 static int unload_module(void)
1000 {
1001         struct ast_var_t *var;
1002
1003         ast_http_uri_unlink(&phoneprovuri);
1004         ast_custom_function_unregister(&pp_each_user_function);
1005         ast_cli_unregister_multiple(pp_cli, ARRAY_LEN(pp_cli));
1006
1007         delete_routes();
1008         delete_users();
1009         delete_profiles();
1010         ao2_ref(profiles, -1);
1011         ao2_ref(http_routes, -1);
1012
1013         while ((var = AST_LIST_REMOVE_HEAD(&global_variables, entries)))
1014                 ast_var_delete(var);
1015
1016         return 0;
1017 }
1018
1019 static int reload(void) 
1020 {
1021         delete_routes();
1022         delete_users();
1023         delete_profiles();
1024         set_config();
1025
1026         return 0;
1027 }
1028
1029 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "HTTP Phone Provisioning",
1030                 .load = load_module,
1031                 .unload = unload_module,
1032                 .reload = reload,
1033         );