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