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