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 file contains public API functions for managing the legacy dhcptab
31 * container format. For the semantics of these functions, please see the
32 * Enterprise DHCP Architecture Document.
33 */
34
35 #include <alloca.h>
36 #include <dhcp_svc_public.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <netinet/in.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <sys/socket.h>
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <unistd.h>
47
48 #include "dhcptab.h"
49 #include "util.h"
50
51 static void dt2path(char *, size_t, const char *, const char *);
52 static int write_rec(int, dt_rec_t *, off_t);
53
54 int
55 open_dt(void **handlep, const char *location, uint_t flags)
56 {
57 dt_handle_t *dhp;
58 int retval;
59 int fd;
60 char dtpath[MAXPATHLEN];
61
62 dhp = malloc(sizeof (dt_handle_t));
63 if (dhp == NULL)
64 return (DSVC_NO_MEMORY);
65
66 dhp->dh_oflags = flags;
67 (void) strlcpy(dhp->dh_location, location, MAXPATHLEN);
68
69 /*
70 * This is a legacy format which has no header, so we neither write
71 * nor verify a header (we just create the file or make sure it
72 * exists, depending on the value of `flags').
73 */
74 dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
75 retval = open_file(dtpath, flags, &fd);
76 if (retval != DSVC_SUCCESS) {
77 free(dhp);
78 return (retval);
79 }
80 (void) close(fd);
81
82 *handlep = dhp;
83 return (DSVC_SUCCESS);
84 }
85
86 int
87 close_dt(void **handlep)
88 {
89 free(*handlep);
90 return (DSVC_SUCCESS);
91 }
92
93 int
94 remove_dt(const char *location)
95 {
96 char dtpath[MAXPATHLEN];
97
98 dt2path(dtpath, MAXPATHLEN, location, "");
99 if (unlink(dtpath) == -1)
100 return (syserr_to_dsvcerr(errno));
101
102 return (DSVC_SUCCESS);
103 }
104
105 /*
106 * Internal version of lookup_dt() used by both lookup_dt() and
107 * update_dt(); same semantics as lookup_dt() except that the `partial'
108 * argument has been generalized into a `flags' field and the handle has
109 * been turned into a FILE pointer.
110 */
111 static int
112 find_dt(FILE *fp, uint_t flags, uint_t query, int count,
113 const dt_rec_t *targetp, dt_rec_list_t **recordsp, uint_t *nrecordsp)
114 {
115 int retval = DSVC_SUCCESS;
116 char *buf = NULL, *fields[DTF_MAX_FIELDS];
117 uint_t nrecords;
118 dt_rec_t *recordp;
119 dt_rec_list_t *records, *new_records;
120 unsigned int nfields;
121 off_t recoff;
122
123 if (fseek(fp, 0, SEEK_SET) == -1)
124 return (DSVC_INTERNAL);
125
126 records = NULL;
127 for (nrecords = 0; count < 0 || nrecords < count; ) {
128 free(buf);
129
130 if (flags & FIND_POSITION)
131 recoff = ftello(fp);
132
133 buf = read_entry(fp);
134 if (buf == NULL) {
135 if (!feof(fp))
136 retval = DSVC_NO_MEMORY;
137 break;
138 }
139
140 /*
141 * Skip pure comment lines; for now this just skips the
142 * header information at the top of the container.
143 */
144 if (buf[0] == DTF_COMMENT_CHAR)
145 continue;
146
147 /*
148 * Parse out the entry into the dt_rec_t
149 */
150 nfields = field_split(buf, DTF_MAX_FIELDS, fields, " \t");
151 if (nfields < DTF_MAX_FIELDS)
152 continue;
153
154 /*
155 * See if we've got a match. If so, allocate the new
156 * record, fill it in, and continue.
157 */
158 if (DSVC_QISEQ(query, DT_QTYPE) &&
159 targetp->dt_type != fields[DTF_TYPE][0])
160 continue;
161 else if (DSVC_QISNEQ(query, DT_QTYPE) &&
162 targetp->dt_type == fields[DTF_TYPE][0])
163 continue;
164
165 if (DSVC_QISEQ(query, DT_QKEY) &&
166 strcmp(targetp->dt_key, fields[DTF_KEY]) != 0)
167 continue;
168 else if (DSVC_QISNEQ(query, DT_QKEY) &&
169 strcmp(targetp->dt_key, fields[DTF_KEY]) == 0)
170 continue;
171
172 /*
173 * Caller just wants a count of the number of matching
174 * records, not the records themselves; continue.
175 */
176 if (recordsp == NULL) {
177 nrecords++;
178 continue;
179 }
180
181 /*
182 * Allocate record; if FIND_POSITION flag is set, then we
183 * need to allocate an extended (dt_recpos_t) record.
184 */
185 if (flags & FIND_POSITION)
186 recordp = malloc(sizeof (dt_recpos_t));
187 else
188 recordp = malloc(sizeof (dt_rec_t));
189
190 if (recordp == NULL) {
191 if ((flags & FIND_PARTIAL) == 0)
192 retval = DSVC_NO_MEMORY;
193 break;
194 }
195
196 /*
197 * Fill in record; if FIND_POSITION flag is set, then pass
198 * back additional location information.
199 */
200 (void) strlcpy(recordp->dt_key, fields[DTF_KEY],
201 sizeof (recordp->dt_key));
202 recordp->dt_sig = 1;
203 recordp->dt_type = fields[DTF_TYPE][0];
204 recordp->dt_value = strdup(fields[DTF_VALUE]);
205 if (recordp->dt_value == NULL) {
206 free(recordp);
207 if ((flags & FIND_PARTIAL) == 0)
208 retval = DSVC_NO_MEMORY;
209 break;
210 }
211
212 if (flags & FIND_POSITION) {
213 ((dt_recpos_t *)recordp)->dtp_off = recoff;
214 ((dt_recpos_t *)recordp)->dtp_size = ftello(fp) -
215 recoff;
216 }
217
218 /*
219 * Chuck the record on the list; up the counter.
220 */
221 new_records = add_dtrec_to_list(recordp, records);
222 if (new_records == NULL) {
223 free_dtrec(recordp);
224 if ((flags & FIND_PARTIAL) == 0)
225 retval = DSVC_NO_MEMORY;
226 break;
227 }
228 records = new_records;
229 nrecords++;
230 }
231
232 free(buf);
233
234 if (retval == DSVC_SUCCESS) {
235 *nrecordsp = nrecords;
236 if (recordsp != NULL)
237 *recordsp = records;
238 return (DSVC_SUCCESS);
239 }
240
241 if (records != NULL)
242 free_dtrec_list(records);
243
244 return (retval);
245 }
246
247 int
248 lookup_dt(void *handle, boolean_t partial, uint_t query, int count,
249 const dt_rec_t *targetp, dt_rec_list_t **recordsp, uint_t *nrecordsp)
250 {
251 int retval;
252 char dtpath[MAXPATHLEN];
253 FILE *fp;
254 dt_handle_t *dhp = (dt_handle_t *)handle;
255
256 if ((dhp->dh_oflags & DSVC_READ) == 0)
257 return (DSVC_ACCESS);
258
259 dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
260 fp = fopen(dtpath, "r");
261 if (fp == NULL)
262 return (syserr_to_dsvcerr(errno));
263
264 retval = find_dt(fp, partial ? FIND_PARTIAL : 0, query, count, targetp,
265 recordsp, nrecordsp);
266
267 (void) fclose(fp);
268 return (retval);
269 }
270
271 /*
272 * Internal dhcptab record update routine, used to factor out the
273 * common code between add_dt(), delete_dt(), and modify_dt(). If
274 * `origp' is NULL, then act like add_dt(); if `newp' is NULL, then
275 * act like delete_dt(); otherwise act like modify_dt().
276 */
277 static int
278 update_dt(const dt_handle_t *dhp, const dt_rec_t *origp, dt_rec_t *newp)
279 {
280 char dtpath[MAXPATHLEN], newpath[MAXPATHLEN];
281 int retval = DSVC_SUCCESS;
282 off_t recoff, recnext;
283 dt_rec_list_t *reclist;
284 FILE *fp;
285 int newfd;
286 uint_t found;
287 int query;
288 struct stat st;
289
290 if ((dhp->dh_oflags & DSVC_WRITE) == 0)
291 return (DSVC_ACCESS);
292
293 /*
294 * Open the container to update and a new container file which we
295 * will store the updated version of the container in. When the
296 * update is done, rename the new file to be the real container.
297 */
298 dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
299 fp = fopen(dtpath, "r");
300 if (fp == NULL)
301 return (syserr_to_dsvcerr(errno));
302
303 dt2path(newpath, MAXPATHLEN, dhp->dh_location, ".new");
304 (void) unlink(newpath);
305 newfd = open(newpath, O_CREAT|O_EXCL|O_WRONLY, 0644);
306 if (newfd == -1) {
307 (void) fclose(fp);
308 return (syserr_to_dsvcerr(errno));
309 }
310
311 DSVC_QINIT(query);
312 DSVC_QEQ(query, DT_QKEY|DT_QTYPE);
313
314 /*
315 * If we're adding a new record or changing a key for an existing
316 * record, bail if the record we want to add already exists.
317 */
318 if (newp != NULL) {
319 if (origp == NULL || origp->dt_type != newp->dt_type ||
320 strcmp(origp->dt_key, newp->dt_key) != 0) {
321 retval = find_dt(fp, 0, query, 1, newp, NULL, &found);
322 if (retval != DSVC_SUCCESS)
323 goto out;
324 if (found != 0) {
325 retval = DSVC_EXISTS;
326 goto out;
327 }
328 }
329 }
330
331 /*
332 * If we're deleting or modifying record, make sure the record
333 * still exists. Note that we don't check signatures because this
334 * is a legacy format that has no signatures.
335 */
336 if (origp != NULL) {
337 retval = find_dt(fp, FIND_POSITION, query, 1, origp, &reclist,
338 &found);
339 if (retval != DSVC_SUCCESS)
340 goto out;
341 if (found == 0) {
342 retval = DSVC_NOENT;
343 goto out;
344 }
345
346 /*
347 * Note the offset of the record we're modifying or deleting
348 * for use down below.
349 */
350 recoff = ((dt_recpos_t *)reclist->dtl_rec)->dtp_off;
351 recnext = recoff + ((dt_recpos_t *)reclist->dtl_rec)->dtp_size;
352
353 free_dtrec_list(reclist);
354 } else {
355 /*
356 * No record to modify or delete, so set `recoff' and
357 * `recnext' appropriately.
358 */
359 recoff = 0;
360 recnext = 0;
361 }
362
363 /*
364 * Make a new copy of the container. If we're deleting or
365 * modifying a record, don't copy that record to the new container.
366 */
367 if (fstat(fileno(fp), &st) == -1) {
368 retval = DSVC_INTERNAL;
369 goto out;
370 }
371
372 retval = copy_range(fileno(fp), 0, newfd, 0, recoff);
373 if (retval != DSVC_SUCCESS)
374 goto out;
375
376 retval = copy_range(fileno(fp), recnext, newfd, recoff,
377 st.st_size - recnext);
378 if (retval != DSVC_SUCCESS)
379 goto out;
380
381 /*
382 * If there's a new record, append it to the new container.
383 */
384 if (newp != NULL) {
385 retval = write_rec(newfd, newp, recoff + st.st_size - recnext);
386 if (retval != DSVC_SUCCESS)
387 goto out;
388 }
389
390 /*
391 * Note: we close these descriptors before the rename(2) (rather
392 * than just having the `out:' label clean them up) to save NFS
393 * some work (otherwise, NFS has to save `dtpath' to an alternate
394 * name since its vnode would still be active).
395 */
396 (void) fclose(fp);
397 (void) close(newfd);
398
399 if (rename(newpath, dtpath) == -1)
400 retval = syserr_to_dsvcerr(errno);
401
402 return (retval);
403 out:
404 (void) fclose(fp);
405 (void) close(newfd);
406 (void) unlink(newpath);
407 return (retval);
408 }
409
410 int
411 delete_dt(void *handle, const dt_rec_t *delp)
412 {
413 return (update_dt((dt_handle_t *)handle, delp, NULL));
414 }
415
416 int
417 add_dt(void *handle, dt_rec_t *addp)
418 {
419 return (update_dt((dt_handle_t *)handle, NULL, addp));
420 }
421
422 int
423 modify_dt(void *handle, const dt_rec_t *origp, dt_rec_t *newp)
424 {
425 return (update_dt((dt_handle_t *)handle, origp, newp));
426 }
427
428 int
429 list_dt(const char *location, char ***listppp, uint_t *countp)
430 {
431 char dtpath[MAXPATHLEN];
432 char **listpp;
433
434 if (access(location, F_OK|R_OK) == -1) {
435 switch (errno) {
436 case EACCES:
437 case EPERM:
438 return (DSVC_ACCESS);
439 case ENOENT:
440 return (DSVC_NO_LOCATION);
441 default:
442 break;
443 }
444 return (DSVC_INTERNAL);
445 }
446
447 dt2path(dtpath, MAXPATHLEN, location, "");
448 if (access(dtpath, F_OK|R_OK) == -1) {
449 *countp = 0;
450 *listppp = NULL;
451 return (DSVC_SUCCESS);
452 }
453
454 listpp = malloc(sizeof (char **));
455 if (listpp == NULL)
456 return (DSVC_NO_MEMORY);
457 listpp[0] = strdup(DT_DHCPTAB);
458 if (listpp[0] == NULL) {
459 free(listpp);
460 return (DSVC_NO_MEMORY);
461 }
462
463 *listppp = listpp;
464 *countp = 1;
465 return (DSVC_SUCCESS);
466 }
467
468 /*
469 * Given a buffer `path' of `pathlen' bytes, fill it in with a path to
470 * the dhcptab in directory `dir' with a suffix of `suffix'.
471 */
472 static void
473 dt2path(char *path, size_t pathlen, const char *dir, const char *suffix)
474 {
475 (void) snprintf(path, pathlen, "%s/%s%s", dir, DT_DHCPTAB, suffix);
476 }
477
478 /*
479 * Write the dt_rec_t pointed to by `recp' into the open container `fd' at
480 * offset `recoff'. Returns DSVC_* error code.
481 */
482 static int
483 write_rec(int fd, dt_rec_t *recp, off_t recoff)
484 {
485 char entbuf[1024], *ent = entbuf;
486 size_t entsize = sizeof (entbuf);
487 int entlen;
488
489 again:
490 entlen = snprintf(ent, entsize, "%s\t%c\t%s\n", recp->dt_key,
491 recp->dt_type, recp->dt_value);
492 if (entlen == -1)
493 return (syserr_to_dsvcerr(errno));
494
495 if (entlen > entsize) {
496 entsize = entlen;
497 ent = alloca(entlen);
498 goto again;
499 }
500
501 if (pnwrite(fd, ent, entlen, recoff) == -1)
502 return (syserr_to_dsvcerr(errno));
503
504 return (DSVC_SUCCESS);
505 }