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, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27#pragma ident	"%Z%%M%	%I%	%E% SMI"
28
29/*
30 * This module contains the functions implementing the interface to the
31 * /etc/inet/dhcpsvc.conf DHCP service configuration file.
32 */
33
34#include <thread.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <unistd.h>
38#include <sys/types.h>
39#include <alloca.h>
40#include <string.h>
41#include <sys/stat.h>
42#include <fcntl.h>
43#include <errno.h>
44#include <dhcp_svc_confkey.h>
45#include <dhcp_svc_confopt.h>
46#include <dhcp_svc_private.h>
47
48/*
49 * Finds the parameter called key, and returns a reference to it.  Returns
50 * NULL if not found or an error occurred.
51 */
52static dhcp_confopt_t *
53find_dhcp_confopt(dhcp_confopt_t *ddp, const char *key)
54{
55	unsigned int	i;
56
57	if (ddp == NULL || key == NULL)
58		return (NULL);
59
60	for (i = 0; ddp[i].co_type != DHCP_END; i++) {
61		if (ddp[i].co_type == DHCP_KEY &&
62		    strcasecmp(ddp[i].co_key, key) == 0)
63			return (&ddp[i]);
64	}
65	return (NULL);
66}
67
68/*
69 * Adds a dhcp_confopt_t to the ddpp table. If the table is NULL, one is
70 * created. The table is terminated by a NULL entry.  The key and value
71 * arguments are copied, not referenced directly.  No check is done to see
72 * if the parameter already exists.  Returns 0 for success, nonzero
73 * otherwise.
74 */
75int
76add_dsvc_conf(dhcp_confopt_t **ddpp, const char *key, const char *value)
77{
78	dhcp_confopt_t		*ndp, tdp;
79	unsigned int		i;
80
81	if (ddpp == NULL || key == NULL || value == NULL) {
82		errno = EINVAL;
83		return (-1);
84	}
85
86	tdp.co_key = strdup(key);
87	tdp.co_type = DHCP_KEY;
88	tdp.co_value = strdup(value);
89	if (tdp.co_key == NULL || tdp.co_value == NULL) {
90		free(tdp.co_key);
91		free(tdp.co_value);
92		errno = ENOMEM;
93		return (-1);
94	}
95
96	for (i = 0; *ddpp && (*ddpp)[i].co_key != NULL; i++)
97		;
98
99	ndp = realloc(*ddpp, (i + 2) * sizeof (dhcp_confopt_t));
100	if (ndp == NULL) {
101		free(tdp.co_key);
102		free(tdp.co_value);
103		errno = ENOMEM;
104		return (-1);
105	}
106
107	ndp[i] = tdp;
108	(void) memset(&ndp[i + 1], 0, sizeof (dhcp_confopt_t));
109	*ddpp = ndp;
110
111	return (0);
112}
113
114/*
115 * Reads the contents of the configuration file into a dynamically
116 * allocated array of dhcp_confopt_t records.  A zeroed element marks the
117 * end of the array.  Blank lines are ignored.  Caller is responsible for
118 * freeing ddp.
119 */
120int
121read_dsvc_conf(dhcp_confopt_t **ddpp)
122{
123	struct stat	sb;
124	int		dd;
125	int		error;
126	unsigned int	entry;
127	char		*cp, *dp, *eol, *value;
128	dhcp_confopt_t	confopt, *tdp, *ddp = NULL;
129	char		conf[MAXPATHLEN];
130
131	if (ddpp == NULL) {
132		errno = EINVAL;
133		return (-1);
134	}
135
136	(void) snprintf(conf, sizeof (conf), "%s" DHCP_CONFOPT_FILE,
137	    DHCP_CONFOPT_ROOT);
138
139	if ((dd = open(conf, O_RDONLY)) == -1)
140		return (-1);
141	if (fstat(dd, &sb) == -1) {
142		error = errno;
143		(void) close(dd);
144		errno = error;
145		return (-1);
146	}
147
148	dp = alloca(sb.st_size);
149	if (read(dd, dp, sb.st_size) != sb.st_size) {
150		error = errno;
151		(void) close(dd);
152		errno = error;
153		return (-1);
154	}
155	(void) close(dd);
156
157	for (entry = 0, cp = dp; cp < &dp[sb.st_size]; cp = eol + 1) {
158		eol = strchr(cp, '\n');
159		if (eol == NULL)		/* done parsing file */
160			break;
161		if (eol == cp) 			/* blank line -- skip */
162			continue;
163		*eol = '\0';
164
165		if (*cp == '#') {
166			confopt.co_type = DHCP_COMMENT;
167			confopt.co_comment = strdup(cp + 1);
168			if (confopt.co_comment == NULL)
169				goto nomem;
170		} else {
171			value = strchr(cp, '=');
172			if (value == NULL)
173				continue;
174			*value = '\0';
175
176			confopt.co_type = DHCP_KEY;
177			confopt.co_key = strdup(cp);
178			if (confopt.co_key == NULL)
179				goto nomem;
180
181			confopt.co_value = strdup(value + 1);
182			if (confopt.co_value == NULL) {
183				free(confopt.co_key);
184				goto nomem;
185			}
186		}
187
188		/* always allocate a spare slot for the zeroed entry */
189		tdp = realloc(ddp, (entry + 2) * sizeof (dhcp_confopt_t));
190		if (tdp == NULL)
191			goto nomem;
192
193		tdp[entry] = confopt;
194		(void) memset(&tdp[entry + 1], 0, sizeof (dhcp_confopt_t));
195		ddp = tdp;
196		entry++;
197	}
198
199	if (ddp == NULL)
200		return (-1);
201
202	*ddpp = ddp;
203	return (0);
204
205nomem:
206	if (ddp != NULL)
207		free_dsvc_conf(ddp);
208
209	errno = ENOMEM;
210	return (-1);
211}
212
213/*
214 * If the requested parameter exists, replace its value with the new
215 * value. If it doesn't exist, then add the parameter with the new value.
216 * Returns 0 for success, -1 otherwise (errno is set).
217 */
218int
219replace_dsvc_conf(dhcp_confopt_t **ddpp, const char *key, const char *value)
220{
221	dhcp_confopt_t	*tdp;
222	int		err;
223
224	if (ddpp == NULL || key == NULL || value == NULL) {
225		errno = EINVAL;
226		return (-1);
227	}
228	if ((tdp = find_dhcp_confopt(*ddpp, key)) != NULL) {
229		char	*valp;
230
231		if ((valp = strdup(value)) == NULL)
232			return (-1); /* NOMEM */
233
234		if (tdp->co_value != NULL)
235			free(tdp->co_value);
236
237		tdp->co_value = valp;
238
239		errno = 0;
240		err = 0;
241	} else
242		err = (add_dsvc_conf(ddpp, key, value) == 0) ? 0 : -1;
243
244	return (err);
245}
246
247/*
248 * Writes ddp array to the configuration file.  If the configuration file
249 * already exists, its contents are replaced with the contents of the ddp
250 * array.  If the configuration file does not exist, it is created using
251 * the identity of the caller (euid/egid) with the permission bits
252 * specified by the mode argument (and modified by the umask).  Caller is
253 * responsible for freeing the array.
254 */
255int
256write_dsvc_conf(dhcp_confopt_t *ddp, mode_t mode)
257{
258	int		tdd;
259	ssize_t		bytes;
260	size_t		i, size;
261	char		*tmpbuf;
262	char		tmpconf[MAXPATHLEN], conf[MAXPATHLEN];
263
264	if (ddp == NULL) {
265		errno = EINVAL;
266		return (-1);
267	}
268
269	/* guess at final file size */
270	for (i = 0, size = 0; ddp[i].co_type != DHCP_END; i++) {
271		if (ddp[i].co_type == DHCP_KEY) {
272			size += strlen(ddp[i].co_key) + 1; /* include = */
273			size += strlen(ddp[i].co_value) + 1; /* include \n */
274		} else
275			size += strlen(ddp[i].co_comment) + 2; /* inc # + \n */
276	}
277
278	if (size == 0) {
279		errno = EINVAL;
280		return (-1);
281	}
282
283	(void) snprintf(conf, sizeof (conf), "%s" DHCP_CONFOPT_FILE,
284	    DHCP_CONFOPT_ROOT);
285	(void) snprintf(tmpconf, sizeof (tmpconf),
286	    "%s" DHCP_CONFOPT_FILE ".%ld.%u", DHCP_CONFOPT_ROOT, getpid(),
287	    thr_self());
288
289	if ((tdd = open(tmpconf, O_CREAT | O_EXCL | O_WRONLY, mode)) < 0)
290		return (-1);
291
292	tmpbuf = alloca(size);
293	for (i = 0; ddp[i].co_type != DHCP_END; i++) {
294		if (ddp[i].co_type == DHCP_KEY)
295			(void) snprintf(tmpbuf, size, "%s=%s\n", ddp[i].co_key,
296			    ddp[i].co_value);
297		else
298			(void) snprintf(tmpbuf, size, "#%s\n",
299			    ddp[i].co_comment);
300
301		bytes = write(tdd, tmpbuf, strlen(tmpbuf));
302
303		/* Nuke the file if we can't successfully update it */
304		if (bytes != strlen(tmpbuf)) {
305			(void) close(tdd);
306			(void) unlink(tmpconf);
307			return (-1);
308		}
309	}
310	(void) close(tdd);
311
312	/* Move new file into place */
313	if (rename(tmpconf, conf) < 0) {
314		(void) unlink(tmpconf);
315		return (-1);
316	}
317
318	return (0);
319}
320
321/*
322 * Frees the memory associated with the ddp array.
323 */
324void
325free_dsvc_conf(dhcp_confopt_t *ddp)
326{
327	unsigned int	i;
328
329	if (ddp == NULL)
330		return;
331
332	for (i = 0; ddp[i].co_type != DHCP_END; i++) {
333		if (ddp[i].co_type == DHCP_KEY) {
334			free(ddp[i].co_key);
335			free(ddp[i].co_value);
336		} else
337			free(ddp[i].co_comment);
338	}
339	free(ddp);
340}
341
342/*
343 * Deletes the configuration file.
344 */
345int
346delete_dsvc_conf(void)
347{
348	char confpath[MAXPATHLEN];
349
350	(void) snprintf(confpath, sizeof (confpath), "%s" DHCP_CONFOPT_FILE,
351	    DHCP_CONFOPT_ROOT);
352	return (unlink(confpath));
353}
354
355/*
356 * Return a copy of the value portion of the named key.  Caller is
357 * responsible for freeing value when they're finished using it.  Returns 0
358 * for success, -1 otherwise (errno is set).
359 */
360int
361query_dsvc_conf(dhcp_confopt_t *ddp, const char *key, char **value)
362{
363	dhcp_confopt_t	*tdp;
364
365	if (key == NULL || value == NULL) {
366		errno = EINVAL;
367		return (-1);
368	}
369	if ((tdp = find_dhcp_confopt(ddp, key)) != NULL) {
370		*value = strdup(tdp->co_value);
371		if (*value == NULL) {
372			errno = ENOMEM;
373			return (-1);
374		}
375		errno = 0;
376		return (0);
377	}
378	errno = ENOENT;
379	*value = NULL;
380	return (-1);
381}
382
383/*
384 * Given a dhcp_confopt_t structure, fill in a dsvc_datastore_t.
385 * Data is copied from dhcp_confopt_t structure.
386 */
387int
388confopt_to_datastore(dhcp_confopt_t *ddp, dsvc_datastore_t *dsp)
389{
390	dhcp_confopt_t	*tdp;
391
392	if (ddp == NULL || dsp == NULL)
393		return (DSVC_INVAL);
394
395	tdp = find_dhcp_confopt(ddp, DSVC_CK_CONVER);
396	if (tdp == NULL || tdp->co_value == NULL)
397		return (DSVC_BAD_CONVER);
398	dsp->d_conver = atoi(tdp->co_value);
399
400	if (query_dsvc_conf(ddp, DSVC_CK_RESOURCE, &dsp->d_resource) == -1)
401		return (DSVC_BAD_RESOURCE);
402
403	if (query_dsvc_conf(ddp, DSVC_CK_PATH, &dsp->d_location) == -1) {
404		free(dsp->d_resource);
405		return (DSVC_BAD_PATH);
406	}
407
408	/*
409	 * RESOURCE_CONFIG is optional - underlying service will complain
410	 * if it isn't right.
411	 */
412	(void) query_dsvc_conf(ddp, DSVC_CK_RESOURCE_CONFIG, &dsp->d_config);
413
414	return (DSVC_SUCCESS);
415}
416