9eba4951c8e5ae7644818706095d8ac16f373796
[asterisk/asterisk.git] / dnsmgr.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2005-2006, Kevin P. Fleming
5  *
6  * Kevin P. Fleming <kpfleming@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Background DNS update manager
22  *
23  * \author Kevin P. Fleming <kpfleming@digium.com> 
24  */
25
26 #include <sys/types.h>
27 #include <netinet/in.h>
28 #include <sys/socket.h>
29 #include <arpa/inet.h>
30 #include <resolv.h>
31 #include <stdio.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <stdlib.h>
35 #include <regex.h>
36 #include <signal.h>
37
38 #include "asterisk.h"
39
40 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
41
42 #include "asterisk/dnsmgr.h"
43 #include "asterisk/linkedlists.h"
44 #include "asterisk/utils.h"
45 #include "asterisk/config.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/sched.h"
48 #include "asterisk/options.h"
49 #include "asterisk/cli.h"
50
51 static struct sched_context *sched;
52 static int refresh_sched = -1;
53 static pthread_t refresh_thread = AST_PTHREADT_NULL;
54
55 struct ast_dnsmgr_entry {
56         struct in_addr *result;         /* where we will store the resulting address */
57         struct in_addr last;            /* the last result, used to check if address has changed */
58         int changed;
59         AST_LIST_ENTRY(ast_dnsmgr_entry) list;
60         char name[1];                   /* just 1 here, but we use calloc to allocate the correct size */
61 };
62
63 static AST_LIST_HEAD_STATIC(entry_list, ast_dnsmgr_entry);
64
65 AST_MUTEX_DEFINE_STATIC(refresh_lock);
66
67 #define REFRESH_DEFAULT 300
68
69 static int enabled = 0;
70 static int refresh_interval;
71
72 struct refresh_info {
73         struct entry_list *entries;
74         int verbose;
75         unsigned int regex_present:1;
76         regex_t filter;
77 };
78
79 static struct refresh_info master_refresh_info = {
80         .entries = &entry_list,
81         .verbose = 0,
82 };
83
84 struct ast_dnsmgr_entry *ast_dnsmgr_get(const char *name, struct in_addr *result)
85 {
86         struct ast_dnsmgr_entry *entry;
87
88         if (!result || ast_strlen_zero(name) || !(entry = ast_calloc(1, sizeof(*entry) + strlen(name))))
89                 return NULL;
90
91         entry->result = result;
92         strcpy(entry->name, name);
93
94         AST_LIST_LOCK(&entry_list);
95         AST_LIST_INSERT_HEAD(&entry_list, entry, list);
96         AST_LIST_UNLOCK(&entry_list);
97
98         return entry;
99 }
100
101 void ast_dnsmgr_release(struct ast_dnsmgr_entry *entry)
102 {
103         if (!entry)
104                 return;
105
106         AST_LIST_LOCK(&entry_list);
107         AST_LIST_REMOVE(&entry_list, entry, list);
108         AST_LIST_UNLOCK(&entry_list);
109         if (option_verbose > 4)
110                 ast_verbose(VERBOSE_PREFIX_4 "removing dns manager for '%s'\n", entry->name);
111         free(entry);
112 }
113
114 int ast_dnsmgr_lookup(const char *name, struct in_addr *result, struct ast_dnsmgr_entry **dnsmgr)
115 {
116         if (ast_strlen_zero(name) || !result || !dnsmgr)
117                 return -1;
118
119         if (*dnsmgr && !strcasecmp((*dnsmgr)->name, name))
120                 return 0;
121
122         if (option_verbose > 3)
123                 ast_verbose(VERBOSE_PREFIX_3 "doing dnsmgr_lookup for '%s'\n", name);
124
125         /* if it's actually an IP address and not a name,
126            there's no need for a managed lookup */
127         if (inet_aton(name, result))
128                 return 0;
129
130         /* if the manager is disabled, do a direct lookup and return the result,
131            otherwise register a managed lookup for the name */
132         if (!enabled) {
133                 struct ast_hostent ahp;
134                 struct hostent *hp;
135
136                 if ((hp = ast_gethostbyname(name, &ahp)))
137                         memcpy(result, hp->h_addr, sizeof(result));
138                 return 0;
139         } else {
140                 if (option_verbose > 2)
141                         ast_verbose(VERBOSE_PREFIX_2 "adding dns manager for '%s'\n", name);
142                 *dnsmgr = ast_dnsmgr_get(name, result);
143                 return !*dnsmgr;
144         }
145 }
146
147 /*
148  * Refresh a dnsmgr entry
149  *
150  * XXX maybe we must lock the entry to make safer
151  */
152 int ast_dnsmgr_refresh(struct ast_dnsmgr_entry *entry, int verbose)
153 {
154         struct ast_hostent ahp;
155         struct hostent *hp;
156         char iabuf[INET_ADDRSTRLEN];
157         char iabuf2[INET_ADDRSTRLEN];
158         struct in_addr tmp;
159         
160         if (verbose && (option_verbose > 2))
161                 ast_verbose(VERBOSE_PREFIX_2 "refreshing '%s'\n", entry->name);
162
163         if ((hp = ast_gethostbyname(entry->name, &ahp))) {
164                 /* check to see if it has changed, do callback if requested (where de callback is defined ????) */
165                 memcpy(&tmp, hp->h_addr, sizeof(tmp));
166                 if (tmp.s_addr != entry->last.s_addr) {
167                         ast_log(LOG_NOTICE, "host '%s' changed from %s to %s\n", 
168                                 entry->name,
169                                 ast_inet_ntoa(iabuf, sizeof(iabuf), entry->last),
170                                 ast_inet_ntoa(iabuf2, sizeof(iabuf2), tmp));
171
172                         memcpy(entry->result, hp->h_addr, sizeof(entry->result));
173                         memcpy(&entry->last, hp->h_addr, sizeof(entry->last));
174                         entry->changed = 1;
175                         return 1;
176                 } 
177                 
178         }
179         return 0;
180 }
181
182 /*
183  * Check if dnsmgr entry has changed from since last call to this function
184  */
185 int ast_dnsmgr_changed(struct ast_dnsmgr_entry *entry) 
186 {
187         int ret = entry->changed;
188         entry->changed = 0;
189         
190         return ret;
191 }
192
193 static void *do_refresh(void *data)
194 {
195         for (;;) {
196                 pthread_testcancel();
197                 usleep(ast_sched_wait(sched));
198                 pthread_testcancel();
199                 ast_sched_runq(sched);
200         }
201         return NULL;
202 }
203
204 static int refresh_list(void *data)
205 {
206         struct refresh_info *info = data;
207         struct ast_dnsmgr_entry *entry;
208
209         /* if a refresh or reload is already in progress, exit now */
210         if (ast_mutex_trylock(&refresh_lock)) {
211                 if (info->verbose)
212                         ast_log(LOG_WARNING, "DNS Manager refresh already in progress.\n");
213                 return -1;
214         }
215
216         if (option_verbose > 2)
217                 ast_verbose(VERBOSE_PREFIX_2 "Refreshing DNS lookups.\n");
218         AST_LIST_LOCK(info->entries);
219         AST_LIST_TRAVERSE(info->entries, entry, list) {
220                 if (info->regex_present && regexec(&info->filter, entry->name, 0, NULL, 0))
221                     continue;
222
223                 ast_dnsmgr_refresh(entry, info->verbose);
224         }
225         AST_LIST_UNLOCK(info->entries);
226
227         ast_mutex_unlock(&refresh_lock);
228
229         /* automatically reschedule based on the interval */
230         return refresh_interval * 1000;
231 }
232
233 void dnsmgr_start_refresh(void)
234 {
235         if (refresh_sched > -1) {
236                 ast_sched_del(sched, refresh_sched);
237                 refresh_sched = ast_sched_add_variable(sched, 100, refresh_list, &master_refresh_info, 1);
238         }
239 }
240
241 static int do_reload(int loading);
242
243 static int handle_cli_reload(int fd, int argc, char *argv[])
244 {
245         if (argc > 2)
246                 return RESULT_SHOWUSAGE;
247
248         do_reload(0);
249         return 0;
250 }
251
252 static int handle_cli_refresh(int fd, int argc, char *argv[])
253 {
254         struct refresh_info info = {
255                 .entries = &entry_list,
256                 .verbose = 1,
257         };
258
259         if (argc > 3)
260                 return RESULT_SHOWUSAGE;
261
262         if (argc == 3) {
263                 if (regcomp(&info.filter, argv[2], REG_EXTENDED | REG_NOSUB))
264                         return RESULT_SHOWUSAGE;
265                 else
266                         info.regex_present = 1;
267         }
268
269         refresh_list(&info);
270
271         if (info.regex_present)
272                 regfree(&info.filter);
273
274         return 0;
275 }
276
277 static int handle_cli_status(int fd, int argc, char *argv[])
278 {
279         int count = 0;
280         struct ast_dnsmgr_entry *entry;
281
282         if (argc > 2)
283                 return RESULT_SHOWUSAGE;
284
285         ast_cli(fd, "DNS Manager: %s\n", enabled ? "enabled" : "disabled");
286         ast_cli(fd, "Refresh Interval: %d seconds\n", refresh_interval);
287         AST_LIST_LOCK(&entry_list);
288         AST_LIST_TRAVERSE(&entry_list, entry, list)
289                 count++;
290         AST_LIST_UNLOCK(&entry_list);
291         ast_cli(fd, "Number of entries: %d\n", count);
292
293         return 0;
294 }
295
296 static struct ast_cli_entry cli_reload = {
297         .cmda = { "dnsmgr", "reload", NULL },
298         .handler = handle_cli_reload,
299         .summary = "Reloads the DNS manager configuration",
300         .usage = 
301         "Usage: dnsmgr reload\n"
302         "       Reloads the DNS manager configuration.\n"
303 };
304
305 static struct ast_cli_entry cli_refresh = {
306         .cmda = { "dnsmgr", "refresh", NULL },
307         .handler = handle_cli_refresh,
308         .summary = "Performs an immediate refresh",
309         .usage = 
310         "Usage: dnsmgr refresh [pattern]\n"
311         "       Peforms an immediate refresh of the managed DNS entries.\n"
312         "       Optional regular expression pattern is used to filter the entries to refresh.\n",
313 };
314
315 static struct ast_cli_entry cli_status = {
316         .cmda = { "dnsmgr", "status", NULL },
317         .handler = handle_cli_status,
318         .summary = "Display the DNS manager status",
319         .usage =
320         "Usage: dnsmgr status\n"
321         "       Displays the DNS manager status.\n"
322 };
323
324 int dnsmgr_init(void)
325 {
326         if (!(sched = sched_context_create())) {
327                 ast_log(LOG_ERROR, "Unable to create schedule context.\n");
328                 return -1;
329         }
330         ast_cli_register(&cli_reload);
331         ast_cli_register(&cli_status);
332         return do_reload(1);
333 }
334
335 int dnsmgr_reload(void)
336 {
337         return do_reload(0);
338 }
339
340 static int do_reload(int loading)
341 {
342         struct ast_config *config;
343         const char *interval_value;
344         const char *enabled_value;
345         int interval;
346         int was_enabled;
347         int res = -1;
348
349         /* ensure that no refresh cycles run while the reload is in progress */
350         ast_mutex_lock(&refresh_lock);
351
352         /* reset defaults in preparation for reading config file */
353         refresh_interval = REFRESH_DEFAULT;
354         was_enabled = enabled;
355         enabled = 0;
356
357         if (refresh_sched > -1)
358                 ast_sched_del(sched, refresh_sched);
359
360         if ((config = ast_config_load("dnsmgr.conf"))) {
361                 if ((enabled_value = ast_variable_retrieve(config, "general", "enable"))) {
362                         enabled = ast_true(enabled_value);
363                 }
364                 if ((interval_value = ast_variable_retrieve(config, "general", "refreshinterval"))) {
365                         if (sscanf(interval_value, "%d", &interval) < 1)
366                                 ast_log(LOG_WARNING, "Unable to convert '%s' to a numeric value.\n", interval_value);
367                         else if (interval < 0)
368                                 ast_log(LOG_WARNING, "Invalid refresh interval '%d' specified, using default\n", interval);
369                         else
370                                 refresh_interval = interval;
371                 }
372                 ast_config_destroy(config);
373         }
374
375         if (enabled && refresh_interval)
376                 ast_log(LOG_NOTICE, "Managed DNS entries will be refreshed every %d seconds.\n", refresh_interval);
377
378         /* if this reload enabled the manager, create the background thread
379            if it does not exist */
380         if (enabled && !was_enabled && (refresh_thread == AST_PTHREADT_NULL)) {
381                 if (ast_pthread_create(&refresh_thread, NULL, do_refresh, NULL) < 0) {
382                         ast_log(LOG_ERROR, "Unable to start refresh thread.\n");
383                 }
384                 else {
385                         ast_cli_register(&cli_refresh);
386                         /* make a background refresh happen right away */
387                         refresh_sched = ast_sched_add_variable(sched, 100, refresh_list, &master_refresh_info, 1);
388                         res = 0;
389                 }
390         }
391         /* if this reload disabled the manager and there is a background thread,
392            kill it */
393         else if (!enabled && was_enabled && (refresh_thread != AST_PTHREADT_NULL)) {
394                 /* wake up the thread so it will exit */
395                 pthread_cancel(refresh_thread);
396                 pthread_kill(refresh_thread, SIGURG);
397                 pthread_join(refresh_thread, NULL);
398                 refresh_thread = AST_PTHREADT_NULL;
399                 ast_cli_unregister(&cli_refresh);
400                 res = 0;
401         }
402         else
403                 res = 0;
404
405         ast_mutex_unlock(&refresh_lock);
406
407         return res;
408 }