1/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
2
3   This program is free software; you can redistribute it and/or modify
4   it under the terms of the GNU General Public License as published by
5   the Free Software Foundation; version 2 dated June, 1991, or
6   (at your option) version 3 dated 29 June, 2007.
7
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11   GNU General Public License for more details.
12
13   You should have received a copy of the GNU General Public License
14   along with this program.  If not, see <http://www.gnu.org/licenses/>.
15*/
16
17#include "dnsmasq.h"
18#ifdef HAVE_INOTIFY
19
20#include <sys/inotify.h>
21#include <sys/param.h> /* For MAXSYMLINKS */
22
23/* the strategy is to set a inotify on the directories containing
24   resolv files, for any files in the directory which are close-write
25   or moved into the directory.
26
27   When either of those happen, we look to see if the file involved
28   is actually a resolv-file, and if so, call poll-resolv with
29   the "force" argument, to ensure it's read.
30
31   This adds one new error condition: the directories containing
32   all specified resolv-files must exist at start-up, even if the actual
33   files don't.
34*/
35
36static char *inotify_buffer;
37#define INOTIFY_SZ (sizeof(struct inotify_event) + NAME_MAX + 1)
38
39/* If path is a symbolic link, return the path it
40   points to, made absolute if relative.
41   If path doesn't exist or is not a symlink, return NULL.
42   Return value is malloc'ed */
43static char *my_readlink(char *path)
44{
45  ssize_t rc, size = 64;
46  char *buf;
47
48  while (1)
49    {
50      buf = safe_malloc(size);
51      rc = readlink(path, buf, (size_t)size);
52
53      if (rc == -1)
54	{
55	  /* Not link or doesn't exist. */
56	  if (errno == EINVAL || errno == ENOENT)
57	    return NULL;
58	  else
59	    die(_("cannot access path %s: %s"), path, EC_MISC);
60	}
61      else if (rc < size-1)
62	{
63	  char *d;
64
65	  buf[rc] = 0;
66	  if (buf[0] != '/' && ((d = strrchr(path, '/'))))
67	    {
68	      /* Add path to relative link */
69	      char *new_buf = safe_malloc((d - path) + strlen(buf) + 2);
70	      *(d+1) = 0;
71	      strcpy(new_buf, path);
72	      strcat(new_buf, buf);
73	      free(buf);
74	      buf = new_buf;
75	    }
76	  return buf;
77	}
78
79      /* Buffer too small, increase and retry */
80      size += 64;
81      free(buf);
82    }
83}
84
85void inotify_dnsmasq_init()
86{
87  struct resolvc *res;
88  inotify_buffer = safe_malloc(INOTIFY_SZ);
89  daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
90
91  if (daemon->inotifyfd == -1)
92    die(_("failed to create inotify: %s"), NULL, EC_MISC);
93
94  if (option_bool(OPT_NO_RESOLV))
95    return;
96
97  for (res = daemon->resolv_files; res; res = res->next)
98    {
99      char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1);
100      int links = MAXSYMLINKS;
101
102      strcpy(path, res->name);
103
104      /* Follow symlinks until we reach a non-symlink, or a non-existant file. */
105      while ((new_path = my_readlink(path)))
106	{
107	  if (links-- == 0)
108	    die(_("too many symlinks following %s"), res->name, EC_MISC);
109	  free(path);
110	  path = new_path;
111	}
112
113      res->wd = -1;
114
115      if ((d = strrchr(path, '/')))
116	{
117	  *d = 0; /* make path just directory */
118	  res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO);
119
120	  res->file = d+1; /* pointer to filename */
121	  *d = '/';
122
123	  if (res->wd == -1 && errno == ENOENT)
124	    die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC);
125	}
126
127      if (res->wd == -1)
128	die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
129
130    }
131}
132
133
134/* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */
135void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz)
136{
137  struct hostsfile *ah;
138
139  for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
140    {
141      DIR *dir_stream = NULL;
142      struct dirent *ent;
143      struct stat buf;
144
145      if (!(ah->flags & flag))
146	continue;
147
148      if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
149	{
150	  my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"),
151		    ah->fname, strerror(errno));
152	  continue;
153	}
154
155       if (!(ah->flags & AH_WD_DONE))
156	 {
157	   ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO);
158	   ah->flags |= AH_WD_DONE;
159	 }
160
161       /* Read contents of dir _after_ calling add_watch, in the hope of avoiding
162	  a race which misses files being added as we start */
163       if (ah->wd == -1 || !(dir_stream = opendir(ah->fname)))
164	 {
165	   my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"),
166		     ah->fname, strerror(errno));
167	   continue;
168	 }
169
170       while ((ent = readdir(dir_stream)))
171	 {
172	   size_t lendir = strlen(ah->fname);
173	   size_t lenfile = strlen(ent->d_name);
174	   char *path;
175
176	   /* ignore emacs backups and dotfiles */
177	   if (lenfile == 0 ||
178	       ent->d_name[lenfile - 1] == '~' ||
179	       (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') ||
180	       ent->d_name[0] == '.')
181	     continue;
182
183	   if ((path = whine_malloc(lendir + lenfile + 2)))
184	     {
185	       strcpy(path, ah->fname);
186	       strcat(path, "/");
187	       strcat(path, ent->d_name);
188
189	       /* ignore non-regular files */
190	       if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
191		 {
192		   if (ah->flags & AH_HOSTS)
193		     total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz);
194#ifdef HAVE_DHCP
195		   else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT))
196		     option_read_dynfile(path, ah->flags);
197#endif
198		 }
199
200	       free(path);
201	     }
202	 }
203    }
204}
205
206int inotify_check(time_t now)
207{
208  int hit = 0;
209  struct hostsfile *ah;
210
211  while (1)
212    {
213      int rc;
214      char *p;
215      struct resolvc *res;
216      struct inotify_event *in;
217
218      while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
219
220      if (rc <= 0)
221	break;
222
223      for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len)
224	{
225	  in = (struct inotify_event*)p;
226
227	  for (res = daemon->resolv_files; res; res = res->next)
228	    if (res->wd == in->wd && in->len != 0 && strcmp(res->file, in->name) == 0)
229	      hit = 1;
230
231	  /* ignore emacs backups and dotfiles */
232	  if (in->len == 0 ||
233	      in->name[in->len - 1] == '~' ||
234	      (in->name[0] == '#' && in->name[in->len - 1] == '#') ||
235	      in->name[0] == '.')
236	    continue;
237
238	  for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
239	    if (ah->wd == in->wd)
240	      {
241		size_t lendir = strlen(ah->fname);
242		char *path;
243
244		if ((path = whine_malloc(lendir + in->len + 2)))
245		  {
246		    strcpy(path, ah->fname);
247		    strcat(path, "/");
248		    strcat(path, in->name);
249
250		    my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path);
251
252		    if (ah->flags & AH_HOSTS)
253		      {
254			read_hostsfile(path, ah->index, 0, NULL, 0);
255#ifdef HAVE_DHCP
256			if (daemon->dhcp || daemon->doing_dhcp6)
257			  {
258			    /* Propogate the consequences of loading a new dhcp-host */
259			    dhcp_update_configs(daemon->dhcp_conf);
260			    lease_update_from_configs();
261			    lease_update_file(now);
262			    lease_update_dns(1);
263			  }
264#endif
265		      }
266#ifdef HAVE_DHCP
267		    else if (ah->flags & AH_DHCP_HST)
268		      {
269			if (option_read_dynfile(path, AH_DHCP_HST))
270			  {
271			    /* Propogate the consequences of loading a new dhcp-host */
272			    dhcp_update_configs(daemon->dhcp_conf);
273			    lease_update_from_configs();
274			    lease_update_file(now);
275			    lease_update_dns(1);
276			  }
277		      }
278		    else if (ah->flags & AH_DHCP_OPT)
279		      option_read_dynfile(path, AH_DHCP_OPT);
280#endif
281
282		    free(path);
283		  }
284	      }
285	}
286    }
287  return hit;
288}
289
290#endif  /* INOTIFY */
291
292