1 /* $OpenBSD: parse.y,v 1.36 2019/06/14 19:55:08 florian Exp $ */ 2 3 /* 4 * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv> 5 * Copyright (c) 2016 Sebastian Benoit <benno@openbsd.org> 6 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> 7 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> 8 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> 9 * Copyright (c) 2001 Markus Friedl. All rights reserved. 10 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. 11 * Copyright (c) 2001 Theo de Raadt. All rights reserved. 12 * 13 * Permission to use, copy, modify, and distribute this software for any 14 * purpose with or without fee is hereby granted, provided that the above 15 * copyright notice and this permission notice appear in all copies. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 18 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 19 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 20 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 21 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 22 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 23 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 24 */ 25 26 %{ 27 #include <sys/types.h> 28 #include <sys/queue.h> 29 #include <sys/stat.h> 30 #include <ctype.h> 31 #include <err.h> 32 #include <errno.h> 33 #include <limits.h> 34 #include <stdarg.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 40 #include "parse.h" 41 #include "extern.h" 42 43 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); 44 static struct file { 45 TAILQ_ENTRY(file) entry; 46 FILE *stream; 47 char *name; 48 size_t ungetpos; 49 size_t ungetsize; 50 u_char *ungetbuf; 51 int eof_reached; 52 int lineno; 53 int errors; 54 } *file, *topfile; 55 struct file *pushfile(const char *); 56 int popfile(void); 57 int yyparse(void); 58 int yylex(void); 59 int yyerror(const char *, ...) 60 __attribute__((__format__ (printf, 1, 2))) 61 __attribute__((__nonnull__ (1))); 62 int kw_cmp(const void *, const void *); 63 int lookup(char *); 64 int igetc(void); 65 int lgetc(int); 66 void lungetc(int); 67 int findeol(void); 68 69 struct authority_c *conf_new_authority(struct acme_conf *, char *); 70 struct domain_c *conf_new_domain(struct acme_conf *, char *); 71 struct keyfile *conf_new_keyfile(struct acme_conf *, char *); 72 void clear_config(struct acme_conf *); 73 void print_config(struct acme_conf *); 74 int conf_check_file(char *); 75 76 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); 77 struct sym { 78 TAILQ_ENTRY(sym) entry; 79 int used; 80 int persist; 81 char *nam; 82 char *val; 83 }; 84 int symset(const char *, const char *, int); 85 char *symget(const char *); 86 87 static struct acme_conf *conf; 88 static struct authority_c *auth; 89 static struct domain_c *domain; 90 static int errors = 0; 91 92 typedef struct { 93 union { 94 int64_t number; 95 char *string; 96 } v; 97 int lineno; 98 } YYSTYPE; 99 100 %} 101 102 %token AUTHORITY URL API ACCOUNT 103 %token DOMAIN ALTERNATIVE NAMES CERT FULL CHAIN KEY SIGN WITH CHALLENGEDIR 104 %token YES NO 105 %token INCLUDE 106 %token ERROR 107 %token RSA ECDSA 108 %token <v.string> STRING 109 %token <v.number> NUMBER 110 %type <v.string> string 111 %type <v.number> keytype 112 113 %% 114 115 grammar : /* empty */ 116 | grammar include '\n' 117 | grammar varset '\n' 118 | grammar '\n' 119 | grammar authority '\n' 120 | grammar domain '\n' 121 | grammar error '\n' { file->errors++; } 122 ; 123 124 include : INCLUDE STRING { 125 struct file *nfile; 126 127 if ((nfile = pushfile($2)) == NULL) { 128 yyerror("failed to include file %s", $2); 129 free($2); 130 YYERROR; 131 } 132 free($2); 133 134 file = nfile; 135 lungetc('\n'); 136 } 137 ; 138 139 string : string STRING { 140 if (asprintf(&$$, "%s %s", $1, $2) == -1) { 141 free($1); 142 free($2); 143 yyerror("string: asprintf"); 144 YYERROR; 145 } 146 free($1); 147 free($2); 148 } 149 | STRING 150 ; 151 152 varset : STRING '=' string { 153 char *s = $1; 154 if (conf->opts & ACME_OPT_VERBOSE) 155 printf("%s = \"%s\"\n", $1, $3); 156 while (*s++) { 157 if (isspace((unsigned char)*s)) { 158 yyerror("macro name cannot contain " 159 "whitespace"); 160 free($1); 161 free($3); 162 YYERROR; 163 } 164 } 165 if (symset($1, $3, 0) == -1) 166 errx(EXIT_FAILURE, "cannot store variable"); 167 free($1); 168 free($3); 169 } 170 ; 171 172 optnl : '\n' optnl 173 | 174 ; 175 176 nl : '\n' optnl /* one newline or more */ 177 ; 178 179 comma : ',' 180 | /*empty*/ 181 ; 182 183 authority : AUTHORITY STRING { 184 char *s; 185 if ((s = strdup($2)) == NULL) 186 err(EXIT_FAILURE, "strdup"); 187 if ((auth = conf_new_authority(conf, s)) == NULL) { 188 free(s); 189 yyerror("authority already defined"); 190 YYERROR; 191 } 192 } '{' optnl authorityopts_l '}' { 193 if (auth->api == NULL) { 194 yyerror("authority %s: no api URL specified", 195 auth->name); 196 YYERROR; 197 } 198 if (auth->account == NULL) { 199 yyerror("authority %s: no account key file " 200 "specified", auth->name); 201 YYERROR; 202 } 203 auth = NULL; 204 } 205 ; 206 207 authorityopts_l : authorityopts_l authorityoptsl nl 208 | authorityoptsl optnl 209 ; 210 211 authorityoptsl : API URL STRING { 212 char *s; 213 if (auth->api != NULL) { 214 yyerror("duplicate api"); 215 YYERROR; 216 } 217 if ((s = strdup($3)) == NULL) 218 err(EXIT_FAILURE, "strdup"); 219 auth->api = s; 220 } 221 | ACCOUNT KEY STRING { 222 char *s; 223 if (auth->account != NULL) { 224 yyerror("duplicate account"); 225 YYERROR; 226 } 227 if ((s = strdup($3)) == NULL) 228 err(EXIT_FAILURE, "strdup"); 229 auth->account = s; 230 } 231 ; 232 233 domain : DOMAIN STRING { 234 char *s; 235 if ((s = strdup($2)) == NULL) 236 err(EXIT_FAILURE, "strdup"); 237 if (!domain_valid(s)) { 238 yyerror("%s: bad domain syntax", s); 239 free(s); 240 YYERROR; 241 } 242 if ((domain = conf_new_domain(conf, s)) == NULL) { 243 free(s); 244 yyerror("domain already defined"); 245 YYERROR; 246 } 247 } '{' optnl domainopts_l '}' { 248 /* enforce minimum config here */ 249 if (domain->key == NULL) { 250 yyerror("no domain key file specified for " 251 "domain %s", domain->domain); 252 YYERROR; 253 } 254 if (domain->cert == NULL && domain->fullchain == NULL) { 255 yyerror("at least certificate file or full " 256 "certificate chain file must be specified " 257 "for domain %s", domain->domain); 258 YYERROR; 259 } 260 domain = NULL; 261 } 262 ; 263 264 keytype : RSA { $$ = KT_RSA; } 265 | ECDSA { $$ = KT_ECDSA; } 266 | { $$ = KT_RSA; } 267 ; 268 269 domainopts_l : domainopts_l domainoptsl nl 270 | domainoptsl optnl 271 ; 272 273 domainoptsl : ALTERNATIVE NAMES '{' altname_l '}' 274 | DOMAIN KEY STRING keytype { 275 char *s; 276 if (domain->key != NULL) { 277 yyerror("duplicate key"); 278 YYERROR; 279 } 280 if ((s = strdup($3)) == NULL) 281 err(EXIT_FAILURE, "strdup"); 282 if (!conf_check_file(s)) { 283 free(s); 284 YYERROR; 285 } 286 if ((conf_new_keyfile(conf, s)) == NULL) { 287 free(s); 288 yyerror("domain key file already used"); 289 YYERROR; 290 } 291 domain->key = s; 292 domain->keytype = $4; 293 } 294 | DOMAIN CERT STRING { 295 char *s; 296 if (domain->cert != NULL) { 297 yyerror("duplicate cert"); 298 YYERROR; 299 } 300 if ((s = strdup($3)) == NULL) 301 err(EXIT_FAILURE, "strdup"); 302 if (s[0] != '/') { 303 free(s); 304 yyerror("not an absolute path"); 305 YYERROR; 306 } 307 if ((conf_new_keyfile(conf, s)) == NULL) { 308 free(s); 309 yyerror("domain cert file already used"); 310 YYERROR; 311 } 312 domain->cert = s; 313 } 314 | DOMAIN CHAIN CERT STRING { 315 char *s; 316 if (domain->chain != NULL) { 317 yyerror("duplicate chain"); 318 YYERROR; 319 } 320 if ((s = strdup($4)) == NULL) 321 err(EXIT_FAILURE, "strdup"); 322 if ((conf_new_keyfile(conf, s)) == NULL) { 323 free(s); 324 yyerror("domain chain file already used"); 325 YYERROR; 326 } 327 domain->chain = s; 328 } 329 | DOMAIN FULL CHAIN CERT STRING { 330 char *s; 331 if (domain->fullchain != NULL) { 332 yyerror("duplicate full chain"); 333 YYERROR; 334 } 335 if ((s = strdup($5)) == NULL) 336 err(EXIT_FAILURE, "strdup"); 337 if ((conf_new_keyfile(conf, s)) == NULL) { 338 free(s); 339 yyerror("domain full chain file already used"); 340 YYERROR; 341 } 342 domain->fullchain = s; 343 } 344 | SIGN WITH STRING { 345 char *s; 346 if (domain->auth != NULL) { 347 yyerror("duplicate sign with"); 348 YYERROR; 349 } 350 if ((s = strdup($3)) == NULL) 351 err(EXIT_FAILURE, "strdup"); 352 if (authority_find(conf, s) == NULL) { 353 yyerror("sign with: unknown authority"); 354 free(s); 355 YYERROR; 356 } 357 domain->auth = s; 358 } 359 | CHALLENGEDIR STRING { 360 char *s; 361 if (domain->challengedir != NULL) { 362 yyerror("duplicate challengedir"); 363 YYERROR; 364 } 365 if ((s = strdup($2)) == NULL) 366 err(EXIT_FAILURE, "strdup"); 367 domain->challengedir = s; 368 } 369 ; 370 371 altname_l : altname comma altname_l 372 | altname 373 ; 374 375 altname : STRING { 376 char *s; 377 struct altname_c *ac; 378 if (!domain_valid($1)) { 379 yyerror("bad domain syntax"); 380 YYERROR; 381 } 382 if ((ac = calloc(1, sizeof(struct altname_c))) == NULL) 383 err(EXIT_FAILURE, "calloc"); 384 if ((s = strdup($1)) == NULL) { 385 free(ac); 386 err(EXIT_FAILURE, "strdup"); 387 } 388 ac->domain = s; 389 TAILQ_INSERT_TAIL(&domain->altname_list, ac, entry); 390 domain->altname_count++; 391 /* 392 * XXX we could check if altname is duplicate 393 * or identical to domain->domain 394 */ 395 } 396 397 %% 398 399 struct keywords { 400 const char *k_name; 401 int k_val; 402 }; 403 404 int 405 yyerror(const char *fmt, ...) 406 { 407 va_list ap; 408 char *msg; 409 410 file->errors++; 411 va_start(ap, fmt); 412 if (vasprintf(&msg, fmt, ap) == -1) 413 err(EXIT_FAILURE, "yyerror vasprintf"); 414 va_end(ap); 415 fprintf(stderr, "%s:%d: %s\n", file->name, yylval.lineno, msg); 416 free(msg); 417 return (0); 418 } 419 420 int 421 kw_cmp(const void *k, const void *e) 422 { 423 return strcmp(k, ((const struct keywords *)e)->k_name); 424 } 425 426 int 427 lookup(char *s) 428 { 429 /* this has to be sorted always */ 430 static const struct keywords keywords[] = { 431 {"account", ACCOUNT}, 432 {"alternative", ALTERNATIVE}, 433 {"api", API}, 434 {"authority", AUTHORITY}, 435 {"certificate", CERT}, 436 {"chain", CHAIN}, 437 {"challengedir", CHALLENGEDIR}, 438 {"domain", DOMAIN}, 439 {"ecdsa", ECDSA}, 440 {"full", FULL}, 441 {"include", INCLUDE}, 442 {"key", KEY}, 443 {"names", NAMES}, 444 {"rsa", RSA}, 445 {"sign", SIGN}, 446 {"url", URL}, 447 {"with", WITH}, 448 }; 449 const struct keywords *p; 450 451 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 452 sizeof(keywords[0]), kw_cmp); 453 454 if (p != NULL) 455 return p->k_val; 456 else 457 return STRING; 458 } 459 460 #define START_EXPAND 1 461 #define DONE_EXPAND 2 462 463 static int expanding; 464 465 int 466 igetc(void) 467 { 468 int c; 469 470 while (1) { 471 if (file->ungetpos > 0) 472 c = file->ungetbuf[--file->ungetpos]; 473 else 474 c = getc(file->stream); 475 476 if (c == START_EXPAND) 477 expanding = 1; 478 else if (c == DONE_EXPAND) 479 expanding = 0; 480 else 481 break; 482 } 483 return c; 484 } 485 486 int 487 lgetc(int quotec) 488 { 489 int c, next; 490 491 if (quotec) { 492 if ((c = igetc()) == EOF) { 493 yyerror("reached end of file while parsing " 494 "quoted string"); 495 if (file == topfile || popfile() == EOF) 496 return (EOF); 497 return quotec; 498 } 499 return c; 500 } 501 502 while ((c = igetc()) == '\\') { 503 next = igetc(); 504 if (next != '\n') { 505 c = next; 506 break; 507 } 508 yylval.lineno = file->lineno; 509 file->lineno++; 510 } 511 512 if (c == EOF) { 513 /* 514 * Fake EOL when hit EOF for the first time. This gets line 515 * count right if last line in included file is syntactically 516 * invalid and has no newline. 517 */ 518 if (file->eof_reached == 0) { 519 file->eof_reached = 1; 520 return '\n'; 521 } 522 while (c == EOF) { 523 if (file == topfile || popfile() == EOF) 524 return (EOF); 525 c = igetc(); 526 } 527 } 528 return c; 529 } 530 531 void 532 lungetc(int c) 533 { 534 if (c == EOF) 535 return; 536 537 if (file->ungetpos >= file->ungetsize) { 538 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); 539 if (p == NULL) 540 err(1, "%s", __func__); 541 file->ungetbuf = p; 542 file->ungetsize *= 2; 543 } 544 file->ungetbuf[file->ungetpos++] = c; 545 } 546 547 int 548 findeol(void) 549 { 550 int c; 551 552 /* skip to either EOF or the first real EOL */ 553 while (1) { 554 c = lgetc(0); 555 if (c == '\n') { 556 file->lineno++; 557 break; 558 } 559 if (c == EOF) 560 break; 561 } 562 return ERROR; 563 } 564 565 int 566 yylex(void) 567 { 568 u_char buf[8096]; 569 u_char *p, *val; 570 int quotec, next, c; 571 int token; 572 573 top: 574 p = buf; 575 while ((c = lgetc(0)) == ' ' || c == '\t') 576 ; /* nothing */ 577 578 yylval.lineno = file->lineno; 579 if (c == '#') 580 while ((c = lgetc(0)) != '\n' && c != EOF) 581 ; /* nothing */ 582 if (c == '$' && !expanding) { 583 while (1) { 584 if ((c = lgetc(0)) == EOF) 585 return 0; 586 587 if (p + 1 >= buf + sizeof(buf) - 1) { 588 yyerror("string too long"); 589 return findeol(); 590 } 591 if (isalnum(c) || c == '_') { 592 *p++ = c; 593 continue; 594 } 595 *p = '\0'; 596 lungetc(c); 597 break; 598 } 599 val = symget(buf); 600 if (val == NULL) { 601 yyerror("macro '%s' not defined", buf); 602 return findeol(); 603 } 604 p = val + strlen(val) - 1; 605 lungetc(DONE_EXPAND); 606 while (p >= val) { 607 lungetc(*p); 608 p--; 609 } 610 lungetc(START_EXPAND); 611 goto top; 612 } 613 614 switch (c) { 615 case '\'': 616 case '"': 617 quotec = c; 618 while (1) { 619 if ((c = lgetc(quotec)) == EOF) 620 return 0; 621 if (c == '\n') { 622 file->lineno++; 623 continue; 624 } else if (c == '\\') { 625 if ((next = lgetc(quotec)) == EOF) 626 return 0; 627 if (next == quotec || next == ' ' || 628 next == '\t') 629 c = next; 630 else if (next == '\n') { 631 file->lineno++; 632 continue; 633 } else 634 lungetc(next); 635 } else if (c == quotec) { 636 *p = '\0'; 637 break; 638 } else if (c == '\0') { 639 yyerror("syntax error"); 640 return findeol(); 641 } 642 if (p + 1 >= buf + sizeof(buf) - 1) { 643 yyerror("string too long"); 644 return findeol(); 645 } 646 *p++ = c; 647 } 648 yylval.v.string = strdup(buf); 649 if (yylval.v.string == NULL) 650 err(EXIT_FAILURE, "%s", __func__); 651 return STRING; 652 } 653 654 #define allowed_to_end_number(x) \ 655 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 656 657 if (c == '-' || isdigit(c)) { 658 do { 659 *p++ = c; 660 if ((size_t)(p-buf) >= sizeof(buf)) { 661 yyerror("string too long"); 662 return findeol(); 663 } 664 } while ((c = lgetc(0)) != EOF && isdigit(c)); 665 lungetc(c); 666 if (p == buf + 1 && buf[0] == '-') 667 goto nodigits; 668 if (c == EOF || allowed_to_end_number(c)) { 669 const char *errstr = NULL; 670 671 *p = '\0'; 672 yylval.v.number = strtonum(buf, LLONG_MIN, 673 LLONG_MAX, &errstr); 674 if (errstr != NULL) { 675 yyerror("\"%s\" invalid number: %s", 676 buf, errstr); 677 return (findeol()); 678 } 679 return NUMBER; 680 } else { 681 nodigits: 682 while (p > buf + 1) 683 lungetc(*--p); 684 c = *--p; 685 if (c == '-') 686 return c; 687 } 688 } 689 690 #define allowed_in_string(x) \ 691 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 692 x != '{' && x != '}' && \ 693 x != '!' && x != '=' && x != '#' && \ 694 x != ',')) 695 696 if (isalnum(c) || c == ':' || c == '_') { 697 do { 698 *p++ = c; 699 if ((size_t)(p-buf) >= sizeof(buf)) { 700 yyerror("string too long"); 701 return (findeol()); 702 } 703 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 704 lungetc(c); 705 *p = '\0'; 706 if ((token = lookup(buf)) == STRING) { 707 if ((yylval.v.string = strdup(buf)) == NULL) 708 err(EXIT_FAILURE, "%s", __func__); 709 } 710 return token; 711 } 712 if (c == '\n') { 713 yylval.lineno = file->lineno; 714 file->lineno++; 715 } 716 if (c == EOF) 717 return 0; 718 return c; 719 } 720 721 struct file * 722 pushfile(const char *name) 723 { 724 struct file *nfile; 725 726 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 727 warn("%s", __func__); 728 return NULL; 729 } 730 if ((nfile->name = strdup(name)) == NULL) { 731 warn("%s", __func__); 732 free(nfile); 733 return NULL; 734 } 735 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 736 warn("%s: %s", __func__, nfile->name); 737 free(nfile->name); 738 free(nfile); 739 return NULL; 740 } 741 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; 742 nfile->ungetsize = 16; 743 nfile->ungetbuf = malloc(nfile->ungetsize); 744 if (nfile->ungetbuf == NULL) { 745 warn("%s", __func__); 746 fclose(nfile->stream); 747 free(nfile->name); 748 free(nfile); 749 return NULL; 750 } 751 TAILQ_INSERT_TAIL(&files, nfile, entry); 752 return nfile; 753 } 754 755 int 756 popfile(void) 757 { 758 struct file *prev; 759 760 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 761 prev->errors += file->errors; 762 763 TAILQ_REMOVE(&files, file, entry); 764 fclose(file->stream); 765 free(file->name); 766 free(file->ungetbuf); 767 free(file); 768 file = prev; 769 return (file ? 0 : EOF); 770 } 771 772 struct acme_conf * 773 parse_config(const char *filename, int opts) 774 { 775 struct sym *sym, *next; 776 777 if ((conf = calloc(1, sizeof(struct acme_conf))) == NULL) 778 err(EXIT_FAILURE, "%s", __func__); 779 conf->opts = opts; 780 781 if ((file = pushfile(filename)) == NULL) { 782 free(conf); 783 return NULL; 784 } 785 topfile = file; 786 787 TAILQ_INIT(&conf->authority_list); 788 TAILQ_INIT(&conf->domain_list); 789 790 yyparse(); 791 errors = file->errors; 792 popfile(); 793 794 /* Free macros and check which have not been used. */ 795 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { 796 if ((conf->opts & ACME_OPT_VERBOSE) && !sym->used) 797 fprintf(stderr, "warning: macro '%s' not " 798 "used\n", sym->nam); 799 if (!sym->persist) { 800 free(sym->nam); 801 free(sym->val); 802 TAILQ_REMOVE(&symhead, sym, entry); 803 free(sym); 804 } 805 } 806 807 if (errors != 0) { 808 clear_config(conf); 809 return NULL; 810 } 811 812 if (opts & ACME_OPT_CHECK) 813 print_config(conf); 814 815 return conf; 816 } 817 818 int 819 symset(const char *nam, const char *val, int persist) 820 { 821 struct sym *sym; 822 823 TAILQ_FOREACH(sym, &symhead, entry) { 824 if (strcmp(nam, sym->nam) == 0) 825 break; 826 } 827 828 if (sym != NULL) { 829 if (sym->persist == 1) 830 return (0); 831 else { 832 free(sym->nam); 833 free(sym->val); 834 TAILQ_REMOVE(&symhead, sym, entry); 835 free(sym); 836 } 837 } 838 if ((sym = calloc(1, sizeof(*sym))) == NULL) 839 return -1; 840 841 sym->nam = strdup(nam); 842 if (sym->nam == NULL) { 843 free(sym); 844 return -1; 845 } 846 sym->val = strdup(val); 847 if (sym->val == NULL) { 848 free(sym->nam); 849 free(sym); 850 return -1; 851 } 852 sym->used = 0; 853 sym->persist = persist; 854 TAILQ_INSERT_TAIL(&symhead, sym, entry); 855 return 0; 856 } 857 858 int 859 cmdline_symset(char *s) 860 { 861 char *sym, *val; 862 int ret; 863 864 if ((val = strrchr(s, '=')) == NULL) 865 return -1; 866 sym = strndup(s, val - s); 867 if (sym == NULL) 868 errx(EXIT_FAILURE, "%s: strndup", __func__); 869 ret = symset(sym, val + 1, 1); 870 free(sym); 871 872 return ret; 873 } 874 875 char * 876 symget(const char *nam) 877 { 878 struct sym *sym; 879 880 TAILQ_FOREACH(sym, &symhead, entry) { 881 if (strcmp(nam, sym->nam) == 0) { 882 sym->used = 1; 883 return sym->val; 884 } 885 } 886 return NULL; 887 } 888 889 struct authority_c * 890 conf_new_authority(struct acme_conf *c, char *s) 891 { 892 struct authority_c *a; 893 894 a = authority_find(c, s); 895 if (a != NULL) 896 return NULL; 897 if ((a = calloc(1, sizeof(struct authority_c))) == NULL) 898 err(EXIT_FAILURE, "%s", __func__); 899 TAILQ_INSERT_TAIL(&c->authority_list, a, entry); 900 901 a->name = s; 902 return a; 903 } 904 905 struct authority_c * 906 authority_find(struct acme_conf *c, char *s) 907 { 908 struct authority_c *a; 909 910 TAILQ_FOREACH(a, &c->authority_list, entry) { 911 if (strncmp(a->name, s, AUTH_MAXLEN) == 0) { 912 return a; 913 } 914 } 915 return NULL; 916 } 917 918 struct authority_c * 919 authority_find0(struct acme_conf *c) 920 { 921 return (TAILQ_FIRST(&c->authority_list)); 922 } 923 924 struct domain_c * 925 conf_new_domain(struct acme_conf *c, char *s) 926 { 927 struct domain_c *d; 928 929 d = domain_find(c, s); 930 if (d != NULL) 931 return (NULL); 932 if ((d = calloc(1, sizeof(struct domain_c))) == NULL) 933 err(EXIT_FAILURE, "%s", __func__); 934 TAILQ_INSERT_TAIL(&c->domain_list, d, entry); 935 936 d->domain = s; 937 TAILQ_INIT(&d->altname_list); 938 939 return d; 940 } 941 942 struct domain_c * 943 domain_find(struct acme_conf *c, char *s) 944 { 945 struct domain_c *d; 946 947 TAILQ_FOREACH(d, &c->domain_list, entry) { 948 if (strncmp(d->domain, s, DOMAIN_MAXLEN) == 0) { 949 return d; 950 } 951 } 952 return NULL; 953 } 954 955 struct keyfile * 956 conf_new_keyfile(struct acme_conf *c, char *s) 957 { 958 struct keyfile *k; 959 960 LIST_FOREACH(k, &c->used_key_list, entry) { 961 if (strncmp(k->name, s, PATH_MAX) == 0) { 962 return NULL; 963 } 964 } 965 966 if ((k = calloc(1, sizeof(struct keyfile))) == NULL) 967 err(EXIT_FAILURE, "%s", __func__); 968 LIST_INSERT_HEAD(&c->used_key_list, k, entry); 969 970 k->name = s; 971 return k; 972 } 973 974 void 975 clear_config(struct acme_conf *xconf) 976 { 977 struct authority_c *a; 978 struct domain_c *d; 979 struct altname_c *ac; 980 981 while ((a = TAILQ_FIRST(&xconf->authority_list)) != NULL) { 982 TAILQ_REMOVE(&xconf->authority_list, a, entry); 983 free(a); 984 } 985 while ((d = TAILQ_FIRST(&xconf->domain_list)) != NULL) { 986 while ((ac = TAILQ_FIRST(&d->altname_list)) != NULL) { 987 TAILQ_REMOVE(&d->altname_list, ac, entry); 988 free(ac); 989 } 990 TAILQ_REMOVE(&xconf->domain_list, d, entry); 991 free(d); 992 } 993 free(xconf); 994 } 995 996 void 997 print_config(struct acme_conf *xconf) 998 { 999 struct authority_c *a; 1000 struct domain_c *d; 1001 struct altname_c *ac; 1002 int f; 1003 1004 TAILQ_FOREACH(a, &xconf->authority_list, entry) { 1005 printf("authority %s {\n", a->name); 1006 if (a->api != NULL) 1007 printf("\tapi url \"%s\"\n", a->api); 1008 if (a->account != NULL) 1009 printf("\taccount key \"%s\"\n", a->account); 1010 printf("}\n\n"); 1011 } 1012 TAILQ_FOREACH(d, &xconf->domain_list, entry) { 1013 f = 0; 1014 printf("domain %s {\n", d->domain); 1015 TAILQ_FOREACH(ac, &d->altname_list, entry) { 1016 if (!f) 1017 printf("\talternative names {"); 1018 if (ac->domain != NULL) { 1019 printf("%s%s", f ? ", " : " ", ac->domain); 1020 f = 1; 1021 } 1022 } 1023 if (f) 1024 printf(" }\n"); 1025 if (d->key != NULL) 1026 printf("\tdomain key \"%s\"\n", d->key); 1027 if (d->cert != NULL) 1028 printf("\tdomain certificate \"%s\"\n", d->cert); 1029 if (d->chain != NULL) 1030 printf("\tdomain chain certificate \"%s\"\n", d->chain); 1031 if (d->fullchain != NULL) 1032 printf("\tdomain full chain certificate \"%s\"\n", 1033 d->fullchain); 1034 if (d->auth != NULL) 1035 printf("\tsign with \"%s\"\n", d->auth); 1036 if (d->challengedir != NULL) 1037 printf("\tchallengedir \"%s\"\n", d->challengedir); 1038 printf("}\n\n"); 1039 } 1040 } 1041 1042 /* 1043 * This isn't RFC1035 compliant, but does the bare minimum in making 1044 * sure that we don't get bogus domain names on the command line, which 1045 * might otherwise screw up our directory structure. 1046 * Returns zero on failure, non-zero on success. 1047 */ 1048 int 1049 domain_valid(const char *cp) 1050 { 1051 1052 for ( ; *cp != '\0'; cp++) 1053 if (!(*cp == '.' || *cp == '-' || 1054 *cp == '_' || isalnum((int)*cp))) 1055 return 0; 1056 return 1; 1057 } 1058 1059 int 1060 conf_check_file(char *s) 1061 { 1062 struct stat st; 1063 1064 if (s[0] != '/') { 1065 warnx("%s: not an absolute path", s); 1066 return 0; 1067 } 1068 if (stat(s, &st)) { 1069 if (errno == ENOENT) 1070 return 1; 1071 warn("cannot stat %s", s); 1072 return 0; 1073 } 1074 if (st.st_mode & (S_IRWXG | S_IRWXO)) { 1075 warnx("%s: group read/writable or world read/writable", s); 1076 return 0; 1077 } 1078 return 1; 1079 } 1080