Annotation of src/usr.bin/cvs/conf.y, Revision 1.6
1.6 ! xsa 1: /* $OpenBSD: conf.y,v 1.5 2004/11/28 15:12:17 pat Exp $ */
1.1 jfb 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
1.3 jfb 146: %token ALLOW DENY LOG QUICK ON TAG FROM USER GROUP
1.1 jfb 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: }
1.3 jfb 187: | USER STRING
188: {
189: cvsd_set(CVSD_SET_USER, $2);
190: free($2);
191: }
192: | GROUP STRING
193: {
194: cvsd_set(CVSD_SET_GROUP, $2);
195: free($2);
196: }
1.1 jfb 197: | MINCHILD number { cvsd_set(CVSD_SET_CHMIN, $2); }
198: | MAXCHILD number { cvsd_set(CVSD_SET_CHMAX, $2); }
199: | REQSOCK STRING
200: {
201: cvsd_set(CVSD_SET_SOCK, $2);
202: free($2);
203: }
204: ;
205:
206: address : STRING
207: {
208: struct cvsd_addr *adp;
209:
210: adp = (struct cvsd_addr *)malloc(sizeof(*adp));
211: if (adp == NULL) {
212: YYERROR;
213: }
214:
215: $$ = adp;
216: }
217: ;
218:
219: aclrule : action options operation pathspec tagspec userspec
220: {
221: struct acl_rule *arp;
222:
223: arp = (struct acl_rule *)malloc(sizeof(*arp));
224: if (arp == NULL) {
225: free($4);
226: free($5);
227: YYERROR;
228: }
229: arp->ar_act = $1;
230: arp->ar_opts = $2;
231: arp->ar_op = $3;
232: SLIST_INIT(&arp->ar_users);
233: arp->ar_path = $4;
234: arp->ar_tag = $5;
235:
236: $$ = arp;
237: }
238: ;
239:
240: action : ALLOW { $$ = CVS_ACL_ALLOW; }
241: | DENY { $$ = CVS_ACL_DENY; }
242: ;
243:
244: options : /* empty */ { $$ = 0; }
245: | LOG { $$ = CVS_ACL_LOGOPT; }
246: | QUICK { $$ = CVS_ACL_QUICKOPT; }
247: | LOG QUICK { $$ = CVS_ACL_LOGOPT | CVS_ACL_QUICKOPT; }
248: ;
249:
250: operation : ADD { $$ = CVS_OP_ADD; }
251: | ANY { $$ = CVS_OP_ANY; }
252: | COMMIT { $$ = CVS_OP_COMMIT; }
253: | TAG { $$ = CVS_OP_TAG; }
254: ;
255:
256: pathspec : /* empty */ { $$ = NULL; }
257: | ON STRING { $$ = $2; }
258: ;
259:
260: tagspec : /* empty */ { $$ = NULL; }
261: | TAG STRING { $$ = $2; }
262: ;
263:
264: userspec : /* empty */
265: | FROM userlist
266: ;
267:
268: userlist : user
269: | userlist ',' user
270: ;
271:
272: user : STRING
273: {
274: uid_t uid;
275: char *ep;
276: struct passwd *pw;
277: struct acl_user *aup;
278:
279: uid = (uid_t)strtol($1, &ep, 10);
280: if (*ep != '\0')
281: pw = getpwnam($1);
282: else
283: pw = getpwuid(uid);
284: if (pw == NULL) {
285: yyerror("invalid username or ID `%s'", $1);
286: YYERROR;
287: }
288:
289: aup = (struct acl_user *)malloc(sizeof(*aup));
290: if (aup == NULL) {
291: yyerror("failed to allocate ACL user data");
292: YYERROR;
293: }
294: aup->au_uid = pw->pw_uid;
295: }
296: ;
297:
298: number : STRING
299: {
300: char *ep;
301: long res;
302: res = strtol($1, &ep, 0);
303: if ((res == LONG_MIN) || (res == LONG_MAX)) {
304: yyerror("%sflow while converting number `%s'",
305: res == LONG_MIN ? "under" : "over", $1);
306: free($1);
307: YYERROR;
308: }
309: else if (*ep != '\0') {
310: yyerror("invalid number `%s'", $1);
311: free($1);
312: YYERROR;
313: }
314:
315: $$ = (u_int64_t)res;
316: free($1);
317: }
318: ;
319:
320: %%
321:
322:
323: struct conf_kw {
324: char *kw_str;
325: u_int kw_id;
326: };
327:
328:
329:
330: static const struct conf_kw keywords[] = {
331: { "add", ADD },
332: { "allow", ALLOW },
333: { "any", ANY },
334: { "commit", COMMIT },
335: { "cvsroot", CVSROOT },
336: { "deny", DENY },
337: { "from", FROM },
1.3 jfb 338: { "group", GROUP },
1.1 jfb 339: { "listen", LISTEN },
340: { "log", LOG },
341: { "on", ON },
342: { "quick", QUICK },
343: { "reqsock", REQSOCK },
344: { "tag", TAG },
1.3 jfb 345: { "user", USER },
1.1 jfb 346:
347: };
348:
349: int
350: kw_cmp(const void *k, const void *e)
351: {
352: return (strcmp(k, ((const struct conf_kw *)e)->kw_str));
353: }
354:
355:
356: int
357: lookup(const char *tstr)
358: {
359: int type;
360: const struct conf_kw *kwp;
361:
362: kwp = bsearch(tstr, keywords, sizeof(keywords)/sizeof(keywords[0]),
363: sizeof(keywords[0]), kw_cmp);
364: if (kwp != NULL)
365: type = kwp->kw_id;
366: else
367: type = STRING;
368: return (type);
369: }
370:
371:
372:
373: int
374: lgetc(FILE *f)
375: {
376: int c;
377:
1.2 jfb 378: /* check if we've got something in the parse buffer first */
379: if (conf_pbuf != NULL) {
380: c = conf_pbuf[conf_pbind++];
381: if (c != '\0')
382: return (c);
383:
384: free(conf_pbuf);
385: conf_pbuf = NULL;
386: conf_pbind = 0;
387: }
388:
1.1 jfb 389: c = getc(f);
390: if ((c == '\t') || (c == ' ')) {
391: do {
392: c = getc(f);
393: } while ((c == ' ') || (c == '\t'));
1.2 jfb 394: lungetc(c, f);
1.1 jfb 395: c = ' ';
396: }
397: else if (c == '\\')
398: c = getc(f);
399:
400: return (c);
401: }
402:
403:
404: int
1.2 jfb 405: lungetc(int c, FILE *f)
406: {
407: if ((conf_pbuf != NULL) && (conf_pbind > 0)) {
408: conf_pbind--;
409: return (0);
410: }
411:
412: return ungetc(c, f);
413:
414:
415: }
416:
417:
418:
419:
420:
421: int
1.1 jfb 422: yylex(void)
423: {
424: int c;
425: char buf[1024], *bp, *ep;
1.2 jfb 426: const char *mval;
1.1 jfb 427:
1.2 jfb 428: lex_start:
1.1 jfb 429: bp = buf;
430: ep = buf + sizeof(buf) - 1;
431:
432: yylval.lineno = conf_lineno;
433:
434: /* skip whitespace */
435: while ((c = lgetc(conf_fin)) == ' ')
436: ;
437:
438: if (c == '#') {
439: do {
440: c = lgetc(conf_fin);
441: } while ((c != '\n') && (c != EOF));
442: }
1.2 jfb 443:
444: if (c == EOF)
1.1 jfb 445: c = 0;
446: else if (c == '\n')
1.2 jfb 447: yylval.lineno = conf_lineno++;
448: else if (c == '$') {
449: c = lgetc(conf_fin);
1.1 jfb 450: do {
1.2 jfb 451: *bp++ = (char)c;
452: if (bp == ep) {
453: yyerror("macro name too long");
454: return (-1);
455: }
456: c = lgetc(conf_fin);
457: } while (isalnum(c) || c == '_');
458: lungetc(c, conf_fin);
459: *bp = '\0';
460:
461: mval = cvs_conf_getmacro(buf);
462: if (mval == NULL) {
463: yyerror("undefined macro `%s'", buf);
464: return (-1);
465: }
466:
467: conf_pbuf = strdup(mval);
1.6 ! xsa 468: if (conf_pbuf == NULL) {
! 469: cvs_log(LP_ERRNO, "failed to copy macro");
! 470: return (-1);
! 471: }
1.2 jfb 472: conf_pbind = 0;
473: goto lex_start;
474: }
475: else if ((c == '=') || (c == ','))
476: ; /* nothing */
477: else {
478: do {
479: *bp++ = (char)c;
1.1 jfb 480: if (bp == ep) {
481: yyerror("string too long");
482: return (-1);
483: }
484:
485: c = lgetc(conf_fin);
486: } while ((c != EOF) && (c != ' ') && (c != '\n'));
1.2 jfb 487: lungetc(c, conf_fin);
1.1 jfb 488: *bp = '\0';
489: c = lookup(buf);
490: if (c == STRING) {
491: yylval.v.string = strdup(buf);
492: if (yylval.v.string == NULL) {
493: cvs_log(LP_ERRNO,
494: "failed to copy token string");
495: return (-1);
496: }
497: }
498: }
499:
500: return (c);
501: }
502:
503:
504:
505: int
506: yyerror(const char *fmt, ...)
507: {
508: char *nfmt;
509: va_list vap;
510:
511: if (asprintf(&nfmt, "%s:%d: %s", conf_file, yylval.lineno, fmt) == -1) {
512: cvs_log(LP_ERRNO, "failed to allocate message buffer");
513: return (-1);
514: }
1.5 pat 515:
516: va_start(vap, fmt);
1.1 jfb 517: cvs_vlog(LP_ERR, nfmt, vap);
1.5 pat 518: va_end(vap);
1.1 jfb 519:
520: free(nfmt);
521: return (0);
522:
523: }
524:
525:
526: /*
527: * cvs_conf_setmacro()
528: *
529: * Add an entry in the macro list for the macro whose name is <macro> and
530: * whose value is <val>.
531: * Returns 0 on success, or -1 on failure.
532: */
533:
534: int
535: cvs_conf_setmacro(char *macro, char *val)
536: {
537: struct conf_macro *cmp;
538:
539: cmp = (struct conf_macro *)malloc(sizeof(*cmp));
540: if (cmp == NULL) {
541: cvs_log(LP_ERRNO, "failed to allocate macro");
542: return (-1);
543: }
544:
545: /* these strings were already dup'ed by the lexer */
546: cmp->cm_name = macro;
547: cmp->cm_val = val;
548:
549: SIMPLEQ_INSERT_TAIL(&conf_macros, cmp, cm_list);
550:
551: return (0);
552: }
553:
554:
555: /*
556: * cvs_conf_getmacro()
557: *
558: * Get a macro <macro>'s associated value. Returns the value string on
559: * success, or NULL if no such macro exists.
560: */
561:
562: const char*
563: cvs_conf_getmacro(const char *macro)
564: {
565: struct conf_macro *cmp;
566:
567: SIMPLEQ_FOREACH(cmp, &conf_macros, cm_list)
568: if (strcmp(cmp->cm_name, macro) == 0)
569: return (cmp->cm_val);
570:
571: return (NULL);
572: }
573:
574:
575: /*
576: * cvs_conf_read()
577: *
578: * Parse the contents of the configuration file <conf>.
579: */
580:
581: int
582: cvs_conf_read(const char *conf)
583: {
584: struct conf_macro *cmp;
585:
586: SIMPLEQ_INIT(&conf_macros);
587: TAILQ_INIT(&acl_rules);
588: acl_nrules = 0;
589:
1.3 jfb 590: cvs_log(LP_INFO, "using configuration file `%s'", conf);
1.1 jfb 591: conf_file = conf;
592: conf_fin = fopen(conf, "r");
593: if (conf_fin == NULL) {
594: cvs_log(LP_ERRNO, "failed to open configuration `%s'", conf);
595: return (-1);
596: }
597:
598: if (yyparse() != 0)
599: conf_lineno = -1;
600:
601: (void)fclose(conf_fin);
602:
603: /* we can get rid of macros now */
604: while ((cmp = SIMPLEQ_FIRST(&conf_macros)) != NULL) {
605: SIMPLEQ_REMOVE_HEAD(&conf_macros, cm_list);
606: free(cmp->cm_name);
607: free(cmp->cm_val);
608: free(cmp);
609: }
610:
611: cvs_log(LP_INFO, "config %s parsed successfully", conf);
612:
613: return (conf_lineno);
614: }
615:
616:
617: /*
618: * cvs_acl_addrule()
619: *
620: * Add a rule to the currently loaded ACL rules.
621: */
622:
623: int
624: cvs_acl_addrule(struct acl_rule *rule)
625: {
626: if (acl_nrules == CVS_ACL_MAXRULES) {
627: cvs_log(LP_ERR, "failed to add ACL rule: Ruleset full");
628: return (-1);
629: }
630:
631: TAILQ_INSERT_TAIL(&acl_rules, rule, ar_list);
632: return (0);
633: }
634:
635:
636: /*
637: * cvs_acl_eval()
638: *
639: * Evaluate a thingamajimmie against the currently loaded ACL ruleset.
640: * Returns CVS_ACL_ALLOW if the operation is permitted, CVS_ACL_DENY otherwise.
641: */
642:
643: u_int
644: cvs_acl_eval(struct cvs_op *op)
645: {
646: u_int res;
1.4 jfb 647: char fpath[MAXPATHLEN];
1.2 jfb 648: CVSFILE *cf;
649: struct acl_rule *rule;
1.1 jfb 650:
651: /* deny by default */
652: res = acl_defact;
653:
1.2 jfb 654: TAILQ_FOREACH(rule, &acl_rules, ar_list) {
655: if (((op->co_op != CVS_OP_ANY) && (op->co_op != rule->ar_op)) ||
656: !cvs_acl_matchuid(rule, op->co_uid) ||
657: !cvs_acl_matchtag(op->co_tag, rule->ar_tag))
1.1 jfb 658: continue;
659:
1.2 jfb 660: /* see if one of the files has a matching path */
1.4 jfb 661: TAILQ_FOREACH(cf, &(op->co_files), cf_list) {
662: /* XXX borked */
663: if (!cvs_acl_matchpath(fpath, rule->ar_path))
1.2 jfb 664: continue;
1.4 jfb 665: }
1.2 jfb 666:
667: res = rule->ar_act;
668:
669: if (rule->ar_opts & CVS_ACL_LOGOPT)
670: cvs_log(LP_WARN, "act=%u, tag=%s, uid=%u",
671: op->co_op, op->co_tag, op->co_uid);
672: if (rule->ar_opts & CVS_ACL_QUICKOPT)
1.1 jfb 673: break;
674: }
675:
676: return (res);
677: }
678:
679:
680: /*
681: * cvs_acl_matchuid()
682: *
683: * Check if an ACL rule has a UID matching <uid>. If no user is specified
684: * for a given rule, any UID will match.
685: * Returns 1 if this is the case, 0 otherwise.
686: */
687:
688: u_int
689: cvs_acl_matchuid(struct acl_rule *rule, uid_t uid)
690: {
691: struct acl_user *aup;
692:
693: if (SLIST_EMPTY(&(rule->ar_users)))
694: return (1);
695:
696: SLIST_FOREACH(aup, &(rule->ar_users), au_list)
697: if (aup->au_uid == uid)
698: return (1);
699: return (0);
700: }
701:
702:
703: /*
704: * cvs_acl_matchtag()
705: *
706: * Returns 1 if this is the case, 0 otherwise.
707: */
708:
709: u_int
710: cvs_acl_matchtag(const char *tag1, const char *tag2)
711: {
712: if ((tag1 == NULL) && (tag2 == NULL)) /* HEAD */
713: return (1);
714:
715: if ((tag1 != NULL) && (tag2 != NULL) &&
716: (strcmp(tag1, tag2) == 0))
717: return (1);
718:
719: return (0);
720: }
721:
722:
723: /*
724: * cvs_acl_matchpath()
725: *
726: * Check if the path <op_path> is a subpath of <acl_path>.
727: * Returns 1 if this is the case, 0 otherwise.
728: */
729:
730: u_int
731: cvs_acl_matchpath(const char *op_path, const char *acl_path)
732: {
733: size_t len;
734: char rop_path[MAXPATHLEN];
735:
736: /* if the ACL path is NULL, apply on all paths */
737: if (acl_path == NULL)
738: return (1);
739:
740: if (realpath(op_path, rop_path) == NULL) {
741: cvs_log(LP_ERRNO, "failed to convert `%s' to a real path",
742: op_path);
743: return (0);
744: }
745:
746: len = strlen(rop_path);
747:
748: if (strncmp(rop_path, acl_path, len) == 0)
749: return (1);
750:
751: return (0);
752: }