Annotation of src/usr.bin/cvs/conf.y, Revision 1.1
1.1 ! jfb 1: /* $OpenBSD$ */
! 2: /*
! 3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
! 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: *
! 10: * 1. Redistributions of source code must retain the above copyright
! 11: * notice, this list of conditions and the following disclaimer.
! 12: * 2. The name of the author may not be used to endorse or promote products
! 13: * derived from this software without specific prior written permission.
! 14: *
! 15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
! 16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
! 17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
! 18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
! 19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
! 20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
! 21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
! 22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
! 23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
! 24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 25: *
! 26: */
! 27: %{
! 28: /*
! 29: * Configuration parser for the CVS daemon
! 30: */
! 31: #include <sys/types.h>
! 32: #include <sys/queue.h>
! 33:
! 34: #include <errno.h>
! 35: #include <stdio.h>
! 36: #include <stdlib.h>
! 37: #include <stdarg.h>
! 38: #include <unistd.h>
! 39: #include <string.h>
! 40:
! 41: #include "cvsd.h"
! 42: #include "cvs.h"
! 43: #include "log.h"
! 44: #include "event.h"
! 45:
! 46: #define CVS_ACL_MAXRULES 256
! 47:
! 48: #define CVS_ACL_DENY 0
! 49: #define CVS_ACL_ALLOW 1
! 50:
! 51: #define CVS_ACL_LOGOPT 0x01
! 52: #define CVS_ACL_QUICKOPT 0x02
! 53:
! 54:
! 55: struct conf_macro {
! 56: char *cm_name;
! 57: char *cm_val;
! 58:
! 59: SIMPLEQ_ENTRY(conf_macro) cm_list;
! 60: };
! 61:
! 62:
! 63: struct acl_user {
! 64: uid_t au_uid;
! 65: SLIST_ENTRY(acl_user) au_list;
! 66: };
! 67:
! 68:
! 69: struct acl_rule {
! 70: u_int8_t ar_id;
! 71: u_int8_t ar_act;
! 72: u_int8_t ar_opts;
! 73: u_int8_t ar_op;
! 74: char *ar_path;
! 75: char *ar_tag;
! 76:
! 77: SLIST_HEAD(, acl_user) ar_users;
! 78: TAILQ_ENTRY(acl_rule) ar_list;
! 79: };
! 80:
! 81:
! 82:
! 83:
! 84:
! 85: typedef struct {
! 86: union {
! 87: u_int64_t num;
! 88: char *string;
! 89: struct acl_rule *rule;
! 90: struct acl_user *user_list;
! 91: struct cvsd_addr *addr;
! 92: } v;
! 93:
! 94: int lineno;
! 95: } YYSTYPE;
! 96:
! 97:
! 98:
! 99:
! 100: int lgetc (FILE *);
! 101:
! 102:
! 103: int yyerror (const char *, ...);
! 104: int yylex (void);
! 105: int yyparse (void);
! 106: int lookup (const char *);
! 107: int kw_cmp (const void *, const void *);
! 108:
! 109: int cvs_conf_setmacro (char *, char *);
! 110: const char* cvs_conf_getmacro (const char *);
! 111:
! 112: int cvs_acl_addrule (struct acl_rule *);
! 113: u_int cvs_acl_matchuid (struct acl_rule *, uid_t);
! 114: u_int cvs_acl_matchtag (const char *, const char *);
! 115: u_int cvs_acl_matchpath (const char *, const char *);
! 116:
! 117:
! 118: static const char *conf_file;
! 119: static FILE *conf_fin;
! 120: static int conf_lineno = 1;
! 121: static int conf_errors = 0;
! 122:
! 123: static SIMPLEQ_HEAD(, conf_macro) conf_macros;
! 124:
! 125: /* ACL rules */
! 126: static TAILQ_HEAD(, acl_rule) acl_rules;
! 127: static u_int acl_nrules = 0;
! 128: static u_int acl_defact = CVS_ACL_DENY;
! 129:
! 130: %}
! 131:
! 132: %token LISTEN CVSROOT MINCHILD MAXCHILD REQSOCK
! 133: %token ALLOW DENY LOG QUICK ON TAG FROM
! 134: %token ANY ADD CHECKOUT COMMIT DIFF HISTORY UPDATE
! 135: %token <v.string> STRING
! 136: %type <v.num> action number options operation
! 137: %type <v.rule> aclrule
! 138: %type <v.addr> address
! 139: %type <v.userlist>
! 140: %type <v.string> pathspec tagspec
! 141: %%
! 142:
! 143: conf : /* empty */
! 144: | conf '\n'
! 145: | conf cfline '\n'
! 146: | conf error '\n' { conf_errors++; }
! 147: ;
! 148:
! 149: cfline : macro_assign
! 150: | directive
! 151: | aclrule
! 152: ;
! 153:
! 154: macro_assign : STRING '=' STRING
! 155: {
! 156: if (cvs_conf_setmacro($1, $3) < 0) {
! 157: free($1);
! 158: free($3);
! 159: YYERROR;
! 160: }
! 161: }
! 162: ;
! 163:
! 164: directive : LISTEN address { cvsd_set(CVSD_SET_ADDR, $2); }
! 165: | CVSROOT STRING
! 166: {
! 167: cvsd_set(CVSD_SET_ROOT, $2);
! 168: free($2);
! 169: }
! 170: | MINCHILD number { cvsd_set(CVSD_SET_CHMIN, $2); }
! 171: | MAXCHILD number { cvsd_set(CVSD_SET_CHMAX, $2); }
! 172: | REQSOCK STRING
! 173: {
! 174: cvsd_set(CVSD_SET_SOCK, $2);
! 175: free($2);
! 176: }
! 177: ;
! 178:
! 179: address : STRING
! 180: {
! 181: struct cvsd_addr *adp;
! 182:
! 183: adp = (struct cvsd_addr *)malloc(sizeof(*adp));
! 184: if (adp == NULL) {
! 185: YYERROR;
! 186: }
! 187:
! 188: $$ = adp;
! 189: }
! 190: ;
! 191:
! 192: aclrule : action options operation pathspec tagspec userspec
! 193: {
! 194: struct acl_rule *arp;
! 195:
! 196: arp = (struct acl_rule *)malloc(sizeof(*arp));
! 197: if (arp == NULL) {
! 198: free($4);
! 199: free($5);
! 200: YYERROR;
! 201: }
! 202: arp->ar_act = $1;
! 203: arp->ar_opts = $2;
! 204: arp->ar_op = $3;
! 205: SLIST_INIT(&arp->ar_users);
! 206: arp->ar_path = $4;
! 207: arp->ar_tag = $5;
! 208:
! 209: $$ = arp;
! 210: }
! 211: ;
! 212:
! 213: action : ALLOW { $$ = CVS_ACL_ALLOW; }
! 214: | DENY { $$ = CVS_ACL_DENY; }
! 215: ;
! 216:
! 217: options : /* empty */ { $$ = 0; }
! 218: | LOG { $$ = CVS_ACL_LOGOPT; }
! 219: | QUICK { $$ = CVS_ACL_QUICKOPT; }
! 220: | LOG QUICK { $$ = CVS_ACL_LOGOPT | CVS_ACL_QUICKOPT; }
! 221: ;
! 222:
! 223: operation : ADD { $$ = CVS_OP_ADD; }
! 224: | ANY { $$ = CVS_OP_ANY; }
! 225: | COMMIT { $$ = CVS_OP_COMMIT; }
! 226: | TAG { $$ = CVS_OP_TAG; }
! 227: ;
! 228:
! 229: pathspec : /* empty */ { $$ = NULL; }
! 230: | ON STRING { $$ = $2; }
! 231: ;
! 232:
! 233: tagspec : /* empty */ { $$ = NULL; }
! 234: | TAG STRING { $$ = $2; }
! 235: ;
! 236:
! 237: userspec : /* empty */
! 238: | FROM userlist
! 239: ;
! 240:
! 241: userlist : user
! 242: | userlist ',' user
! 243: ;
! 244:
! 245: user : STRING
! 246: {
! 247: uid_t uid;
! 248: char *ep;
! 249: struct passwd *pw;
! 250: struct acl_user *aup;
! 251:
! 252: uid = (uid_t)strtol($1, &ep, 10);
! 253: if (*ep != '\0')
! 254: pw = getpwnam($1);
! 255: else
! 256: pw = getpwuid(uid);
! 257: if (pw == NULL) {
! 258: yyerror("invalid username or ID `%s'", $1);
! 259: YYERROR;
! 260: }
! 261:
! 262: aup = (struct acl_user *)malloc(sizeof(*aup));
! 263: if (aup == NULL) {
! 264: yyerror("failed to allocate ACL user data");
! 265: YYERROR;
! 266: }
! 267: aup->au_uid = pw->pw_uid;
! 268: }
! 269: ;
! 270:
! 271: number : STRING
! 272: {
! 273: char *ep;
! 274: long res;
! 275: res = strtol($1, &ep, 0);
! 276: if ((res == LONG_MIN) || (res == LONG_MAX)) {
! 277: yyerror("%sflow while converting number `%s'",
! 278: res == LONG_MIN ? "under" : "over", $1);
! 279: free($1);
! 280: YYERROR;
! 281: }
! 282: else if (*ep != '\0') {
! 283: yyerror("invalid number `%s'", $1);
! 284: free($1);
! 285: YYERROR;
! 286: }
! 287:
! 288: $$ = (u_int64_t)res;
! 289: free($1);
! 290: }
! 291: ;
! 292:
! 293: %%
! 294:
! 295:
! 296: struct conf_kw {
! 297: char *kw_str;
! 298: u_int kw_id;
! 299: };
! 300:
! 301:
! 302:
! 303: static const struct conf_kw keywords[] = {
! 304: { "add", ADD },
! 305: { "allow", ALLOW },
! 306: { "any", ANY },
! 307: { "commit", COMMIT },
! 308: { "cvsroot", CVSROOT },
! 309: { "deny", DENY },
! 310: { "from", FROM },
! 311: { "listen", LISTEN },
! 312: { "log", LOG },
! 313: { "on", ON },
! 314: { "quick", QUICK },
! 315: { "reqsock", REQSOCK },
! 316: { "tag", TAG },
! 317:
! 318: };
! 319:
! 320: int
! 321: kw_cmp(const void *k, const void *e)
! 322: {
! 323: return (strcmp(k, ((const struct conf_kw *)e)->kw_str));
! 324: }
! 325:
! 326:
! 327: int
! 328: lookup(const char *tstr)
! 329: {
! 330: int type;
! 331: const struct conf_kw *kwp;
! 332:
! 333: kwp = bsearch(tstr, keywords, sizeof(keywords)/sizeof(keywords[0]),
! 334: sizeof(keywords[0]), kw_cmp);
! 335: if (kwp != NULL)
! 336: type = kwp->kw_id;
! 337: else
! 338: type = STRING;
! 339: return (type);
! 340: }
! 341:
! 342:
! 343:
! 344: int
! 345: lgetc(FILE *f)
! 346: {
! 347: int c;
! 348:
! 349: c = getc(f);
! 350: if ((c == '\t') || (c == ' ')) {
! 351: do {
! 352: c = getc(f);
! 353: } while ((c == ' ') || (c == '\t'));
! 354: ungetc(c, f);
! 355: c = ' ';
! 356: }
! 357: else if (c == '\\')
! 358: c = getc(f);
! 359:
! 360: return (c);
! 361: }
! 362:
! 363:
! 364: int
! 365: yylex(void)
! 366: {
! 367: int c;
! 368: char buf[1024], *bp, *ep;
! 369:
! 370: bp = buf;
! 371: ep = buf + sizeof(buf) - 1;
! 372:
! 373: yylval.lineno = conf_lineno;
! 374:
! 375: /* skip whitespace */
! 376: while ((c = lgetc(conf_fin)) == ' ')
! 377: ;
! 378:
! 379: if (c == '#') {
! 380: do {
! 381: c = lgetc(conf_fin);
! 382: } while ((c != '\n') && (c != EOF));
! 383: }
! 384: else if (c == EOF)
! 385: c = 0;
! 386: else if (c == '\n')
! 387: conf_lineno++;
! 388: else if (c != ',') {
! 389: do {
! 390: *bp++ = c;
! 391: if (bp == ep) {
! 392: yyerror("string too long");
! 393: return (-1);
! 394: }
! 395:
! 396: c = lgetc(conf_fin);
! 397: if (c == EOF)
! 398: break;
! 399: } while ((c != EOF) && (c != ' ') && (c != '\n'));
! 400: ungetc(c, conf_fin);
! 401: *bp = '\0';
! 402: c = lookup(buf);
! 403: if (c == STRING) {
! 404: yylval.v.string = strdup(buf);
! 405: if (yylval.v.string == NULL) {
! 406: cvs_log(LP_ERRNO,
! 407: "failed to copy token string");
! 408: return (-1);
! 409: }
! 410: }
! 411: }
! 412:
! 413: return (c);
! 414: }
! 415:
! 416:
! 417:
! 418: int
! 419: yyerror(const char *fmt, ...)
! 420: {
! 421: char *nfmt;
! 422: va_list vap;
! 423:
! 424: va_start(vap, fmt);
! 425:
! 426: if (asprintf(&nfmt, "%s:%d: %s", conf_file, yylval.lineno, fmt) == -1) {
! 427: cvs_log(LP_ERRNO, "failed to allocate message buffer");
! 428: return (-1);
! 429: }
! 430: cvs_vlog(LP_ERR, nfmt, vap);
! 431:
! 432: free(nfmt);
! 433: va_end(vap);
! 434: return (0);
! 435:
! 436: }
! 437:
! 438:
! 439: /*
! 440: * cvs_conf_setmacro()
! 441: *
! 442: * Add an entry in the macro list for the macro whose name is <macro> and
! 443: * whose value is <val>.
! 444: * Returns 0 on success, or -1 on failure.
! 445: */
! 446:
! 447: int
! 448: cvs_conf_setmacro(char *macro, char *val)
! 449: {
! 450: struct conf_macro *cmp;
! 451:
! 452: cmp = (struct conf_macro *)malloc(sizeof(*cmp));
! 453: if (cmp == NULL) {
! 454: cvs_log(LP_ERRNO, "failed to allocate macro");
! 455: return (-1);
! 456: }
! 457:
! 458: /* these strings were already dup'ed by the lexer */
! 459: printf("macro(%s) = `%s'\n", macro, val);
! 460: cmp->cm_name = macro;
! 461: cmp->cm_val = val;
! 462:
! 463: SIMPLEQ_INSERT_TAIL(&conf_macros, cmp, cm_list);
! 464:
! 465: return (0);
! 466: }
! 467:
! 468:
! 469: /*
! 470: * cvs_conf_getmacro()
! 471: *
! 472: * Get a macro <macro>'s associated value. Returns the value string on
! 473: * success, or NULL if no such macro exists.
! 474: */
! 475:
! 476: const char*
! 477: cvs_conf_getmacro(const char *macro)
! 478: {
! 479: struct conf_macro *cmp;
! 480:
! 481: SIMPLEQ_FOREACH(cmp, &conf_macros, cm_list)
! 482: if (strcmp(cmp->cm_name, macro) == 0)
! 483: return (cmp->cm_val);
! 484:
! 485: return (NULL);
! 486: }
! 487:
! 488:
! 489: /*
! 490: * cvs_conf_read()
! 491: *
! 492: * Parse the contents of the configuration file <conf>.
! 493: */
! 494:
! 495: int
! 496: cvs_conf_read(const char *conf)
! 497: {
! 498: struct conf_macro *cmp;
! 499:
! 500: SIMPLEQ_INIT(&conf_macros);
! 501: TAILQ_INIT(&acl_rules);
! 502: acl_nrules = 0;
! 503:
! 504: conf_file = conf;
! 505: conf_fin = fopen(conf, "r");
! 506: if (conf_fin == NULL) {
! 507: cvs_log(LP_ERRNO, "failed to open configuration `%s'", conf);
! 508: return (-1);
! 509: }
! 510:
! 511: if (yyparse() != 0)
! 512: conf_lineno = -1;
! 513:
! 514: (void)fclose(conf_fin);
! 515:
! 516: /* we can get rid of macros now */
! 517: while ((cmp = SIMPLEQ_FIRST(&conf_macros)) != NULL) {
! 518: SIMPLEQ_REMOVE_HEAD(&conf_macros, cm_list);
! 519: free(cmp->cm_name);
! 520: free(cmp->cm_val);
! 521: free(cmp);
! 522: }
! 523:
! 524: cvs_log(LP_INFO, "config %s parsed successfully", conf);
! 525:
! 526: return (conf_lineno);
! 527: }
! 528:
! 529:
! 530: /*
! 531: * cvs_acl_addrule()
! 532: *
! 533: * Add a rule to the currently loaded ACL rules.
! 534: */
! 535:
! 536: int
! 537: cvs_acl_addrule(struct acl_rule *rule)
! 538: {
! 539: if (acl_nrules == CVS_ACL_MAXRULES) {
! 540: cvs_log(LP_ERR, "failed to add ACL rule: Ruleset full");
! 541: return (-1);
! 542: }
! 543:
! 544: TAILQ_INSERT_TAIL(&acl_rules, rule, ar_list);
! 545: return (0);
! 546: }
! 547:
! 548:
! 549: /*
! 550: * cvs_acl_eval()
! 551: *
! 552: * Evaluate a thingamajimmie against the currently loaded ACL ruleset.
! 553: * Returns CVS_ACL_ALLOW if the operation is permitted, CVS_ACL_DENY otherwise.
! 554: */
! 555:
! 556: u_int
! 557: cvs_acl_eval(struct cvs_op *op)
! 558: {
! 559: u_int res;
! 560: struct acl_rule *arp;
! 561:
! 562: /* deny by default */
! 563: res = acl_defact;
! 564:
! 565: TAILQ_FOREACH(arp, &acl_rules, ar_list) {
! 566: if (((op->co_op != CVS_OP_ANY) && (op->co_op != arp->ar_op)) ||
! 567: !cvs_acl_matchuid(arp, op->co_uid) ||
! 568: !cvs_acl_matchtag(op->co_tag, arp->ar_tag) ||
! 569: !cvs_acl_matchpath(op->co_path, arp->ar_path))
! 570: continue;
! 571:
! 572: res = arp->ar_act;
! 573:
! 574: if (arp->ar_opts & CVS_ACL_LOGOPT)
! 575: cvs_log(LP_WARN, "act=%u, path=%s, tag=%s, uid=%u",
! 576: op->co_op, op->co_path, op->co_tag, op->co_uid);
! 577: if (arp->ar_opts & CVS_ACL_QUICKOPT)
! 578: break;
! 579: }
! 580:
! 581: return (res);
! 582: }
! 583:
! 584:
! 585: /*
! 586: * cvs_acl_matchuid()
! 587: *
! 588: * Check if an ACL rule has a UID matching <uid>. If no user is specified
! 589: * for a given rule, any UID will match.
! 590: * Returns 1 if this is the case, 0 otherwise.
! 591: */
! 592:
! 593: u_int
! 594: cvs_acl_matchuid(struct acl_rule *rule, uid_t uid)
! 595: {
! 596: struct acl_user *aup;
! 597:
! 598: if (SLIST_EMPTY(&(rule->ar_users)))
! 599: return (1);
! 600:
! 601: SLIST_FOREACH(aup, &(rule->ar_users), au_list)
! 602: if (aup->au_uid == uid)
! 603: return (1);
! 604: return (0);
! 605: }
! 606:
! 607:
! 608: /*
! 609: * cvs_acl_matchtag()
! 610: *
! 611: * Returns 1 if this is the case, 0 otherwise.
! 612: */
! 613:
! 614: u_int
! 615: cvs_acl_matchtag(const char *tag1, const char *tag2)
! 616: {
! 617: if ((tag1 == NULL) && (tag2 == NULL)) /* HEAD */
! 618: return (1);
! 619:
! 620: if ((tag1 != NULL) && (tag2 != NULL) &&
! 621: (strcmp(tag1, tag2) == 0))
! 622: return (1);
! 623:
! 624: return (0);
! 625: }
! 626:
! 627:
! 628: /*
! 629: * cvs_acl_matchpath()
! 630: *
! 631: * Check if the path <op_path> is a subpath of <acl_path>.
! 632: * Returns 1 if this is the case, 0 otherwise.
! 633: */
! 634:
! 635: u_int
! 636: cvs_acl_matchpath(const char *op_path, const char *acl_path)
! 637: {
! 638: size_t len;
! 639: char rop_path[MAXPATHLEN];
! 640:
! 641: /* if the ACL path is NULL, apply on all paths */
! 642: if (acl_path == NULL)
! 643: return (1);
! 644:
! 645: if (realpath(op_path, rop_path) == NULL) {
! 646: cvs_log(LP_ERRNO, "failed to convert `%s' to a real path",
! 647: op_path);
! 648: return (0);
! 649: }
! 650:
! 651: printf("comparing `%s' to `%s'\n", rop_path, acl_path);
! 652: len = strlen(rop_path);
! 653:
! 654: if (strncmp(rop_path, acl_path, len) == 0)
! 655: return (1);
! 656:
! 657: return (0);
! 658: }