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