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