]> git.ipfire.org Git - thirdparty/dhcpcd.git/blame - if-options.c
Add the remembered routes in reverse order. This saves having to reverse them later...
[thirdparty/dhcpcd.git] / if-options.c
CommitLineData
fd05b7dc
RM
1/*
2 * dhcpcd - DHCP client daemon
3 * Copyright 2006-2008 Roy Marples <roy@marples.name>
4 * All rights reserved
5
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/types.h>
29
30#include <arpa/inet.h>
31
32#include <ctype.h>
33#include <errno.h>
34#include <getopt.h>
35#include <paths.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <unistd.h>
40#include <time.h>
41
42#include "common.h"
43#include "config.h"
44#include "dhcpf.h"
45#include "if-options.h"
46#include "logger.h"
47#include "net.h"
48
49/* Don't set any optional arguments here so we retain POSIX
50 * compatibility with getopt */
51#define OPTS "bc:df:h:i:kl:m:no:pqr:s:t:u:v:xABC:DEF:GI:KLO:Q:TVX:"
52
53const struct option cf_options[] = {
54 {"background", no_argument, NULL, 'b'},
55 {"script", required_argument, NULL, 'c'},
56 {"debug", no_argument, NULL, 'd'},
57 {"config", required_argument, NULL, 'f'},
58 {"hostname", optional_argument, NULL, 'h'},
59 {"vendorclassid", optional_argument, NULL, 'i'},
60 {"release", no_argument, NULL, 'k'},
61 {"leasetime", required_argument, NULL, 'l'},
62 {"metric", required_argument, NULL, 'm'},
63 {"rebind", no_argument, NULL, 'n'},
64 {"option", required_argument, NULL, 'o'},
65 {"persistent", no_argument, NULL, 'p'},
66 {"quiet", no_argument, NULL, 'q'},
67 {"request", optional_argument, NULL, 'r'},
68 {"inform", optional_argument, NULL, 's'},
69 {"timeout", required_argument, NULL, 't'},
70 {"userclass", required_argument, NULL, 'u'},
71 {"vendor", required_argument, NULL, 'v'},
72 {"exit", no_argument, NULL, 'x'},
73 {"noarp", no_argument, NULL, 'A'},
74 {"nobackground", no_argument, NULL, 'B'},
75 {"nohook", required_argument, NULL, 'C'},
76 {"duid", no_argument, NULL, 'D'},
77 {"lastlease", no_argument, NULL, 'E'},
78 {"fqdn", optional_argument, NULL, 'F'},
79 {"nogateway", no_argument, NULL, 'G'},
80 {"clientid", optional_argument, NULL, 'I'},
81 {"nolink", no_argument, NULL, 'K'},
82 {"noipv4ll", no_argument, NULL, 'L'},
83 {"nooption", optional_argument, NULL, 'O'},
84 {"require", required_argument, NULL, 'Q'},
85 {"test", no_argument, NULL, 'T'},
86 {"variables", no_argument, NULL, 'V'},
87 {"blacklist", required_argument, NULL, 'X'},
88 {NULL, 0, NULL, '\0'}
89};
90
91static int
92atoint(const char *s)
93{
94 char *t;
95 long n;
96
97 errno = 0;
98 n = strtol(s, &t, 0);
99 if ((errno != 0 && n == 0) || s == t ||
100 (errno == ERANGE && (n == LONG_MAX || n == LONG_MIN)))
101 {
102 logger(LOG_ERR, "`%s' out of range", s);
103 return -1;
104 }
105
106 return (int)n;
107}
108
109static char *
110add_environ(struct if_options *ifo, const char *value, int uniq)
111{
112 char **newlist;
113 char **lst = ifo->environ;
114 size_t i = 0, l, lv;
115 char *match = NULL, *p;
116
117 match = xstrdup(value);
118 p = strchr(match, '=');
119 if (p)
120 *p++ = '\0';
121 l = strlen(match);
122
123 while (lst && lst[i]) {
124 if (match && strncmp(lst[i], match, l) == 0) {
125 if (uniq) {
126 free(lst[i]);
127 lst[i] = xstrdup(value);
128 } else {
129 /* Append a space and the value to it */
130 l = strlen(lst[i]);
131 lv = strlen(p);
132 lst[i] = xrealloc(lst[i], l + lv + 2);
133 lst[i][l] = ' ';
134 memcpy(lst[i] + l + 1, p, lv);
135 lst[i][l + lv + 1] = '\0';
136 }
137 free(match);
138 return lst[i];
139 }
140 i++;
141 }
142
143 newlist = xrealloc(lst, sizeof(char *) * (i + 2));
144 newlist[i] = xstrdup(value);
145 newlist[i + 1] = NULL;
146 ifo->environ = newlist;
147 free(match);
148 return newlist[i];
149}
150
151#define parse_string(buf, len, arg) parse_string_hwaddr(buf, len, arg, 0)
152static ssize_t
153parse_string_hwaddr(char *sbuf, ssize_t slen, const char *str, int clid)
154{
155 ssize_t l;
156 const char *p;
157 int i, punt_last = 0;
158 char c[4];
159
160 /* If surrounded by quotes then it's a string */
161 if (*str == '"') {
162 str++;
163 l = strlen(str);
164 p = str + l - 1;
165 if (*p == '"')
166 punt_last = 1;
167 } else {
168 l = hwaddr_aton(NULL, str);
169 if (l > 1) {
170 if (l > slen) {
171 errno = ENOBUFS;
172 return -1;
173 }
174 hwaddr_aton((uint8_t *)sbuf, str);
175 return l;
176 }
177 }
178
179 /* Process escapes */
180 l = 0;
181 /* If processing a string on the clientid, first byte should be
182 * 0 to indicate a non hardware type */
183 if (clid) {
184 *sbuf++ = 0;
185 l++;
186 }
187 c[3] = '\0';
188 while (*str) {
189 if (++l > slen) {
190 errno = ENOBUFS;
191 return -1;
192 }
193 if (*str == '\\') {
194 str++;
195 switch(*str++) {
196 case '\0':
197 break;
198 case 'b':
199 *sbuf++ = '\b';
200 break;
201 case 'n':
202 *sbuf++ = '\n';
203 break;
204 case 'r':
205 *sbuf++ = '\r';
206 break;
207 case 't':
208 *sbuf++ = '\t';
209 break;
210 case 'x':
211 /* Grab a hex code */
212 c[1] = '\0';
213 for (i = 0; i < 2; i++) {
214 if (isxdigit((unsigned char)*str) == 0)
215 break;
216 c[i] = *str++;
217 }
218 if (c[1] != '\0') {
219 c[2] = '\0';
220 *sbuf++ = strtol(c, NULL, 16);
221 } else
222 l--;
223 break;
224 case '0':
225 /* Grab an octal code */
226 c[2] = '\0';
227 for (i = 0; i < 3; i++) {
228 if (*str < '0' || *str > '7')
229 break;
230 c[i] = *str++;
231 }
232 if (c[2] != '\0') {
233 i = strtol(c, NULL, 8);
234 if (i > 255)
235 i = 255;
236 *sbuf ++= i;
237 } else
238 l--;
239 break;
240 default:
241 *sbuf++ = *str++;
242 }
243 } else
244 *sbuf++ = *str++;
245 }
246 if (punt_last)
247 *--sbuf = '\0';
248 return l;
249}
250
251static int
252parse_option(struct if_options *ifo, int opt, const char *arg)
253{
254 int i;
255 char *p;
256 ssize_t s;
257 struct in_addr addr;
258
259 switch(opt) {
260 case 'b':
261 ifo->options |= DHCPCD_BACKGROUND;
262 break;
263 case 'c':
264 strlcpy(ifo->script, arg, sizeof(ifo->script));
265 break;
266 case 'd':
267 break;
268 case 'h':
269 if (arg)
270 s = parse_string(ifo->hostname + 1,
271 HOSTNAME_MAX_LEN, arg);
272 else
273 s = 0;
274 if (s == -1) {
275 logger(LOG_ERR, "hostname: %s", strerror(errno));
276 return -1;
277 }
278 if (s != 0 && ifo->hostname[1] == '.') {
279 logger(LOG_ERR, "hostname cannot begin with a .");
280 return -1;
281 }
282 ifo->hostname[0] = (uint8_t)s;
283 break;
284 case 'i':
285 if (arg)
286 s = parse_string((char *)ifo->vendorclassid + 1,
287 VENDORCLASSID_MAX_LEN, arg);
288 else
289 s = 0;
290 if (s == -1) {
291 logger(LOG_ERR, "vendorclassid: %s", strerror(errno));
292 return -1;
293 }
294 *ifo->vendorclassid = (uint8_t)s;
295 break;
f43e5853
RM
296 case 'k':
297 break;
fd05b7dc
RM
298 case 'l':
299 if (*arg == '-') {
300 logger(LOG_ERR,
301 "leasetime must be a positive value");
302 return -1;
303 }
304 errno = 0;
305 ifo->leasetime = (uint32_t)strtol(arg, NULL, 0);
306 if (errno == EINVAL || errno == ERANGE) {
307 logger(LOG_ERR, "`%s' out of range", arg);
308 return -1;
309 }
310 break;
311 case 'm':
312 ifo->metric = atoint(arg);
313 if (ifo->metric < 0) {
314 logger(LOG_ERR, "metric must be a positive value");
315 return -1;
316 }
317 break;
f43e5853
RM
318 case 'n':
319 break;
fd05b7dc
RM
320 case 'o':
321 if (make_option_mask(ifo->requestmask, arg, 1) != 0) {
322 logger(LOG_ERR, "unknown option `%s'", arg);
323 return -1;
324 }
325 break;
326 case 'p':
327 ifo->options |= DHCPCD_PERSISTENT;
328 break;
329 case 'q':
330 setloglevel(LOG_WARNING);
331 break;
332 case 's':
333 ifo->options |= DHCPCD_INFORM;
334 ifo->options |= DHCPCD_PERSISTENT;
335 ifo->options &= ~DHCPCD_ARP;
336 if (!arg || *arg == '\0') {
337 ifo->request_address.s_addr = 0;
338 break;
339 } else {
340 if ((p = strchr(arg, '/'))) {
341 /* nullify the slash, so the -r option
342 * can read the address */
343 *p++ = '\0';
344 if (sscanf(p, "%d", &i) != 1 ||
345 inet_cidrtoaddr(i, &ifo->request_netmask) != 0)
346 {
347 logger(LOG_ERR,
348 "`%s' is not a valid CIDR",
349 p);
350 return -1;
351 }
352 }
353 }
354 /* FALLTHROUGH */
355 case 'r':
356 if (!(ifo->options & DHCPCD_INFORM))
357 ifo->options |= DHCPCD_REQUEST;
358 if (arg && !inet_aton(arg, &ifo->request_address)) {
359 logger(LOG_ERR, "`%s' is not a valid IP address",
360 arg);
361 return -1;
362 }
363 break;
364 case 't':
365 ifo->timeout = atoint(arg);
366 if (ifo->timeout < 0) {
367 logger (LOG_ERR, "timeout must be a positive value");
368 return -1;
369 }
370 break;
371 case 'u':
372 s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1;
373 s = parse_string((char *)ifo->userclass + ifo->userclass[0] + 2,
374 s, arg);
375 if (s == -1) {
376 logger(LOG_ERR, "userclass: %s", strerror(errno));
377 return -1;
378 }
379 if (s != 0) {
380 ifo->userclass[ifo->userclass[0] + 1] = s;
381 ifo->userclass[0] += s + 1;
382 }
383 break;
384 case 'v':
385 p = strchr(arg, ',');
386 if (!p || !p[1]) {
387 logger(LOG_ERR, "invalid vendor format");
388 return -1;
389 }
390 *p = '\0';
391 i = atoint(arg);
392 arg = p + 1;
393 if (i < 1 || i > 254) {
394 logger(LOG_ERR, "vendor option should be between"
395 " 1 and 254 inclusive");
396 return -1;
397 }
398 s = VENDOR_MAX_LEN - ifo->vendor[0] - 2;
399 if (inet_aton(arg, &addr) == 1) {
400 if (s < 6) {
401 s = -1;
402 errno = ENOBUFS;
403 } else
404 memcpy(ifo->vendor + ifo->vendor[0] + 3,
405 &addr.s_addr, sizeof(addr.s_addr));
406 } else {
407 s = parse_string((char *)ifo->vendor + ifo->vendor[0] + 3,
408 s, arg);
409 }
410 if (s == -1) {
411 logger(LOG_ERR, "vendor: %s", strerror(errno));
412 return -1;
413 }
414 if (s != 0) {
415 ifo->vendor[ifo->vendor[0] + 1] = i;
416 ifo->vendor[ifo->vendor[0] + 2] = s;
417 ifo->vendor[0] += s + 2;
418 }
419 break;
420 case 'x':
421 break;
422 case 'A':
423 ifo->options &= ~DHCPCD_ARP;
424 /* IPv4LL requires ARP */
425 ifo->options &= ~DHCPCD_IPV4LL;
426 break;
427 case 'B':
428 ifo->options &= ~DHCPCD_DAEMONISE;
429 break;
430 case 'C':
431 /* Commas to spaces for shell */
432 while ((p = strchr(arg, ',')))
433 *p = ' ';
434 s = strlen("skip_hooks=") + strlen(arg) + 1;
435 p = xmalloc(sizeof(char) * s);
436 snprintf(p, s, "skip_hooks=%s", arg);
437 add_environ(ifo, p, 0);
438 free(p);
439 break;
440 case 'D':
441 ifo->options |= DHCPCD_DUID;
442 break;
443 case 'E':
444 ifo->options |= DHCPCD_LASTLEASE;
445 break;
446 case 'F':
447 if (!arg) {
448 ifo->fqdn = FQDN_BOTH;
449 break;
450 }
451 if (strcmp(arg, "none") == 0)
452 ifo->fqdn = FQDN_NONE;
453 else if (strcmp(arg, "ptr") == 0)
454 ifo->fqdn = FQDN_PTR;
455 else if (strcmp(arg, "both") == 0)
456 ifo->fqdn = FQDN_BOTH;
457 else if (strcmp(arg, "disable") == 0)
458 ifo->fqdn = FQDN_DISABLE;
459 else {
460 logger(LOG_ERR, "invalid value `%s' for FQDN", arg);
461 return -1;
462 }
463 break;
464 case 'G':
465 ifo->options &= ~DHCPCD_GATEWAY;
466 break;
467 case 'I':
468 /* Strings have a type of 0 */;
469 ifo->clientid[1] = 0;
470 if (arg)
471 s = parse_string_hwaddr((char *)ifo->clientid + 1,
472 CLIENTID_MAX_LEN, arg, 1);
473 else
474 s = 0;
475 if (s == -1) {
476 logger(LOG_ERR, "clientid: %s", strerror(errno));
477 return -1;
478 }
479 ifo->clientid[0] = (uint8_t)s;
480 if (s == 0) {
481 ifo->options &= ~DHCPCD_DUID;
482 ifo->options &= ~DHCPCD_CLIENTID;
483 }
484 break;
485 case 'K':
486 ifo->options &= ~DHCPCD_LINK;
487 break;
488 case 'L':
489 ifo->options &= ~DHCPCD_IPV4LL;
490 break;
491 case 'O':
492 if (make_option_mask(ifo->requestmask, arg, -1) != 0 ||
493 make_option_mask(ifo->requiremask, arg, -1) != 0 ||
494 make_option_mask(ifo->nomask, arg, 1) != 0)
495 {
496 logger(LOG_ERR, "unknown option `%s'", arg);
497 return -1;
498 }
499 break;
500 case 'Q':
501 if (make_option_mask(ifo->requiremask, arg, 1) != 0 ||
502 make_option_mask(ifo->requestmask, arg, 1) != 0)
503 {
504 logger(LOG_ERR, "unknown option `%s'", arg);
505 return -1;
506 }
507 break;
508 case 'X':
509 if (!inet_aton(arg, &addr)) {
510 logger(LOG_ERR, "`%s' is not a valid IP address",
511 arg);
512 return -1;
513 }
514 ifo->blacklist = xrealloc(ifo->blacklist,
515 sizeof(in_addr_t) * (ifo->blacklist_len + 1));
516 ifo->blacklist[ifo->blacklist_len] = addr.s_addr;
517 ifo->blacklist_len++;
518 break;
519 default:
520 return 0;
521 }
522
523 return 1;
524}
525
526static int
527parse_config_line(struct if_options *ifo, const char *opt, char *line)
528{
529 unsigned int i;
530
531 for (i = 0; i < sizeof(cf_options) / sizeof(cf_options[0]); i++) {
532 if (!cf_options[i].name ||
533 strcmp(cf_options[i].name, opt) != 0)
534 continue;
535
536 if (cf_options[i].has_arg == required_argument && !line) {
537 fprintf(stderr,
538 PACKAGE ": option requires an argument -- %s\n",
539 opt);
540 return -1;
541 }
542
543 return parse_option(ifo, cf_options[i].val, line);
544 }
545
546 fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt);
547 return -1;
548}
549
550struct if_options *
551read_config(const char *file, const char *ifname)
552{
553 struct if_options *ifo;
554 FILE *f;
555 size_t len = 0;
556 char *line, *option, *p, *buffer = NULL;
557 int skip = 0;
558
559 /* Seed our default options */
560 ifo = xzalloc(sizeof(*ifo));
561 ifo->options |= DHCPCD_CLIENTID | DHCPCD_GATEWAY | DHCPCD_DAEMONISE;
562 ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK;
563 ifo->timeout = DEFAULT_TIMEOUT;
f43e5853 564 ifo->metric = -1;
fd05b7dc
RM
565 gethostname(ifo->hostname + 1, sizeof(ifo->hostname));
566 if (strcmp(ifo->hostname + 1, "(none)") == 0 ||
567 strcmp(ifo->hostname + 1, "localhost") == 0)
568 ifo->hostname[1] = '\0';
569 *ifo->hostname = strlen(ifo->hostname + 1);
570 strlcpy(ifo->script, SCRIPT, sizeof(ifo->script));
571 ifo->vendorclassid[0] = snprintf((char *)ifo->vendorclassid + 1,
572 VENDORCLASSID_MAX_LEN,
573 "%s %s", PACKAGE, VERSION);
574
575 /* Parse our options file */
576 f = fopen(file, "r");
577 if (!f)
578 return ifo;
579
580 while ((get_line(&buffer, &len, f))) {
581 line = buffer;
582 while ((option = strsep(&line, " \t")))
583 if (*option != '\0')
584 break;
585 if (!option || *option == '\0' || *option == '#')
586 continue;
587 /* Trim leading whitespace */
588 if (line) {
589 while (*line != '\0' && (*line == ' ' || *line == '\t'))
590 line++;
591 }
592 /* Trim trailing whitespace */
593 if (line && *line) {
594 p = line + strlen(line) - 1;
595 while (p != line &&
596 (*p == ' ' || *p == '\t') &&
597 *(p - 1) != '\\')
598 *p-- = '\0';
599 }
600 /* Start of an interface block, skip if not ours */
601 if (strcmp(option, "interface") == 0) {
602 if (ifname && line && strcmp(line, ifname) == 0)
603 skip = 0;
604 else
605 skip = 1;
606 continue;
607 }
608 if (skip)
609 continue;
610 if (parse_config_line(ifo, option, line) != 1) {
611 break;
612 }
613 }
614 free(buffer);
615 fclose(f);
616
617 /* Terminate the encapsulated options */
618 if (ifo->vendor[0]) {
619 ifo->vendor[0]++;
620 ifo->vendor[ifo->vendor[0]] = DHO_END;
621 }
622 return ifo;
623}
624
625int
626add_options(struct if_options *ifo, int argc, char **argv)
627{
628 int oi, opt, r = 1;
629
630 optind = 0;
631 while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
632 {
633 r = parse_option(ifo, opt, optarg);
634 if (r != 1)
635 break;
636 }
637 /* Terminate the encapsulated options */
638 if (r == 1 && ifo->vendor[0]) {
639 ifo->vendor[0]++;
640 ifo->vendor[ifo->vendor[0]] = DHO_END;
641 }
642 return r;
643}
644
645void
646free_options(struct if_options *ifo)
647{
648 size_t i;
649
f43e5853
RM
650 if (ifo) {
651 if (ifo->environ) {
652 i = 0;
653 while (ifo->environ[i])
654 free(ifo->environ[i++]);
655 free(ifo->environ);
656 }
657 free(ifo->blacklist);
658 free(ifo);
fd05b7dc 659 }
fd05b7dc 660}