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  */
  52 static dhcp_confopt_t *
  53 find_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  */
  75 int
  76 add_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  */
 120 int
 121 read_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 
 205 nomem:
 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  */
 218 int
 219 replace_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  */
 255 int
 256 write_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  */
 324 void
 325 free_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  */
 345 int
 346 delete_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  */
 360 int
 361 query_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  */
 387 int
 388 confopt_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 }