• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/odhcpd/src/statefiles.c

  1 /*
  2  * SPDX-FileCopyrightText: 2013 Steven Barth <steven@midlink.org>
  3  * SPDX-FileCopyrightText: 2013 Hans Dedecker <dedeckeh@gmail.com>
  4  * SPDX-FileCopyrightText: 2022 Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
  5  * SPDX-FileCopyrightText: 2024 Paul Donald <newtwen@gmail.com>
  6  * SPDX-FileCopyrightText: 2024 David Härdeman <david@hardeman.nu>
  7  *
  8  * SPDX-License-Identifier: GPL2.0-only
  9  */
 10 
 11 #include <stdio.h>
 12 #include <stdint.h>
 13 #include <stddef.h>
 14 #include <string.h>
 15 #include <stdlib.h>
 16 #include <libgen.h>
 17 #include <unistd.h>
 18 #include <fcntl.h>
 19 #include <time.h>
 20 #include <netinet/in.h>
 21 #include <arpa/nameser.h>
 22 #include <arpa/inet.h>
 23 #include <resolv.h>
 24 
 25 #include <libubox/md5.h>
 26 
 27 #include "odhcpd.h"
 28 #include "dhcpv6-ia.h"
 29 #include "statefiles.h"
 30 
 31 static uint8_t statemd5[16];
 32 
 33 struct write_ctxt {
 34         FILE *fp;
 35         md5_ctx_t md5;
 36         struct interface *iface;
 37         time_t now; // CLOCK_MONOTONIC
 38         time_t wall_time;
 39         char *buf;
 40         int buf_len;
 41         int buf_idx;
 42 };
 43 
 44 static void statefiles_write_host(const char *ipbuf, const char *hostname, struct write_ctxt *ctxt)
 45 {
 46         char exp_dn[DNS_MAX_NAME_LEN];
 47 
 48         if (dn_expand(ctxt->iface->search, ctxt->iface->search + ctxt->iface->search_len,
 49                       ctxt->iface->search, exp_dn, sizeof(exp_dn)) > 0)
 50                 fprintf(ctxt->fp, "%s\t%s.%s\t%s\n", ipbuf, hostname, exp_dn, hostname);
 51         else
 52                 fprintf(ctxt->fp, "%s\t%s\n", ipbuf, hostname);
 53 }
 54 
 55 static bool statefiles_write_host6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease,
 56                                    struct in6_addr *addr)
 57 {
 58         char ipbuf[INET6_ADDRSTRLEN];
 59 
 60         if (!lease->hostname || lease->flags & OAF_BROKEN_HOSTNAME || !(lease->flags & OAF_DHCPV6_NA))
 61                 return false;
 62 
 63         if (ctxt->fp) {
 64                 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
 65                 statefiles_write_host(ipbuf, lease->hostname, ctxt);
 66         }
 67 
 68         return true;
 69 }
 70 
 71 static void statefiles_write_host6_cb(struct dhcpv6_lease *lease, struct in6_addr *addr, _unused int prefix,
 72                                       _unused uint32_t pref_lt, _unused uint32_t valid_lt, void *arg)
 73 {
 74         struct write_ctxt *ctxt = (struct write_ctxt *)arg;
 75 
 76         statefiles_write_host6(ctxt, lease, addr);
 77 }
 78 
 79 static bool statefiles_write_host4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease)
 80 {
 81         char ipbuf[INET_ADDRSTRLEN];
 82         struct in_addr addr = { .s_addr = lease->addr };
 83 
 84         if (!lease->hostname || lease->flags & OAF_BROKEN_HOSTNAME)
 85                 return false;
 86 
 87         if (ctxt->fp) {
 88                 inet_ntop(AF_INET, &addr, ipbuf, sizeof(ipbuf));
 89                 statefiles_write_host(ipbuf, lease->hostname, ctxt);
 90         }
 91 
 92         return true;
 93 }
 94 
 95 static void statefiles_write_hosts(time_t now)
 96 {
 97         struct write_ctxt ctxt;
 98         const char *tmp_hostsfile = ".odhcpd.hosts";
 99         int fd;
100 
101         if (config.dhcp_hostsdir_fd < 0)
102                 return;
103 
104         avl_for_each_element(&interfaces, ctxt.iface, avl) {
105                 char *hostsfile;
106 
107                 hostsfile = alloca(strlen(ODHCPD_HOSTS_FILE_PREFIX) + strlen(ctxt.iface->name) + 1);
108                 sprintf(hostsfile, "%s.%s", ODHCPD_HOSTS_FILE_PREFIX, ctxt.iface->name);
109 
110                 fd = openat(config.dhcp_hostsdir_fd, tmp_hostsfile, O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
111                 if (fd < 0)
112                         goto err;
113 
114                 if (lockf(fd, F_LOCK, 0) < 0)
115                         goto err;
116 
117                 if (ftruncate(fd, 0) < 0)
118                         goto err;
119 
120                 ctxt.fp = fdopen(fd, "w");
121                 if (!ctxt.fp)
122                         goto err;
123 
124                 if (ctxt.iface->dhcpv6 == MODE_SERVER) {
125                         struct dhcpv6_lease *lease;
126 
127                         list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) {
128                                 if (!(lease->flags & OAF_BOUND))
129                                         continue;
130 
131                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
132                                         continue;
133 
134                                 odhcpd_enum_addr6(ctxt.iface, lease, now,
135                                                   statefiles_write_host6_cb, &ctxt);
136                         }
137                 }
138 
139                 if (ctxt.iface->dhcpv4 == MODE_SERVER) {
140                         struct dhcpv4_lease *lease;
141 
142                         list_for_each_entry(lease, &ctxt.iface->dhcpv4_leases, head) {
143                                 if (!(lease->flags & OAF_BOUND))
144                                         continue;
145 
146                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
147                                         continue;
148 
149                                 statefiles_write_host4(&ctxt, lease);
150                         }
151                 }
152 
153                 fclose(ctxt.fp);
154                 renameat(config.dhcp_hostsdir_fd, tmp_hostsfile,
155                          config.dhcp_hostsdir_fd, hostsfile);
156         }
157 
158         return;
159 
160 err:
161         error("Unable to write hostsfile: %m");
162         close(fd);
163 }
164 
165 static void statefiles_write_state6_addr(struct dhcpv6_lease *lease, struct in6_addr *addr, int prefix,
166                                          _unused uint32_t pref_lt, _unused uint32_t valid_lt, void *arg)
167 {
168         struct write_ctxt *ctxt = (struct write_ctxt *)arg;
169         char ipbuf[INET6_ADDRSTRLEN];
170 
171         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
172 
173         if (statefiles_write_host6(ctxt, lease, addr)) {
174                 md5_hash(ipbuf, strlen(ipbuf), &ctxt->md5);
175                 md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5);
176         }
177 
178         if (!ctxt->fp)
179                 return;
180 
181         ctxt->buf_idx += snprintf(ctxt->buf + ctxt->buf_idx,
182                                   ctxt->buf_len - ctxt->buf_idx,
183                                   " %s/%d", ipbuf, prefix);
184 }
185 
186 static void statefiles_write_state6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease)
187 {
188         char duidbuf[DUID_HEXSTRLEN];
189 
190         ctxt->buf_idx = 0;
191         odhcpd_enum_addr6(ctxt->iface, lease, ctxt->now, statefiles_write_state6_addr, ctxt);
192 
193         if (!ctxt->fp)
194                 return;
195 
196         odhcpd_hexlify(duidbuf, lease->clid_data, lease->clid_len);
197 
198         /* # <iface> <hexduid> <hexiaid> <hostname> <valid_until> <assigned_[host|subnet]_id> <pfx_length> [<addrs> ...] */
199         fprintf(ctxt->fp,
200                 "# %s %s %x %s%s %" PRId64 " %" PRIx64 " %" PRIu8 "%s\n",
201                 ctxt->iface->ifname, duidbuf, ntohl(lease->iaid),
202                 (lease->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
203                 (lease->hostname ? lease->hostname : "-"),
204                 (lease->valid_until > ctxt->now ?
205                  (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) :
206                  (INFINITE_VALID(lease->valid_until) ? -1 : 0)),
207                 (lease->flags & OAF_DHCPV6_NA ?
208                  lease->assigned_host_id :
209                  (uint64_t)lease->assigned_subnet_id),
210                 lease->length,
211                 ctxt->buf);
212 }
213 
214 static void statefiles_write_state4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease)
215 {
216         char hexhwaddr[sizeof(lease->hwaddr) * 2 + 1];
217         struct in_addr addr = { .s_addr = lease->addr };
218         char ipbuf[INET6_ADDRSTRLEN];
219 
220         inet_ntop(AF_INET, &addr, ipbuf, sizeof(ipbuf));
221 
222         if (statefiles_write_host4(ctxt, lease)) {
223                 md5_hash(ipbuf, strlen(ipbuf), &ctxt->md5);
224                 md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5);
225         }
226 
227         if (!ctxt->fp)
228                 return;
229 
230         odhcpd_hexlify(hexhwaddr, lease->hwaddr, sizeof(lease->hwaddr));
231 
232         /* # <iface> <hexhwaddr> "ipv4" <hostname> <valid_until> <hexaddr> "32" <addrstr>"/32" */
233         fprintf(ctxt->fp,
234                 "# %s %s ipv4 %s%s %" PRId64 " %x 32 %s/32\n",
235                 ctxt->iface->ifname, hexhwaddr,
236                 (lease->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "",
237                 (lease->hostname ? lease->hostname : "-"),
238                 (lease->valid_until > ctxt->now ?
239                  (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) :
240                  (INFINITE_VALID(lease->valid_until) ? -1 : 0)),
241                 ntohl(lease->addr), ipbuf);
242 }
243 
244 /* Returns true if there are changes to be written to the hosts file(s) */
245 static bool statefiles_write_state(time_t now)
246 {
247         char leasebuf[512];
248         struct write_ctxt ctxt = {
249                 .fp = NULL,
250                 .buf = leasebuf,
251                 .buf_len = sizeof(leasebuf),
252                 .now = now,
253                 .wall_time = time(NULL),
254         };
255         char *tmp_statefile = NULL;
256         uint8_t newmd5[16];
257         int fd;
258 
259         if (config.dhcp_statedir_fd >= 0 && config.dhcp_statefile) {
260                 size_t tmp_statefile_strlen = strlen(config.dhcp_statefile) + 2;
261 
262                 tmp_statefile = alloca(tmp_statefile_strlen);
263                 sprintf(tmp_statefile, ".%s", config.dhcp_statefile);
264 
265                 fd = openat(config.dhcp_statedir_fd, tmp_statefile, O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
266                 if (fd < 0)
267                         goto err;
268 
269                 if (lockf(fd, F_LOCK, 0) < 0)
270                         goto err;
271 
272                 if (ftruncate(fd, 0) < 0)
273                         goto err;
274 
275                 ctxt.fp = fdopen(fd, "w");
276                 if (!ctxt.fp)
277                         goto err;
278         }
279 
280         md5_begin(&ctxt.md5);
281 
282         avl_for_each_element(&interfaces, ctxt.iface, avl) {
283                 if (ctxt.iface->dhcpv6 == MODE_SERVER) {
284                         struct dhcpv6_lease *lease;
285 
286                         list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) {
287                                 if (!(lease->flags & OAF_BOUND))
288                                         continue;
289 
290                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
291                                         continue;
292 
293                                 statefiles_write_state6(&ctxt, lease);
294                         }
295                 }
296 
297                 if (ctxt.iface->dhcpv4 == MODE_SERVER) {
298                         struct dhcpv4_lease *lease;
299 
300                         list_for_each_entry(lease, &ctxt.iface->dhcpv4_leases, head) {
301                                 if (!(lease->flags & OAF_BOUND))
302                                         continue;
303 
304                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
305                                         continue;
306 
307                                 statefiles_write_state4(&ctxt, lease);
308                         }
309                 }
310         }
311 
312         if (ctxt.fp) {
313                 fclose(ctxt.fp);
314 
315                 renameat(config.dhcp_statedir_fd, tmp_statefile,
316                          config.dhcp_statedir_fd, config.dhcp_statefile);
317         }
318 
319         md5_end(newmd5, &ctxt.md5);
320         if (!memcmp(newmd5, statemd5, sizeof(newmd5)))
321                 return false;
322 
323         memcpy(statemd5, newmd5, sizeof(statemd5));
324         return true;
325 
326 err:
327         error("Unable to write statefile: %m");
328         close(fd);
329         return false;
330 }
331 
332 bool statefiles_write()
333 {
334         time_t now = odhcpd_time();
335 
336         if (!statefiles_write_state(now))
337                 return false;
338 
339         statefiles_write_hosts(now);
340 
341         if (config.dhcp_cb) {
342                 char *argv[2] = { config.dhcp_cb, NULL };
343                 if (!vfork()) {
344                         execv(argv[0], argv);
345                         _exit(128);
346                 }
347         }
348 
349         return true;
350 }
351 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt