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