busybox/networking/udhcp/files.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * files.c -- DHCP server file manipulation *
   4 * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
   5 *
   6 * Licensed under GPLv2, see file LICENSE in this tarball for details.
   7 */
   8
   9#include <netinet/ether.h>
  10
  11#include "common.h"
  12#include "dhcpd.h"
  13#include "options.h"
  14
  15#if BB_LITTLE_ENDIAN
  16static inline uint64_t hton64(uint64_t v)
  17{
  18        return (((uint64_t)htonl(v)) << 32) | htonl(v >> 32);
  19}
  20#else
  21#define hton64(v) (v)
  22#endif
  23#define ntoh64(v) hton64(v)
  24
  25
  26/* on these functions, make sure your datatype matches */
  27static int read_ip(const char *line, void *arg)
  28{
  29        len_and_sockaddr *lsa;
  30
  31        lsa = host_and_af2sockaddr(line, 0, AF_INET);
  32        if (!lsa)
  33                return 0;
  34        *(uint32_t*)arg = lsa->u.sin.sin_addr.s_addr;
  35        free(lsa);
  36        return 1;
  37}
  38
  39
  40static int read_mac(const char *line, void *arg)
  41{
  42        return NULL == ether_aton_r(line, (struct ether_addr *)arg);
  43}
  44
  45
  46static int read_str(const char *line, void *arg)
  47{
  48        char **dest = arg;
  49
  50        free(*dest);
  51        *dest = xstrdup(line);
  52        return 1;
  53}
  54
  55
  56static int read_u32(const char *line, void *arg)
  57{
  58        *(uint32_t*)arg = bb_strtou32(line, NULL, 10);
  59        return errno == 0;
  60}
  61
  62
  63static int read_yn(const char *line, void *arg)
  64{
  65        char *dest = arg;
  66
  67        if (!strcasecmp("yes", line)) {
  68                *dest = 1;
  69                return 1;
  70        }
  71        if (!strcasecmp("no", line)) {
  72                *dest = 0;
  73                return 1;
  74        }
  75        return 0;
  76}
  77
  78
  79/* find option 'code' in opt_list */
  80struct option_set* FAST_FUNC find_option(struct option_set *opt_list, uint8_t code)
  81{
  82        while (opt_list && opt_list->data[OPT_CODE] < code)
  83                opt_list = opt_list->next;
  84
  85        if (opt_list && opt_list->data[OPT_CODE] == code)
  86                return opt_list;
  87        return NULL;
  88}
  89
  90
  91/* add an option to the opt_list */
  92static void attach_option(struct option_set **opt_list,
  93                const struct dhcp_option *option, char *buffer, int length)
  94{
  95        struct option_set *existing, *new, **curr;
  96
  97        existing = find_option(*opt_list, option->code);
  98        if (!existing) {
  99                DEBUG("Attaching option %02x to list", option->code);
 100
 101#if ENABLE_FEATURE_UDHCP_RFC3397
 102                if ((option->flags & TYPE_MASK) == OPTION_STR1035)
 103                        /* reuse buffer and length for RFC1035-formatted string */
 104                        buffer = (char *)dname_enc(NULL, 0, buffer, &length);
 105#endif
 106
 107                /* make a new option */
 108                new = xmalloc(sizeof(*new));
 109                new->data = xmalloc(length + 2);
 110                new->data[OPT_CODE] = option->code;
 111                new->data[OPT_LEN] = length;
 112                memcpy(new->data + 2, buffer, length);
 113
 114                curr = opt_list;
 115                while (*curr && (*curr)->data[OPT_CODE] < option->code)
 116                        curr = &(*curr)->next;
 117
 118                new->next = *curr;
 119                *curr = new;
 120#if ENABLE_FEATURE_UDHCP_RFC3397
 121                if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL)
 122                        free(buffer);
 123#endif
 124                return;
 125        }
 126
 127        /* add it to an existing option */
 128        DEBUG("Attaching option %02x to existing member of list", option->code);
 129        if (option->flags & OPTION_LIST) {
 130#if ENABLE_FEATURE_UDHCP_RFC3397
 131                if ((option->flags & TYPE_MASK) == OPTION_STR1035)
 132                        /* reuse buffer and length for RFC1035-formatted string */
 133                        buffer = (char *)dname_enc(existing->data + 2,
 134                                        existing->data[OPT_LEN], buffer, &length);
 135#endif
 136                if (existing->data[OPT_LEN] + length <= 255) {
 137                        existing->data = xrealloc(existing->data,
 138                                        existing->data[OPT_LEN] + length + 3);
 139                        if ((option->flags & TYPE_MASK) == OPTION_STRING) {
 140                                /* ' ' can bring us to 256 - bad */
 141                                if (existing->data[OPT_LEN] + length >= 255)
 142                                        return;
 143                                /* add space separator between STRING options in a list */
 144                                existing->data[existing->data[OPT_LEN] + 2] = ' ';
 145                                existing->data[OPT_LEN]++;
 146                        }
 147                        memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length);
 148                        existing->data[OPT_LEN] += length;
 149                } /* else, ignore the data, we could put this in a second option in the future */
 150#if ENABLE_FEATURE_UDHCP_RFC3397
 151                if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL)
 152                        free(buffer);
 153#endif
 154        } /* else, ignore the new data */
 155}
 156
 157
 158/* read a dhcp option and add it to opt_list */
 159static int read_opt(const char *const_line, void *arg)
 160{
 161        struct option_set **opt_list = arg;
 162        char *opt, *val, *endptr;
 163        char *line;
 164        const struct dhcp_option *option;
 165        int retval, length, idx;
 166        char buffer[8] ALIGNED(4);
 167        uint16_t *result_u16 = (uint16_t *) buffer;
 168        uint32_t *result_u32 = (uint32_t *) buffer;
 169
 170        /* Cheat, the only const line we'll actually get is "" */
 171        line = (char *) const_line;
 172        opt = strtok(line, " \t=");
 173        if (!opt)
 174                return 0;
 175
 176        idx = index_in_strings(dhcp_option_strings, opt); /* NB: was strcasecmp! */
 177        if (idx < 0)
 178                return 0;
 179        option = &dhcp_options[idx];
 180
 181        retval = 0;
 182        do {
 183                val = strtok(NULL, ", \t");
 184                if (!val) break;
 185                length = dhcp_option_lengths[option->flags & TYPE_MASK];
 186                retval = 0;
 187                opt = buffer; /* new meaning for variable opt */
 188                switch (option->flags & TYPE_MASK) {
 189                case OPTION_IP:
 190                        retval = read_ip(val, buffer);
 191                        break;
 192                case OPTION_IP_PAIR:
 193                        retval = read_ip(val, buffer);
 194                        val = strtok(NULL, ", \t/-");
 195                        if (!val)
 196                                retval = 0;
 197                        if (retval)
 198                                retval = read_ip(val, buffer + 4);
 199                        break;
 200                case OPTION_STRING:
 201#if ENABLE_FEATURE_UDHCP_RFC3397
 202                case OPTION_STR1035:
 203#endif
 204                        length = strlen(val);
 205                        if (length > 0) {
 206                                if (length > 254) length = 254;
 207                                opt = val;
 208                                retval = 1;
 209                        }
 210                        break;
 211                case OPTION_BOOLEAN:
 212                        retval = read_yn(val, buffer);
 213                        break;
 214                case OPTION_U8:
 215                        buffer[0] = strtoul(val, &endptr, 0);
 216                        retval = (endptr[0] == '\0');
 217                        break;
 218                /* htonX are macros in older libc's, using temp var
 219                 * in code below for safety */
 220                /* TODO: use bb_strtoX? */
 221                case OPTION_U16: {
 222                        unsigned long tmp = strtoul(val, &endptr, 0);
 223                        *result_u16 = htons(tmp);
 224                        retval = (endptr[0] == '\0' /*&& tmp < 0x10000*/);
 225                        break;
 226                }
 227                case OPTION_S16: {
 228                        long tmp = strtol(val, &endptr, 0);
 229                        *result_u16 = htons(tmp);
 230                        retval = (endptr[0] == '\0');
 231                        break;
 232                }
 233                case OPTION_U32: {
 234                        unsigned long tmp = strtoul(val, &endptr, 0);
 235                        *result_u32 = htonl(tmp);
 236                        retval = (endptr[0] == '\0');
 237                        break;
 238                }
 239                case OPTION_S32: {
 240                        long tmp = strtol(val, &endptr, 0);
 241                        *result_u32 = htonl(tmp);
 242                        retval = (endptr[0] == '\0');
 243                        break;
 244                }
 245                default:
 246                        break;
 247                }
 248                if (retval)
 249                        attach_option(opt_list, option, opt, length);
 250        } while (retval && option->flags & OPTION_LIST);
 251        return retval;
 252}
 253
 254static int read_staticlease(const char *const_line, void *arg)
 255{
 256        char *line;
 257        char *mac_string;
 258        char *ip_string;
 259        struct ether_addr mac_bytes;
 260        uint32_t ip;
 261
 262        /* Read mac */
 263        line = (char *) const_line;
 264        mac_string = strtok_r(line, " \t", &line);
 265        read_mac(mac_string, &mac_bytes);
 266
 267        /* Read ip */
 268        ip_string = strtok_r(NULL, " \t", &line);
 269        read_ip(ip_string, &ip);
 270
 271        addStaticLease(arg, (uint8_t*) &mac_bytes, ip);
 272
 273        if (ENABLE_UDHCP_DEBUG) printStaticLeases(arg);
 274
 275        return 1;
 276}
 277
 278
 279struct config_keyword {
 280        const char *keyword;
 281        int (*handler)(const char *line, void *var);
 282        void *var;
 283        const char *def;
 284};
 285
 286static const struct config_keyword keywords[] = {
 287        /* keyword       handler   variable address               default */
 288        {"start",        read_ip,  &(server_config.start_ip),     "192.168.0.20"},
 289        {"end",          read_ip,  &(server_config.end_ip),       "192.168.0.254"},
 290        {"interface",    read_str, &(server_config.interface),    "eth0"},
 291        /* Avoid "max_leases value not sane" warning by setting default
 292         * to default_end_ip - default_start_ip + 1: */
 293        {"max_leases",   read_u32, &(server_config.max_leases),   "235"},
 294//      {"remaining",    read_yn,  &(server_config.remaining),    "yes"},
 295        {"auto_time",    read_u32, &(server_config.auto_time),    "7200"},
 296        {"decline_time", read_u32, &(server_config.decline_time), "3600"},
 297        {"conflict_time",read_u32, &(server_config.conflict_time),"3600"},
 298        {"offer_time",   read_u32, &(server_config.offer_time),   "60"},
 299        {"min_lease",    read_u32, &(server_config.min_lease),    "60"},
 300        {"lease_file",   read_str, &(server_config.lease_file),   LEASES_FILE},
 301        {"pidfile",      read_str, &(server_config.pidfile),      "/var/run/udhcpd.pid"},
 302        {"siaddr",       read_ip,  &(server_config.siaddr),       "0.0.0.0"},
 303        /* keywords with no defaults must be last! */
 304        {"option",       read_opt, &(server_config.options),      ""},
 305        {"opt",          read_opt, &(server_config.options),      ""},
 306        {"notify_file",  read_str, &(server_config.notify_file),  ""},
 307        {"sname",        read_str, &(server_config.sname),        ""},
 308        {"boot_file",    read_str, &(server_config.boot_file),    ""},
 309        {"static_lease", read_staticlease, &(server_config.static_leases), ""},
 310};
 311enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 };
 312
 313void FAST_FUNC read_config(const char *file)
 314{
 315        parser_t *parser;
 316        const struct config_keyword *k;
 317        unsigned i;
 318        char *token[2];
 319
 320        for (i = 0; i < KWS_WITH_DEFAULTS; i++)
 321                keywords[i].handler(keywords[i].def, keywords[i].var);
 322
 323        parser = config_open(file);
 324        while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
 325                for (k = keywords, i = 0; i < ARRAY_SIZE(keywords); k++, i++) {
 326                        if (!strcasecmp(token[0], k->keyword)) {
 327                                if (!k->handler(token[1], k->var)) {
 328                                        bb_error_msg("can't parse line %u in %s",
 329                                                        parser->lineno, file);
 330                                        /* reset back to the default value */
 331                                        k->handler(k->def, k->var);
 332                                }
 333                                break;
 334                        }
 335                }
 336        }
 337        config_close(parser);
 338
 339        server_config.start_ip = ntohl(server_config.start_ip);
 340        server_config.end_ip = ntohl(server_config.end_ip);
 341}
 342
 343
 344void FAST_FUNC write_leases(void)
 345{
 346        int fd;
 347        unsigned i;
 348        leasetime_t curr;
 349        int64_t written_at;
 350
 351        fd = open_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC);
 352        if (fd < 0)
 353                return;
 354
 355        curr = written_at = time(NULL);
 356
 357        written_at = hton64(written_at);
 358        full_write(fd, &written_at, sizeof(written_at));
 359
 360        for (i = 0; i < server_config.max_leases; i++) {
 361                leasetime_t tmp_time;
 362
 363                if (leases[i].yiaddr == 0)
 364                        continue;
 365
 366                /* Screw with the time in the struct, for easier writing */
 367                tmp_time = leases[i].expires;
 368
 369                leases[i].expires -= curr;
 370                if ((signed_leasetime_t) leases[i].expires < 0)
 371                        leases[i].expires = 0;
 372                leases[i].expires = htonl(leases[i].expires);
 373
 374                /* No error check. If the file gets truncated,
 375                 * we lose some leases on restart. Oh well. */
 376                full_write(fd, &leases[i], sizeof(leases[i]));
 377
 378                /* Then restore it when done */
 379                leases[i].expires = tmp_time;
 380        }
 381        close(fd);
 382
 383        if (server_config.notify_file) {
 384// TODO: vfork-based child creation
 385                char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file);
 386                system(cmd);
 387                free(cmd);
 388        }
 389}
 390
 391
 392void FAST_FUNC read_leases(const char *file)
 393{
 394        struct dhcpOfferedAddr lease;
 395        int64_t written_at, time_passed;
 396        int fd;
 397        USE_UDHCP_DEBUG(unsigned i;)
 398
 399        fd = open_or_warn(file, O_RDONLY);
 400        if (fd < 0)
 401                return;
 402
 403        if (full_read(fd, &written_at, sizeof(written_at)) != sizeof(written_at))
 404                goto ret;
 405        written_at = ntoh64(written_at);
 406
 407        time_passed = time(NULL) - written_at;
 408        /* Strange written_at, or lease file from old version of udhcpd
 409         * which had no "written_at" field? */
 410        if ((uint64_t)time_passed > 12 * 60 * 60)
 411                goto ret;
 412
 413        USE_UDHCP_DEBUG(i = 0;)
 414        while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) {
 415                /* ADDME: what if it matches some static lease? */
 416                uint32_t y = ntohl(lease.yiaddr);
 417                if (y >= server_config.start_ip && y <= server_config.end_ip) {
 418                        signed_leasetime_t expires = ntohl(lease.expires) - (signed_leasetime_t)time_passed;
 419                        if (expires <= 0)
 420                                continue;
 421                        /* NB: add_lease takes "relative time", IOW,
 422                         * lease duration, not lease deadline. */
 423                        if (!(add_lease(lease.chaddr, lease.yiaddr, expires, lease.hostname))) {
 424                                bb_error_msg("too many leases while loading %s", file);
 425                                break;
 426                        }
 427                        USE_UDHCP_DEBUG(i++;)
 428                }
 429        }
 430        DEBUG("Read %d leases", i);
 431 ret:
 432        close(fd);
 433}
 434