1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <net/if.h>
34 #include <netinet/dhcp.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <time.h>
38 #include <string.h>			/* memcpy */
39 #include <fcntl.h>
40 #include <limits.h>
41 
42 #include "dhcp_hostconf.h"
43 
44 static void		relativize_time(DHCP_OPT *, time_t, time_t);
45 static void		relativize_v6(uint32_t *, time_t, time_t);
46 
47 /*
48  * ifname_to_hostconf(): converts an interface name into a hostconf file for
49  *			 that interface
50  *
51  *   input: const char *: the interface name
52  *	    boolean_t: B_TRUE if using DHCPv6
53  *  output: char *: the hostconf filename
54  *    note: uses an internal static buffer (not threadsafe)
55  */
56 
57 char *
ifname_to_hostconf(const char * ifname,boolean_t isv6)58 ifname_to_hostconf(const char *ifname, boolean_t isv6)
59 {
60 	static char filename[sizeof (DHCP_HOSTCONF_TMPL6) + LIFNAMSIZ];
61 
62 	(void) snprintf(filename, sizeof (filename), "%s%s%s",
63 	    DHCP_HOSTCONF_PREFIX, ifname,
64 	    isv6 ? DHCP_HOSTCONF_SUFFIX6 : DHCP_HOSTCONF_SUFFIX);
65 
66 	return (filename);
67 }
68 
69 /*
70  * remove_hostconf(): removes an interface.dhc file
71  *
72  *   input: const char *: the interface name
73  *	    boolean_t: B_TRUE if using DHCPv6
74  *  output: int: 0 if the file is removed, -1 if it can't be removed
75  *          (errno is set)
76  */
77 
78 int
remove_hostconf(const char * ifname,boolean_t isv6)79 remove_hostconf(const char *ifname, boolean_t isv6)
80 {
81 	return (unlink(ifname_to_hostconf(ifname, isv6)));
82 }
83 
84 /*
85  * read_hostconf(): reads the contents of an <if>.dhc file into a PKT_LIST
86  *
87  *   input: const char *: the interface name
88  *	    PKT_LIST **: a pointer to a PKT_LIST * to store the info in
89  *	    uint_t: the length of the list of PKT_LISTs
90  *	    boolean_t: B_TRUE if using DHCPv6
91  *  output: int: >0 if the file is read and loaded into the PKT_LIST *
92  *	    successfully, -1 otherwise (errno is set)
93  *    note: the PKT and PKT_LISTs are dynamically allocated here
94  */
95 
96 int
read_hostconf(const char * ifname,PKT_LIST ** plpp,uint_t plplen,boolean_t isv6)97 read_hostconf(const char *ifname, PKT_LIST **plpp, uint_t plplen,
98     boolean_t isv6)
99 {
100 	PKT_LIST	*plp = NULL;
101 	PKT		*pkt = NULL;
102 	int		fd;
103 	time_t		orig_time, current_time = time(NULL);
104 	uint32_t	lease;
105 	uint32_t	magic;
106 	int		pcnt = 0;
107 	int		retval;
108 
109 	fd = open(ifname_to_hostconf(ifname, isv6), O_RDONLY);
110 	if (fd == -1)
111 		return (-1);
112 
113 	if (read(fd, &magic, sizeof (magic)) != sizeof (magic))
114 		goto failure;
115 
116 	if (magic != (isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC))
117 		goto failure;
118 
119 	if (read(fd, &orig_time, sizeof (orig_time)) != sizeof (orig_time))
120 		goto failure;
121 
122 	/*
123 	 * read the packet back in from disk, and for v4, run it through
124 	 * dhcp_options_scan(). note that we use calloc() because
125 	 * dhcp_options_scan() relies on the structure being zeroed.
126 	 */
127 
128 	for (pcnt = 0; pcnt < plplen; pcnt++) {
129 
130 		plp = NULL;
131 		pkt = NULL;
132 
133 		if ((plp = calloc(1, sizeof (PKT_LIST))) == NULL)
134 			goto failure;
135 
136 		retval = read(fd, &plp->len, sizeof (plp->len));
137 		if (retval == 0 && pcnt != 0) {
138 			/*
139 			 * Reached end of file on a boundary, but after
140 			 * we've read at least one packet, so we consider
141 			 * this successful, allowing us to use files from
142 			 * older versions of the agent happily.
143 			 */
144 			free(plp);
145 			break;
146 		} else if (retval != sizeof (plp->len))
147 			goto failure;
148 
149 		if ((pkt = malloc(plp->len)) == NULL)
150 			goto failure;
151 
152 		if (read(fd, pkt, plp->len) != plp->len)
153 			goto failure;
154 
155 		plp->pkt = pkt;
156 
157 		plpp[pcnt] = plp;
158 
159 		if (!isv6 && dhcp_options_scan(plp, B_TRUE) != 0)
160 			goto failure;
161 
162 		/*
163 		 * First packet used to validate that we're interested,
164 		 * the rest are presumed to be historical reference and
165 		 * are not relativized
166 		 */
167 		if (pcnt == 0)
168 			continue;
169 
170 		if (isv6) {
171 			dhcpv6_option_t	d6o;
172 			dhcpv6_ia_na_t	d6in;
173 			dhcpv6_iaaddr_t	d6ia;
174 			uchar_t		*opts, *optmax, *subomax;
175 
176 			/*
177 			 * Loop over contents of the packet to find the address
178 			 * options.
179 			 */
180 			opts = (uchar_t *)pkt + sizeof (dhcpv6_message_t);
181 			optmax = (uchar_t *)pkt + plp->len;
182 			while (opts + sizeof (d6o) <= optmax) {
183 
184 				/*
185 				 * Extract option header and make sure option
186 				 * is intact.
187 				 */
188 				(void) memcpy(&d6o, opts, sizeof (d6o));
189 				d6o.d6o_code = ntohs(d6o.d6o_code);
190 				d6o.d6o_len = ntohs(d6o.d6o_len);
191 				subomax = opts + sizeof (d6o) + d6o.d6o_len;
192 				if (subomax > optmax)
193 					break;
194 
195 				/*
196 				 * If this isn't an option that contains
197 				 * address or prefix leases, then skip over it.
198 				 */
199 				if (d6o.d6o_code != DHCPV6_OPT_IA_NA &&
200 				    d6o.d6o_code != DHCPV6_OPT_IA_TA &&
201 				    d6o.d6o_code != DHCPV6_OPT_IA_PD) {
202 					opts = subomax;
203 					continue;
204 				}
205 
206 				/*
207 				 * Handle the option first.
208 				 */
209 				if (d6o.d6o_code == DHCPV6_OPT_IA_TA) {
210 					/* no timers in this structure */
211 					opts += sizeof (dhcpv6_ia_ta_t);
212 				} else {
213 					/* both na and pd */
214 					if (opts + sizeof (d6in) > subomax) {
215 						opts = subomax;
216 						continue;
217 					}
218 					(void) memcpy(&d6in, opts,
219 					    sizeof (d6in));
220 					relativize_v6(&d6in.d6in_t1, orig_time,
221 					    current_time);
222 					relativize_v6(&d6in.d6in_t2, orig_time,
223 					    current_time);
224 					(void) memcpy(opts, &d6in,
225 					    sizeof (d6in));
226 					opts += sizeof (d6in);
227 				}
228 
229 				/*
230 				 * Now handle each suboption (address) inside.
231 				 */
232 				while (opts + sizeof (d6o) <= subomax) {
233 					/*
234 					 * Verify the suboption header first.
235 					 */
236 					(void) memcpy(&d6o, opts,
237 					    sizeof (d6o));
238 					d6o.d6o_code = ntohs(d6o.d6o_code);
239 					d6o.d6o_len = ntohs(d6o.d6o_len);
240 					if (opts + sizeof (d6o) + d6o.d6o_len >
241 					    subomax)
242 						break;
243 					if (d6o.d6o_code != DHCPV6_OPT_IAADDR) {
244 						opts += sizeof (d6o) +
245 						    d6o.d6o_len;
246 						continue;
247 					}
248 
249 					/*
250 					 * Now process the contents.
251 					 */
252 					if (opts + sizeof (d6ia) > subomax)
253 						break;
254 					(void) memcpy(&d6ia, opts,
255 					    sizeof (d6ia));
256 					relativize_v6(&d6ia.d6ia_preflife,
257 					    orig_time, current_time);
258 					relativize_v6(&d6ia.d6ia_vallife,
259 					    orig_time, current_time);
260 					(void) memcpy(opts, &d6ia,
261 					    sizeof (d6ia));
262 					opts += sizeof (d6o) + d6o.d6o_len;
263 				}
264 				opts = subomax;
265 			}
266 		} else {
267 
268 			/*
269 			 * make sure the IPv4 DHCP lease is still valid.
270 			 */
271 
272 			if (plp->opts[CD_LEASE_TIME] != NULL &&
273 			    plp->opts[CD_LEASE_TIME]->len ==
274 			    sizeof (lease_t)) {
275 
276 				(void) memcpy(&lease,
277 				    plp->opts[CD_LEASE_TIME]->value,
278 				    sizeof (lease_t));
279 
280 				lease = ntohl(lease);
281 				if ((lease != DHCP_PERM) &&
282 				    (orig_time + lease) <= current_time)
283 					goto failure;
284 			}
285 
286 			relativize_time(plp->opts[CD_T1_TIME], orig_time,
287 			    current_time);
288 			relativize_time(plp->opts[CD_T2_TIME], orig_time,
289 			    current_time);
290 			relativize_time(plp->opts[CD_LEASE_TIME], orig_time,
291 			    current_time);
292 		}
293 	}
294 
295 	(void) close(fd);
296 	return (pcnt);
297 
298 failure:
299 	free(pkt);
300 	free(plp);
301 	while (pcnt-- > 0) {
302 		free(plpp[pcnt]->pkt);
303 		free(plpp[pcnt]);
304 	}
305 	(void) close(fd);
306 	return (-1);
307 }
308 
309 /*
310  * write_hostconf(): writes the contents of a PKT_LIST into an <if>.dhc file
311  *
312  *   input: const char *: the interface name
313  *	    PKT_LIST **: a list of pointers to PKT_LIST to write
314  *	    uint_t: length of the list of PKT_LIST pointers
315  *	    time_t: a starting time to treat the relative lease times
316  *		    in the first packet as relative to
317  *	    boolean_t: B_TRUE if using DHCPv6
318  *  output: int: 0 if the file is written successfully, -1 otherwise
319  *	    (errno is set)
320  */
321 
322 int
write_hostconf(const char * ifname,PKT_LIST * pl[],uint_t pllen,time_t relative_to,boolean_t isv6)323 write_hostconf(
324     const char *ifname,
325     PKT_LIST *pl[],
326     uint_t pllen,
327     time_t relative_to,
328     boolean_t isv6)
329 {
330 	int		fd;
331 	struct iovec	iov[IOV_MAX];
332 	int		retval;
333 	uint32_t	magic;
334 	ssize_t		explen = 0; /* Expected length of write */
335 	int		i, iovlen = 0;
336 
337 	fd = open(ifname_to_hostconf(ifname, isv6), O_WRONLY|O_CREAT|O_TRUNC,
338 	    0600);
339 	if (fd == -1)
340 		return (-1);
341 
342 	/*
343 	 * first write our magic number, then the relative time of the
344 	 * leases, then for each packet we write the length of the packet
345 	 * followed by the packet.  we will then use the relative time in
346 	 * read_hostconf() to recalculate the lease times for the first packet.
347 	 */
348 
349 	magic = isv6 ? DHCP_HOSTCONF_MAGIC6 : DHCP_HOSTCONF_MAGIC;
350 	iov[iovlen].iov_base = (caddr_t)&magic;
351 	explen += iov[iovlen++].iov_len  = sizeof (magic);
352 	iov[iovlen].iov_base = (caddr_t)&relative_to;
353 	explen += iov[iovlen++].iov_len  = sizeof (relative_to);
354 	for (i = 0; i < pllen && iovlen < (IOV_MAX - 1); i++) {
355 		iov[iovlen].iov_base = (caddr_t)&pl[i]->len;
356 		explen += iov[iovlen++].iov_len  = sizeof (pl[i]->len);
357 		iov[iovlen].iov_base = (caddr_t)pl[i]->pkt;
358 		explen += iov[iovlen++].iov_len  = pl[i]->len;
359 	}
360 
361 	retval = writev(fd, iov, iovlen);
362 
363 	(void) close(fd);
364 
365 	if (retval != explen)
366 		return (-1);
367 
368 	return (0);
369 }
370 
371 /*
372  * relativize_time(): re-relativizes a time in a DHCP option
373  *
374  *   input: DHCP_OPT *: the DHCP option parameter to convert
375  *	    time_t: the time the leases in the packet are currently relative to
376  *	    time_t: the current time which leases will become relative to
377  *  output: void
378  */
379 
380 static void
relativize_time(DHCP_OPT * option,time_t orig_time,time_t current_time)381 relativize_time(DHCP_OPT *option, time_t orig_time, time_t current_time)
382 {
383 	uint32_t	pkt_time;
384 	time_t		time_diff = current_time - orig_time;
385 
386 	if (option == NULL || option->len != sizeof (lease_t))
387 		return;
388 
389 	(void) memcpy(&pkt_time, option->value, option->len);
390 	if (ntohl(pkt_time) != DHCP_PERM)
391 		pkt_time = htonl(ntohl(pkt_time) - time_diff);
392 
393 	(void) memcpy(option->value, &pkt_time, option->len);
394 }
395 
396 /*
397  * relativize_v6(): re-relativizes a time in a DHCPv6 option
398  *
399  *   input: uint32_t *: the time value to convert
400  *	    time_t: the time the leases in the packet are currently relative to
401  *	    time_t: the current time which leases will become relative to
402  *  output: void
403  */
404 
405 static void
relativize_v6(uint32_t * val,time_t orig_time,time_t current_time)406 relativize_v6(uint32_t *val, time_t orig_time, time_t current_time)
407 {
408 	uint32_t	hval;
409 	time_t		time_diff = current_time - orig_time;
410 
411 	hval = ntohl(*val);
412 	if (hval != DHCPV6_INFTIME) {
413 		if (hval < time_diff)
414 			*val = 0;
415 		else
416 			*val = htonl(hval - time_diff);
417 	}
418 }
419