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