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