]> git.ipfire.org Git - thirdparty/dhcpcd.git/blame - if-options.c
As our logger calls emulate syslog, we can use %m. However, this means we have to...
[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) {
da166178
RM
260 case 'b': /* FALLTHROUGH */
261 case 'd': /* FALLTHROUGH */
262 case 'k': /* FALLTHROUGH */
263 case 'n': /* FALLTHROUGH */
264 case 'x': /* FALLTHROUGH */
e7eeaf88
RM
265 case 'B': /* FALLTHROUGH */
266 case 'T': /* We need to handle non interface options */
fd05b7dc
RM
267 break;
268 case 'c':
269 strlcpy(ifo->script, arg, sizeof(ifo->script));
270 break;
fd05b7dc
RM
271 case 'h':
272 if (arg)
273 s = parse_string(ifo->hostname + 1,
274 HOSTNAME_MAX_LEN, arg);
275 else
276 s = 0;
277 if (s == -1) {
f6de5860 278 logger(LOG_ERR, "hostname: %m");
fd05b7dc
RM
279 return -1;
280 }
281 if (s != 0 && ifo->hostname[1] == '.') {
282 logger(LOG_ERR, "hostname cannot begin with a .");
283 return -1;
284 }
285 ifo->hostname[0] = (uint8_t)s;
286 break;
287 case 'i':
288 if (arg)
289 s = parse_string((char *)ifo->vendorclassid + 1,
290 VENDORCLASSID_MAX_LEN, arg);
291 else
292 s = 0;
293 if (s == -1) {
f6de5860 294 logger(LOG_ERR, "vendorclassid: %m");
fd05b7dc
RM
295 return -1;
296 }
297 *ifo->vendorclassid = (uint8_t)s;
298 break;
299 case 'l':
300 if (*arg == '-') {
301 logger(LOG_ERR,
302 "leasetime must be a positive value");
303 return -1;
304 }
305 errno = 0;
306 ifo->leasetime = (uint32_t)strtol(arg, NULL, 0);
307 if (errno == EINVAL || errno == ERANGE) {
308 logger(LOG_ERR, "`%s' out of range", arg);
309 return -1;
310 }
311 break;
312 case 'm':
313 ifo->metric = atoint(arg);
314 if (ifo->metric < 0) {
315 logger(LOG_ERR, "metric must be a positive value");
316 return -1;
317 }
318 break;
319 case 'o':
320 if (make_option_mask(ifo->requestmask, arg, 1) != 0) {
321 logger(LOG_ERR, "unknown option `%s'", arg);
322 return -1;
323 }
324 break;
325 case 'p':
326 ifo->options |= DHCPCD_PERSISTENT;
327 break;
328 case 'q':
329 setloglevel(LOG_WARNING);
330 break;
331 case 's':
332 ifo->options |= DHCPCD_INFORM;
333 ifo->options |= DHCPCD_PERSISTENT;
334 ifo->options &= ~DHCPCD_ARP;
335 if (!arg || *arg == '\0') {
336 ifo->request_address.s_addr = 0;
337 break;
338 } else {
339 if ((p = strchr(arg, '/'))) {
340 /* nullify the slash, so the -r option
341 * can read the address */
342 *p++ = '\0';
343 if (sscanf(p, "%d", &i) != 1 ||
344 inet_cidrtoaddr(i, &ifo->request_netmask) != 0)
345 {
346 logger(LOG_ERR,
347 "`%s' is not a valid CIDR",
348 p);
349 return -1;
350 }
351 }
352 }
353 /* FALLTHROUGH */
354 case 'r':
355 if (!(ifo->options & DHCPCD_INFORM))
356 ifo->options |= DHCPCD_REQUEST;
357 if (arg && !inet_aton(arg, &ifo->request_address)) {
358 logger(LOG_ERR, "`%s' is not a valid IP address",
359 arg);
360 return -1;
361 }
362 break;
363 case 't':
364 ifo->timeout = atoint(arg);
365 if (ifo->timeout < 0) {
366 logger (LOG_ERR, "timeout must be a positive value");
367 return -1;
368 }
369 break;
370 case 'u':
371 s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1;
372 s = parse_string((char *)ifo->userclass + ifo->userclass[0] + 2,
373 s, arg);
374 if (s == -1) {
f6de5860 375 logger(LOG_ERR, "userclass: %m");
fd05b7dc
RM
376 return -1;
377 }
378 if (s != 0) {
379 ifo->userclass[ifo->userclass[0] + 1] = s;
380 ifo->userclass[0] += s + 1;
381 }
382 break;
383 case 'v':
384 p = strchr(arg, ',');
385 if (!p || !p[1]) {
386 logger(LOG_ERR, "invalid vendor format");
387 return -1;
388 }
389 *p = '\0';
390 i = atoint(arg);
391 arg = p + 1;
392 if (i < 1 || i > 254) {
393 logger(LOG_ERR, "vendor option should be between"
394 " 1 and 254 inclusive");
395 return -1;
396 }
397 s = VENDOR_MAX_LEN - ifo->vendor[0] - 2;
398 if (inet_aton(arg, &addr) == 1) {
399 if (s < 6) {
400 s = -1;
401 errno = ENOBUFS;
402 } else
403 memcpy(ifo->vendor + ifo->vendor[0] + 3,
404 &addr.s_addr, sizeof(addr.s_addr));
405 } else {
406 s = parse_string((char *)ifo->vendor + ifo->vendor[0] + 3,
407 s, arg);
408 }
409 if (s == -1) {
f6de5860 410 logger(LOG_ERR, "vendor: %m");
fd05b7dc
RM
411 return -1;
412 }
413 if (s != 0) {
414 ifo->vendor[ifo->vendor[0] + 1] = i;
415 ifo->vendor[ifo->vendor[0] + 2] = s;
416 ifo->vendor[0] += s + 2;
417 }
418 break;
fd05b7dc
RM
419 case 'A':
420 ifo->options &= ~DHCPCD_ARP;
421 /* IPv4LL requires ARP */
422 ifo->options &= ~DHCPCD_IPV4LL;
423 break;
fd05b7dc
RM
424 case 'C':
425 /* Commas to spaces for shell */
426 while ((p = strchr(arg, ',')))
427 *p = ' ';
428 s = strlen("skip_hooks=") + strlen(arg) + 1;
429 p = xmalloc(sizeof(char) * s);
430 snprintf(p, s, "skip_hooks=%s", arg);
431 add_environ(ifo, p, 0);
432 free(p);
433 break;
434 case 'D':
435 ifo->options |= DHCPCD_DUID;
436 break;
437 case 'E':
438 ifo->options |= DHCPCD_LASTLEASE;
439 break;
440 case 'F':
441 if (!arg) {
442 ifo->fqdn = FQDN_BOTH;
443 break;
444 }
445 if (strcmp(arg, "none") == 0)
446 ifo->fqdn = FQDN_NONE;
447 else if (strcmp(arg, "ptr") == 0)
448 ifo->fqdn = FQDN_PTR;
449 else if (strcmp(arg, "both") == 0)
450 ifo->fqdn = FQDN_BOTH;
451 else if (strcmp(arg, "disable") == 0)
452 ifo->fqdn = FQDN_DISABLE;
453 else {
454 logger(LOG_ERR, "invalid value `%s' for FQDN", arg);
455 return -1;
456 }
457 break;
458 case 'G':
459 ifo->options &= ~DHCPCD_GATEWAY;
460 break;
461 case 'I':
462 /* Strings have a type of 0 */;
463 ifo->clientid[1] = 0;
464 if (arg)
465 s = parse_string_hwaddr((char *)ifo->clientid + 1,
466 CLIENTID_MAX_LEN, arg, 1);
467 else
468 s = 0;
469 if (s == -1) {
f6de5860 470 logger(LOG_ERR, "clientid: %m");
fd05b7dc
RM
471 return -1;
472 }
473 ifo->clientid[0] = (uint8_t)s;
474 if (s == 0) {
475 ifo->options &= ~DHCPCD_DUID;
476 ifo->options &= ~DHCPCD_CLIENTID;
477 }
478 break;
479 case 'K':
480 ifo->options &= ~DHCPCD_LINK;
481 break;
482 case 'L':
483 ifo->options &= ~DHCPCD_IPV4LL;
484 break;
485 case 'O':
486 if (make_option_mask(ifo->requestmask, arg, -1) != 0 ||
487 make_option_mask(ifo->requiremask, arg, -1) != 0 ||
488 make_option_mask(ifo->nomask, arg, 1) != 0)
489 {
490 logger(LOG_ERR, "unknown option `%s'", arg);
491 return -1;
492 }
493 break;
494 case 'Q':
495 if (make_option_mask(ifo->requiremask, arg, 1) != 0 ||
496 make_option_mask(ifo->requestmask, arg, 1) != 0)
497 {
498 logger(LOG_ERR, "unknown option `%s'", arg);
499 return -1;
500 }
501 break;
502 case 'X':
503 if (!inet_aton(arg, &addr)) {
504 logger(LOG_ERR, "`%s' is not a valid IP address",
505 arg);
506 return -1;
507 }
508 ifo->blacklist = xrealloc(ifo->blacklist,
509 sizeof(in_addr_t) * (ifo->blacklist_len + 1));
510 ifo->blacklist[ifo->blacklist_len] = addr.s_addr;
511 ifo->blacklist_len++;
512 break;
513 default:
514 return 0;
515 }
516
517 return 1;
518}
519
520static int
521parse_config_line(struct if_options *ifo, const char *opt, char *line)
522{
523 unsigned int i;
524
525 for (i = 0; i < sizeof(cf_options) / sizeof(cf_options[0]); i++) {
526 if (!cf_options[i].name ||
527 strcmp(cf_options[i].name, opt) != 0)
528 continue;
529
530 if (cf_options[i].has_arg == required_argument && !line) {
531 fprintf(stderr,
532 PACKAGE ": option requires an argument -- %s\n",
533 opt);
534 return -1;
535 }
536
537 return parse_option(ifo, cf_options[i].val, line);
538 }
539
540 fprintf(stderr, PACKAGE ": unknown option -- %s\n", opt);
541 return -1;
542}
543
544struct if_options *
545read_config(const char *file, const char *ifname)
546{
547 struct if_options *ifo;
548 FILE *f;
549 size_t len = 0;
550 char *line, *option, *p, *buffer = NULL;
551 int skip = 0;
552
553 /* Seed our default options */
554 ifo = xzalloc(sizeof(*ifo));
555 ifo->options |= DHCPCD_CLIENTID | DHCPCD_GATEWAY | DHCPCD_DAEMONISE;
556 ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK;
557 ifo->timeout = DEFAULT_TIMEOUT;
f43e5853 558 ifo->metric = -1;
fd05b7dc
RM
559 gethostname(ifo->hostname + 1, sizeof(ifo->hostname));
560 if (strcmp(ifo->hostname + 1, "(none)") == 0 ||
561 strcmp(ifo->hostname + 1, "localhost") == 0)
562 ifo->hostname[1] = '\0';
563 *ifo->hostname = strlen(ifo->hostname + 1);
564 strlcpy(ifo->script, SCRIPT, sizeof(ifo->script));
565 ifo->vendorclassid[0] = snprintf((char *)ifo->vendorclassid + 1,
566 VENDORCLASSID_MAX_LEN,
567 "%s %s", PACKAGE, VERSION);
568
569 /* Parse our options file */
570 f = fopen(file, "r");
571 if (!f)
572 return ifo;
573
574 while ((get_line(&buffer, &len, f))) {
575 line = buffer;
576 while ((option = strsep(&line, " \t")))
577 if (*option != '\0')
578 break;
579 if (!option || *option == '\0' || *option == '#')
580 continue;
581 /* Trim leading whitespace */
582 if (line) {
583 while (*line != '\0' && (*line == ' ' || *line == '\t'))
584 line++;
585 }
586 /* Trim trailing whitespace */
587 if (line && *line) {
588 p = line + strlen(line) - 1;
589 while (p != line &&
590 (*p == ' ' || *p == '\t') &&
591 *(p - 1) != '\\')
592 *p-- = '\0';
593 }
594 /* Start of an interface block, skip if not ours */
595 if (strcmp(option, "interface") == 0) {
596 if (ifname && line && strcmp(line, ifname) == 0)
597 skip = 0;
598 else
599 skip = 1;
600 continue;
601 }
602 if (skip)
603 continue;
604 if (parse_config_line(ifo, option, line) != 1) {
605 break;
606 }
607 }
608 free(buffer);
609 fclose(f);
610
611 /* Terminate the encapsulated options */
612 if (ifo->vendor[0]) {
613 ifo->vendor[0]++;
614 ifo->vendor[ifo->vendor[0]] = DHO_END;
615 }
616 return ifo;
617}
618
619int
620add_options(struct if_options *ifo, int argc, char **argv)
621{
622 int oi, opt, r = 1;
623
624 optind = 0;
625 while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
626 {
627 r = parse_option(ifo, opt, optarg);
628 if (r != 1)
629 break;
630 }
631 /* Terminate the encapsulated options */
632 if (r == 1 && ifo->vendor[0]) {
633 ifo->vendor[0]++;
634 ifo->vendor[ifo->vendor[0]] = DHO_END;
635 }
636 return r;
637}
638
639void
640free_options(struct if_options *ifo)
641{
642 size_t i;
643
f43e5853
RM
644 if (ifo) {
645 if (ifo->environ) {
646 i = 0;
647 while (ifo->environ[i])
648 free(ifo->environ[i++]);
649 free(ifo->environ);
650 }
651 free(ifo->blacklist);
652 free(ifo);
fd05b7dc 653 }
fd05b7dc 654}