• 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  * SPDX-FileCopyrightText: 2025 Álvaro Fernández Rojas <noltari@gmail.com>
  8  *
  9  * SPDX-License-Identifier: GPL2.0-only
 10  */
 11 
 12 #include <stdio.h>
 13 #include <stdint.h>
 14 #include <stddef.h>
 15 #include <string.h>
 16 #include <stdlib.h>
 17 #include <libgen.h>
 18 #include <unistd.h>
 19 #include <fcntl.h>
 20 #include <time.h>
 21 #include <netinet/in.h>
 22 #include <netinet/ether.h>
 23 #include <arpa/nameser.h>
 24 #include <arpa/inet.h>
 25 #include <resolv.h>
 26 #include <spawn.h>
 27 
 28 #include <libubox/md5.h>
 29 #include <json-c/json.h>
 30 
 31 #include "odhcpd.h"
 32 #include "dhcpv6-ia.h"
 33 #include "statefiles.h"
 34 
 35 static uint8_t statemd5[16];
 36 
 37 struct write_ctxt {
 38         FILE *fp;
 39         md5_ctx_t md5;
 40         struct interface *iface;
 41         time_t now; // CLOCK_MONOTONIC
 42         time_t wall_time;
 43 };
 44 
 45 static FILE *statefiles_open_tmp_file(int dirfd)
 46 {
 47         int fd;
 48         FILE *fp;
 49 
 50         if (dirfd < 0)
 51                 return NULL;
 52 
 53         fd = openat(dirfd, ODHCPD_TMP_FILE, O_CREAT | O_WRONLY | O_CLOEXEC, 0644);
 54         if (fd < 0)
 55                 goto err;
 56 
 57         if (lockf(fd, F_LOCK, 0) < 0)
 58                 goto err;
 59 
 60         if (ftruncate(fd, 0) < 0)
 61                 goto err_del;
 62 
 63         fp = fdopen(fd, "w");
 64         if (!fp)
 65                 goto err_del;
 66 
 67         return fp;
 68 
 69 err_del:
 70         unlinkat(dirfd, ODHCPD_TMP_FILE, 0);
 71 err:
 72         close(fd);
 73         error("Failed to create temporary file: %m");
 74         return NULL;
 75 }
 76 
 77 static void statefiles_finish_tmp_file(int dirfd, FILE **fpp, const char *prefix, const char *suffix)
 78 {
 79         char *filename;
 80 
 81         if (dirfd < 0 || !fpp || !*fpp)
 82                 return;
 83 
 84         if (!prefix) {
 85                 unlinkat(dirfd, ODHCPD_TMP_FILE, 0);
 86                 fclose(*fpp);
 87                 *fpp = NULL;
 88                 return;
 89         }
 90 
 91         if (fflush(*fpp))
 92                 error("Error flushing tmpfile: %m");
 93 
 94         if (fsync(fileno(*fpp)) < 0)
 95                 error("Error synching tmpfile: %m");
 96 
 97         fclose(*fpp);
 98         *fpp = NULL;
 99 
100         if (suffix) {
101                 filename = alloca(strlen(prefix) + strlen(".") + strlen(suffix) + 1);
102                 sprintf(filename, "%s.%s", prefix, suffix);
103         } else {
104                 filename = alloca(strlen(prefix) + 1);
105                 sprintf(filename, "%s", prefix);
106         }
107 
108         renameat(dirfd, ODHCPD_TMP_FILE, dirfd, filename);
109 }
110 
111 #define JSON_LENGTH "length"
112 #define JSON_PREFIX "prefix"
113 #define JSON_SLAAC "slaac"
114 #define JSON_TIME "time"
115 
116 static inline time_t statefiles_time_from_json(time_t json_time)
117 {
118         time_t ref, now;
119 
120         ref = time(NULL);
121         now = odhcpd_time();
122 
123         if (now > json_time || ref > json_time)
124                 return 0;
125 
126         return json_time + (now - ref);
127 }
128 
129 static inline time_t statefiles_time_to_json(time_t config_time)
130 {
131         time_t ref, now;
132 
133         ref = time(NULL);
134         now = odhcpd_time();
135 
136         return config_time + (ref - now);
137 }
138 
139 static inline bool statefiles_ra_pio_enabled(struct interface *iface)
140 {
141         return config.ra_piodir_fd >= 0 && iface->ra == MODE_SERVER && !iface->master;
142 }
143 
144 static bool statefiles_ra_pio_time(json_object *slaac_json, time_t *slaac_time)
145 {
146         time_t pio_json_time, pio_time;
147         json_object *time_json;
148 
149         time_json = json_object_object_get(slaac_json, JSON_TIME);
150         if (!time_json)
151                 return true;
152 
153         pio_json_time = (time_t) json_object_get_int64(time_json);
154         if (!pio_json_time)
155                 return true;
156 
157         pio_time = statefiles_time_from_json(pio_json_time);
158         if (!pio_time)
159                 return false;
160 
161         *slaac_time = pio_time;
162 
163         return true;
164 }
165 
166 static json_object *statefiles_load_ra_pio_json(struct interface *iface)
167 {
168         json_object *json;
169         char filename[strlen(ODHCPD_PIO_FILE_PREFIX) + strlen(".") + strlen(iface->ifname) + 1];
170         int fd;
171 
172         sprintf(filename, "%s.%s", ODHCPD_PIO_FILE_PREFIX, iface->ifname);
173         fd = openat(config.ra_piodir_fd, filename, O_RDONLY | O_CLOEXEC);
174         if (fd < 0)
175                 return NULL;
176 
177         json = json_object_from_fd(fd);
178 
179         close(fd);
180 
181         if (!json)
182                 error("rfc9096: %s: json read error %s",
183                       iface->ifname,
184                       json_util_get_last_err());
185 
186         return json;
187 }
188 
189 void statefiles_read_prefix_information(struct interface *iface)
190 {
191         json_object *json, *slaac_json;
192         struct ra_pio *new_pios;
193         size_t pio_cnt;
194         time_t now;
195 
196         if (!statefiles_ra_pio_enabled(iface))
197                 return;
198 
199         json = statefiles_load_ra_pio_json(iface);
200         if (!json)
201                 return;
202 
203         slaac_json = json_object_object_get(json, JSON_SLAAC);
204         if (!slaac_json) {
205                 json_object_put(json);
206                 return;
207         }
208 
209         now = odhcpd_time();
210 
211         pio_cnt = json_object_array_length(slaac_json);
212         new_pios = realloc(iface->pios, sizeof(struct ra_pio) * pio_cnt);
213         if (!new_pios) {
214                 json_object_put(json);
215                 return;
216         }
217 
218         iface->pios = new_pios;
219         iface->pio_cnt = 0;
220         for (size_t i = 0; i < pio_cnt; i++) {
221                 json_object *cur_pio_json, *length_json, *prefix_json;
222                 const char *pio_str;
223                 time_t pio_lt = 0;
224                 struct ra_pio *pio;
225                 uint8_t pio_len;
226 
227                 cur_pio_json = json_object_array_get_idx(slaac_json, i);
228                 if (!cur_pio_json)
229                         continue;
230 
231                 if (!statefiles_ra_pio_time(cur_pio_json, &pio_lt))
232                         continue;
233 
234                 length_json = json_object_object_get(cur_pio_json, JSON_LENGTH);
235                 if (!length_json)
236                         continue;
237 
238                 prefix_json = json_object_object_get(cur_pio_json, JSON_PREFIX);
239                 if (!prefix_json)
240                         continue;
241 
242                 pio_len = (uint8_t) json_object_get_uint64(length_json);
243                 pio_str = json_object_get_string(prefix_json);
244                 pio = &iface->pios[iface->pio_cnt];
245 
246                 inet_pton(AF_INET6, pio_str, &pio->prefix);
247                 pio->length = pio_len;
248                 pio->lifetime = pio_lt;
249                 info("rfc9096: %s: load %s/%u (%u)",
250                      iface->ifname,
251                      pio_str,
252                      pio_len,
253                      ra_pio_lifetime(pio, now));
254 
255                 iface->pio_cnt++;
256         }
257 
258         json_object_put(json);
259 
260         if (!iface->pio_cnt) {
261                 free(iface->pios);
262                 iface->pios = NULL;
263         } else if (iface->pio_cnt != pio_cnt) {
264                 struct ra_pio *tmp;
265 
266                 tmp = realloc(iface->pios, sizeof(struct ra_pio) * iface->pio_cnt);
267                 if (tmp)
268                         iface->pios = tmp;
269         }
270 }
271 
272 void statefiles_write_prefix_information(struct interface *iface)
273 {
274         struct json_object *json, *slaac_json;
275         char ipv6_str[INET6_ADDRSTRLEN];
276         time_t now;
277         FILE *fp;
278 
279         if (!statefiles_ra_pio_enabled(iface))
280                 return;
281 
282         if (!iface->pio_update)
283                 return;
284 
285         fp = statefiles_open_tmp_file(config.ra_piodir_fd);
286         if (!fp)
287                 return;
288 
289         now = odhcpd_time();
290 
291         json = json_object_new_object();
292         if (!json)
293                 goto out;
294 
295         slaac_json = json_object_new_array_ext(iface->pio_cnt);
296         if (!slaac_json)
297                 goto out;
298 
299         json_object_object_add(json, JSON_SLAAC, slaac_json);
300 
301         for (size_t i = 0; i < iface->pio_cnt; i++) {
302                 struct json_object *cur_pio_json, *len_json, *pfx_json;
303                 const struct ra_pio *cur_pio = &iface->pios[i];
304 
305                 if (ra_pio_expired(cur_pio, now))
306                         continue;
307 
308                 cur_pio_json = json_object_new_object();
309                 if (!cur_pio_json)
310                         continue;
311 
312                 inet_ntop(AF_INET6, &cur_pio->prefix, ipv6_str, sizeof(ipv6_str));
313 
314                 pfx_json = json_object_new_string(ipv6_str);
315                 if (!pfx_json) {
316                         json_object_put(cur_pio_json);
317                         continue;
318                 }
319 
320                 len_json = json_object_new_uint64(cur_pio->length);
321                 if (!len_json) {
322                         json_object_put(cur_pio_json);
323                         json_object_put(pfx_json);
324                         continue;
325                 }
326 
327                 json_object_object_add(cur_pio_json, JSON_PREFIX, pfx_json);
328                 json_object_object_add(cur_pio_json, JSON_LENGTH, len_json);
329 
330                 if (cur_pio->lifetime) {
331                         struct json_object *time_json;
332                         time_t pio_lt;
333 
334                         pio_lt = statefiles_time_to_json(cur_pio->lifetime);
335 
336                         time_json = json_object_new_int64(pio_lt);
337                         if (time_json)
338                                 json_object_object_add(cur_pio_json, JSON_TIME, time_json);
339                 }
340 
341                 json_object_array_add(slaac_json, cur_pio_json);
342         }
343 
344         if (json_object_to_fd(fileno(fp), json, JSON_C_TO_STRING_PLAIN)) {
345                 error("rfc9096: %s: json write error %s",
346                       iface->ifname,
347                       json_util_get_last_err());
348                 goto out;
349         }
350 
351         statefiles_finish_tmp_file(config.ra_piodir_fd, &fp, ODHCPD_PIO_FILE_PREFIX, iface->ifname);
352         iface->pio_update = false;
353         warn("rfc9096: %s: piofile updated", iface->ifname);
354 
355 out:
356         json_object_put(json);
357         statefiles_finish_tmp_file(config.ra_piodir_fd, &fp, NULL, NULL);
358 }
359 
360 static void statefiles_write_host(const char *ipbuf, const char *hostname, struct write_ctxt *ctxt)
361 {
362         char exp_dn[DNS_MAX_NAME_LEN];
363 
364         if (dn_expand(ctxt->iface->dns_search, ctxt->iface->dns_search + ctxt->iface->dns_search_len,
365                       ctxt->iface->dns_search, exp_dn, sizeof(exp_dn)) > 0)
366                 fprintf(ctxt->fp, "%s\t%s.%s\t%s\n", ipbuf, hostname, exp_dn, hostname);
367         else
368                 fprintf(ctxt->fp, "%s\t%s\n", ipbuf, hostname);
369 }
370 
371 static bool statefiles_write_host6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease,
372                                    struct in6_addr *addr)
373 {
374         char ipbuf[INET6_ADDRSTRLEN];
375 
376         if (!lease->hostname || !lease->hostname_valid || !(lease->flags & OAF_DHCPV6_NA))
377                 return false;
378 
379         if (ctxt->fp) {
380                 inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
381                 statefiles_write_host(ipbuf, lease->hostname, ctxt);
382         }
383 
384         return true;
385 }
386 
387 static void statefiles_write_host6_cb(struct dhcpv6_lease *lease, struct in6_addr *addr, _o_unused uint8_t prefix_len,
388                                       _o_unused uint32_t pref_lt, _o_unused uint32_t valid_lt, void *arg)
389 {
390         struct write_ctxt *ctxt = (struct write_ctxt *)arg;
391 
392         statefiles_write_host6(ctxt, lease, addr);
393 }
394 
395 static bool statefiles_write_host4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease)
396 {
397         char ipbuf[INET_ADDRSTRLEN];
398 
399         if (!lease->hostname || !lease->hostname_valid)
400                 return false;
401 
402         if (ctxt->fp) {
403                 inet_ntop(AF_INET, &lease->ipv4, ipbuf, sizeof(ipbuf));
404                 statefiles_write_host(ipbuf, lease->hostname, ctxt);
405         }
406 
407         return true;
408 }
409 
410 static void statefiles_write_hosts(time_t now)
411 {
412         struct write_ctxt ctxt;
413 
414         if (config.dhcp_hostsdir_fd < 0)
415                 return;
416 
417         avl_for_each_element(&interfaces, ctxt.iface, avl) {
418                 if (ctxt.iface->ignore)
419                         continue;
420 
421                 if (ctxt.iface->dhcpv4 != MODE_SERVER && ctxt.iface->dhcpv6 != MODE_SERVER)
422                         continue;
423 
424                 ctxt.fp = statefiles_open_tmp_file(config.dhcp_hostsdir_fd);
425                 if (!ctxt.fp)
426                         continue;
427 
428                 if (ctxt.iface->dhcpv6 == MODE_SERVER) {
429                         struct dhcpv6_lease *lease;
430 
431                         list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) {
432                                 if (!lease->bound)
433                                         continue;
434 
435                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
436                                         continue;
437 
438                                 odhcpd_enum_addr6(ctxt.iface, lease, now,
439                                                   statefiles_write_host6_cb, &ctxt);
440                         }
441                 }
442 
443                 if (ctxt.iface->dhcpv4 == MODE_SERVER) {
444                         struct dhcpv4_lease *lease;
445 
446                         avl_for_each_element(&ctxt.iface->dhcpv4_leases, lease, iface_avl) {
447                                 if (!lease->bound)
448                                         continue;
449 
450                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
451                                         continue;
452 
453                                 statefiles_write_host4(&ctxt, lease);
454                         }
455                 }
456 
457                 statefiles_finish_tmp_file(config.dhcp_hostsdir_fd, &ctxt.fp,
458                                            ODHCPD_HOSTS_FILE_PREFIX, ctxt.iface->name);
459         }
460 }
461 
462 static void statefiles_write_state6_addr(struct dhcpv6_lease *lease, struct in6_addr *addr, uint8_t prefix_len,
463                                          _o_unused uint32_t pref_lt, _o_unused uint32_t valid_lt, void *arg)
464 {
465         struct write_ctxt *ctxt = (struct write_ctxt *)arg;
466         char ipbuf[INET6_ADDRSTRLEN];
467 
468         if (lease->hostname && lease->hostname_valid && lease->flags & OAF_DHCPV6_NA) {
469                 md5_hash(addr, sizeof(*addr), &ctxt->md5);
470                 md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5);
471         }
472 
473         if (!ctxt->fp)
474                 return;
475 
476         inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
477         fprintf(ctxt->fp, " %s/%" PRIu8, ipbuf, prefix_len);
478 }
479 
480 static void statefiles_write_state6(struct write_ctxt *ctxt, struct dhcpv6_lease *lease)
481 {
482         char duidbuf[DUID_HEXSTRLEN];
483 
484         if (ctxt->fp) {
485                 odhcpd_hexlify(duidbuf, lease->duid, lease->duid_len);
486 
487                 /* # <iface> <hexduid> <hexiaid> <hostname> <valid_until> <assigned_[host|subnet]_id> <pfx_length> [<addrs> ...] */
488                 fprintf(ctxt->fp,
489                         "# %s %s %x %s%s %" PRId64 " %" PRIx64 " %" PRIu8,
490                         ctxt->iface->ifname, duidbuf, ntohl(lease->iaid),
491                         lease->hostname && !lease->hostname_valid ? "broken\\x20": "",
492                         lease->hostname ? lease->hostname : "-",
493                         (lease->valid_until > ctxt->now ?
494                          (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) :
495                          (INFINITE_VALID(lease->valid_until) ? -1 : 0)),
496                         (lease->flags & OAF_DHCPV6_NA ?
497                          lease->assigned_host_id :
498                          (uint64_t)lease->assigned_subnet_id),
499                         lease->length);
500         }
501 
502         odhcpd_enum_addr6(ctxt->iface, lease, ctxt->now, statefiles_write_state6_addr, ctxt);
503 
504         if (ctxt->fp)
505                 putc('\n', ctxt->fp);
506 }
507 
508 static void statefiles_write_state4(struct write_ctxt *ctxt, struct dhcpv4_lease *lease)
509 {
510         char ipbuf[INET6_ADDRSTRLEN];
511 
512         if (lease->hostname && lease->hostname_valid) {
513                 md5_hash(&lease->ipv4, sizeof(lease->ipv4), &ctxt->md5);
514                 md5_hash(lease->hostname, strlen(lease->hostname), &ctxt->md5);
515         }
516 
517         if (!ctxt->fp)
518                 return;
519 
520         inet_ntop(AF_INET, &lease->ipv4, ipbuf, sizeof(ipbuf));
521 
522         /* # <iface> <hexhwaddr> "ipv4" <hostname> <valid_until> <hexaddr> "32" <addrstr>"/32" */
523         fprintf(ctxt->fp,
524                 "# %s %s ipv4 %s%s %" PRId64 " %x 32 %s/32\n",
525                 ctxt->iface->ifname,
526                 ether_ntoa((struct ether_addr *)lease->hwaddr),
527                 lease->hostname && !lease->hostname_valid ? "broken\\x20" : "",
528                 lease->hostname ? lease->hostname : "-",
529                 (lease->valid_until > ctxt->now ?
530                  (int64_t)(lease->valid_until - ctxt->now + ctxt->wall_time) :
531                  (INFINITE_VALID(lease->valid_until) ? -1 : 0)),
532                 ntohl(lease->ipv4.s_addr), ipbuf);
533 }
534 
535 /* Returns true if there are changes to be written to the hosts file(s) */
536 static bool statefiles_write_state(time_t now)
537 {
538         struct write_ctxt ctxt = {
539                 .fp = NULL,
540                 .now = now,
541                 .wall_time = time(NULL),
542         };
543         uint8_t newmd5[16];
544 
545         /* Return value unchecked, continue in order to get the md5 */
546         ctxt.fp = statefiles_open_tmp_file(config.dhcp_statedir_fd);
547 
548         md5_begin(&ctxt.md5);
549 
550         avl_for_each_element(&interfaces, ctxt.iface, avl) {
551                 if (ctxt.iface->dhcpv6 == MODE_SERVER) {
552                         struct dhcpv6_lease *lease;
553 
554                         list_for_each_entry(lease, &ctxt.iface->ia_assignments, head) {
555                                 if (!lease->bound)
556                                         continue;
557 
558                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
559                                         continue;
560 
561                                 statefiles_write_state6(&ctxt, lease);
562                         }
563                 }
564 
565                 if (ctxt.iface->dhcpv4 == MODE_SERVER) {
566                         struct dhcpv4_lease *lease;
567 
568                         avl_for_each_element(&ctxt.iface->dhcpv4_leases, lease, iface_avl) {
569                                 if (!lease->bound)
570                                         continue;
571 
572                                 if (!INFINITE_VALID(lease->valid_until) && lease->valid_until <= now)
573                                         continue;
574 
575                                 statefiles_write_state4(&ctxt, lease);
576                         }
577                 }
578         }
579 
580         statefiles_finish_tmp_file(config.dhcp_statedir_fd, &ctxt.fp, config.dhcp_statefile, NULL);
581 
582         md5_end(newmd5, &ctxt.md5);
583         if (!memcmp(newmd5, statemd5, sizeof(newmd5)))
584                 return false;
585 
586         memcpy(statemd5, newmd5, sizeof(statemd5));
587         return true;
588 }
589 
590 bool statefiles_write()
591 {
592         time_t now = odhcpd_time();
593 
594         if (!statefiles_write_state(now))
595                 return false;
596 
597         statefiles_write_hosts(now);
598 
599         if (config.dhcp_cb) {
600                 char *argv[2] = { config.dhcp_cb, NULL };
601                 pid_t pid;
602 
603                 posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
604         }
605 
606         return true;
607 }
608 
609 void statefiles_setup_dirfd(const char *path, int *dirfd)
610 {
611         if (!dirfd)
612                 return;
613 
614         if (*dirfd >= 0) {
615                 close(*dirfd);
616                 *dirfd = -1;
617         }
618 
619         if (!path)
620                 return;
621 
622         mkdir_p(strdupa(path), 0755);
623 
624         *dirfd = open(path, O_PATH | O_DIRECTORY | O_CLOEXEC);
625         if (*dirfd < 0)
626                 error("Unable to open directory '%s': %m", path);
627 }
628 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt