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