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